青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆 - 97, 文章 - 22, 評(píng)論 - 81, 引用 - 0
數(shù)據(jù)加載中……

網(wǎng)絡(luò)編程路漫漫(一)啟程

一、套接字
      1、什么是套接字(socket)
      2、創(chuàng)建套接字
            1) 協(xié)議族(Protocol Family)
            2) 套接字類(lèi)型
            3) 協(xié)議確定
      3、分配IP和端口
            1) IP(Internet Protocol)
            2) 端口
            3) 地址信息詳解
            4) 主機(jī)字節(jié)序/網(wǎng)絡(luò)字節(jié)序
            5) 綁定IP和端口
二、基于TCP的服務(wù)器端
      1、TCP/IP協(xié)議
      2、TCP服務(wù)器主流程
      3、等待連接請(qǐng)求
      4、受理請(qǐng)求
      5、數(shù)據(jù)交換
      6、斷開(kāi)連接
      7、調(diào)試工具
三、基于TCP的客戶(hù)端
      1、TCP客戶(hù)端主流程
      2、請(qǐng)求連接
四、回顧主流程

 一、套接字
        1、什么是套接字(socket)
        首先,我不想闡述太多的概念,直接拿例子說(shuō)話最實(shí)際,說(shuō)得越多就越亂。那么讓我們先來(lái)看一個(gè)概念(囧),網(wǎng)絡(luò)編程。
        網(wǎng)絡(luò)編程就是編寫(xiě)程序?qū)膳_(tái)連網(wǎng)的計(jì)算機(jī)實(shí)現(xiàn)數(shù)據(jù)交換。如何進(jìn)行數(shù)據(jù)交換?首先需要物理連接,這個(gè)不是我們程序員需要關(guān)心的事情,我們需要關(guān)心的是如何編寫(xiě)數(shù)據(jù)傳輸軟件。操作系統(tǒng)為我們提供了“套接字”(socket)模塊來(lái)干這件事。
        套接字是網(wǎng)絡(luò)數(shù)據(jù)傳輸用的軟件設(shè)備。更加直白的解釋是這樣的:網(wǎng)絡(luò)上兩個(gè)程序通過(guò)一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)交換,這個(gè)連接的一端稱(chēng)為一個(gè)套接字。
        2、創(chuàng)建套接字
        好了,概念貌似是比較清晰了的樣子,那么讓我們來(lái)看看套接字到底是個(gè)什么鬼。
        創(chuàng)建套接字的函數(shù)如下:
                int socket(int domain, int type, int protocol);
                          成功時(shí)返回文件描述符,失敗時(shí)返回-1。
        這個(gè)函數(shù)是操作系統(tǒng)提供的,用于創(chuàng)建一個(gè)套接字的內(nèi)核對(duì)象(內(nèi)核對(duì)象是指由操作系統(tǒng)創(chuàng)建的一系列資源,比如進(jìn)程、線程、文件、套接字、信號(hào)量、互斥量等等)。返回值是一個(gè)文件描述符,可以簡(jiǎn)單的理解為一個(gè)ID。如果熟悉MFC的話,我們會(huì)發(fā)現(xiàn)Windows開(kāi)發(fā)時(shí)創(chuàng)建窗口返回的是一個(gè)叫“句柄”的東西。沒(méi)錯(cuò)了,Linux上叫文件描述符,Windows上叫句柄。
        這里有必要講一下文件描述符和socket的關(guān)系,因?yàn)樵贚inux下socket操作和文件操作沒(méi)有區(qū)別,即socket也是文件的一種(Windows下并非如此)。文件描述符是系統(tǒng)分配給文件或套接字的整數(shù)。在stdio.h頭文件下有三個(gè)預(yù)定義的文件描述符:
    #define stdin  (&__iob_func()[0])
    #define stdout (&__iob_func()[1])
    #define stderr (&__iob_func()[2])
         即0代表標(biāo)準(zhǔn)輸入,1代表標(biāo)準(zhǔn)輸出,2代表標(biāo)準(zhǔn)錯(cuò)誤。所以應(yīng)用程序申請(qǐng)的socket編號(hào)從3開(kāi)始。
         接下來(lái)解釋下socket創(chuàng)建時(shí)用到的參數(shù):
         domain 代表套接字中使用的協(xié)議族,type 代表套接字?jǐn)?shù)據(jù)傳輸類(lèi)型,protocol 代表通信協(xié)議。這樣一解釋?zhuān)遣皇潜緛?lái)還有點(diǎn)理解,現(xiàn)在完全懵逼了?不要緊張,一個(gè)一個(gè)來(lái)解釋。
            1)協(xié)議族(Protocol Family)
         能用圖的時(shí)候堅(jiān)決不寫(xiě)字,所以就有了下面這個(gè)圖。
 
圖一-2-1
        IPv4是互聯(lián)網(wǎng)協(xié)議的第四版,也是第一個(gè)被廣泛使用,構(gòu)成現(xiàn)今互聯(lián)網(wǎng)技術(shù)的基礎(chǔ)的協(xié)議。所以我們著重講解PF_INET,其它的暫且可以不管了,是不是很開(kāi)心?
            2)套接字類(lèi)型
        套接字類(lèi)型決定了數(shù)據(jù)傳輸方式。總共有兩種:SOCK_STREAM創(chuàng)建面向連接的套接字、SOCK_DGRAM創(chuàng)建面向消息的套接字。
        SOCK_STREAM:
            面向連接,可以理解成一條傳送帶,只要這條傳送帶質(zhì)量沒(méi)有問(wèn)題(也就是網(wǎng)一直連著),那么傳送帶上的物品就不會(huì)丟失,較晚的物品不會(huì)先到達(dá)(傳送帶的保序特性),并且傳輸?shù)奈锲凡淮嬖跀?shù)據(jù)邊界:即發(fā)送方和接收方的發(fā)送/接收的動(dòng)作并非一一對(duì)應(yīng),比如發(fā)送方在傳送帶起點(diǎn)連續(xù)多次放了一些物品,接收方可以只通過(guò)一次操作就取走所有物品。這是因?yàn)榘l(fā)送和接收數(shù)據(jù)有一個(gè)內(nèi)部緩沖(buffer),發(fā)送方的數(shù)據(jù)通過(guò)發(fā)送方的輸出緩沖存放至接收方的輸入緩沖,如果接收方不取走數(shù)據(jù),這些數(shù)據(jù)就一直在它的輸入緩沖中。緩沖滿(mǎn)了會(huì)提供數(shù)據(jù)重傳機(jī)制,所以面向連接的套接字不會(huì)存在數(shù)據(jù)丟失。一句話概括:
            可靠、保序、面向連接的數(shù)據(jù)傳輸方式的套接字。
        SOCK_DGRAM:
            面向消息,可以理解成快遞。快遞運(yùn)送過(guò)程有可能丟失,不同快遞公司運(yùn)送的速度不同,所以無(wú)法保證先寄出的快遞先到達(dá),并且發(fā)送一個(gè)快遞,接收的也是一個(gè),因此有數(shù)據(jù)邊界。而且必須限制快遞的大小。一句話概括:
            不可靠、不保序、以高速數(shù)據(jù)傳輸為目的的套接字。
            3)協(xié)議確定
        參數(shù)PF_INET指定的IPv4協(xié)議族中,指定SOCK_STREAM面向連接的傳輸方式,滿(mǎn)足前兩個(gè)條件的協(xié)議只有IPPROTO_TCP,因此可以如下調(diào)用創(chuàng)建:
        int tcp_sock = socket(PF_INET, SOCK_STREAM, 0);
        參數(shù)PF_INET指定的IPv4協(xié)議族中,指定SOCK_DGRAM面向消息的傳輸方式,滿(mǎn)足前兩個(gè)條件的協(xié)議只有IPPROTO_UDP,因此可以如下調(diào)用創(chuàng)建:
        int udp_sock = socket(PF_INET, SOCK_DGRAM, 0);
        因?yàn)閮煞Nsocket都只有一種協(xié)議,所以第三個(gè)參數(shù)可以省略。
        那么什么時(shí)候用TCP,什么時(shí)候用UDP呢?來(lái)日方長(zhǎng)。先賣(mài)個(gè)關(guān)子。這里先以TCP連接為例子進(jìn)行展開(kāi)。
        3、分配IP和端口
        上面講了一大堆,竟然只講了一個(gè)socket的創(chuàng)建,看來(lái)我還是太啰嗦了。那么,既然啰嗦了,就再啰嗦幾句吧(囧)。創(chuàng)建完的套接字需要綁定一個(gè)網(wǎng)絡(luò)地址,這樣網(wǎng)絡(luò)的兩端才能進(jìn)行通信。
        綁定套接字網(wǎng)絡(luò)地址的函數(shù)如下:
                int bind(int sockfd, struct sockaddr* addr, socket_t addrlen);
                          成功時(shí)返回0,失敗時(shí)返回-1。
         第一個(gè)參數(shù)就是我們創(chuàng)建的那個(gè)套接字ID(文件描述符),第二個(gè)參數(shù)就說(shuō)來(lái)話長(zhǎng)了,首先來(lái)看下sockaddr這個(gè)結(jié)構(gòu)體的定義:
                struct sockaddr {
          unsigned short sin_family;       /* Address family */
          char sa_data[14];                /* 14 bytes of protocol address */
      };

         看完了嗎?
         不說(shuō)話就是看完了,那我們?cè)賮?lái)看另一個(gè)?
                struct sockaddr_in {
          unsigned short sin_family;       /* Address family */
          unsigned short sin_port;         /* Port number */
          struct in_addr sin_addr;         /* Internet address */
          unsigned char sin_zero[8];       /* Same size as struct sockaddr */
      };

         嗯,細(xì)心的你應(yīng)該發(fā)現(xiàn)了,還有一個(gè)結(jié)構(gòu)體沒(méi)有定義。
                struct in_addr {
            unsigned int s_addr;            /* Internet address */
      };
         現(xiàn)在開(kāi)始科普,1個(gè)sockaddr結(jié)構(gòu)體的總字節(jié)數(shù)為16(2+14),sockaddr_in的總字節(jié)數(shù)也是16(2+2+4+8)。所以?xún)烧呖梢酝ㄟ^(guò)取地址后用指針進(jìn)行強(qiáng)制轉(zhuǎn)換(C/C++中的基礎(chǔ)知識(shí))。那么為什么要設(shè)計(jì)兩種結(jié)構(gòu)體呢?先來(lái)看下IP和端口的定義。
            1)IP(Internet Protocol)
        為了使計(jì)算機(jī)連接到網(wǎng)絡(luò)并收發(fā)數(shù)據(jù),必須向其分配IP地址。IP地址主要分為兩類(lèi):IPv4和IPv6。IPv4是4字節(jié)的,IPv6是為了應(yīng)對(duì)IP地址耗盡的問(wèn)題而提出的標(biāo)準(zhǔn),它是16字節(jié)的。目前主要應(yīng)用的還是IPv4,這里也只討論IPv4的情況。
        IPv4標(biāo)準(zhǔn)的4字節(jié)IP地址分為網(wǎng)絡(luò)地址和主機(jī)地址,且分為A、B、C、D、E等類(lèi)型,E類(lèi)作為保留使用,如圖一-3-1所示。
圖一-3-1
        數(shù)據(jù)在互聯(lián)網(wǎng)進(jìn)行傳輸?shù)臅r(shí)候,首先瀏覽網(wǎng)絡(luò)地址,將數(shù)據(jù)傳輸?shù)綄?duì)應(yīng)的網(wǎng)絡(luò),再由該網(wǎng)絡(luò)將數(shù)據(jù)分派到對(duì)應(yīng)的主機(jī)。
        并且只需要判斷首字節(jié)就能清楚的知道是哪類(lèi)地址。A類(lèi)地址首字節(jié)范圍:0~127,B類(lèi)地址首字節(jié)范圍:128~191,A類(lèi)地址首字節(jié)范圍:192~223。
            2)端口號(hào)
        IP用于區(qū)分計(jì)算機(jī),端口號(hào)用于區(qū)分應(yīng)用程序。比如你在看視頻和瀏覽網(wǎng)頁(yè)的時(shí)候都需要用到數(shù)據(jù)傳輸,那么如何區(qū)分?jǐn)?shù)據(jù)是傳遞到那個(gè)應(yīng)用程序呢,就需要給套接字指定端口號(hào)。端口號(hào)是一個(gè)2字節(jié)的無(wú)符號(hào)整型,端口號(hào)范圍0-65535,其中0-1023為知名端口已經(jīng)被占用(如FTP、HTTP、SMTP)。
            3)地址信息詳解
        了解IP和端口后,就可以填充sockaddr_in結(jié)構(gòu)了。讓我們?cè)賮?lái)回顧一下sockaddr_in結(jié)構(gòu)體。
                * sin_family
                        還記得創(chuàng)建socket時(shí)候的第一個(gè)參數(shù)嗎?我們這里只討論IPv4協(xié)議族,所以這里用PF_INET即可(有些代碼里用AF_INET,它和PF_INET在Windows里是同一個(gè)宏)。
                * sin_port
                        保存16位(2字節(jié))端口號(hào),需要以網(wǎng)絡(luò)字節(jié)序保存(稍后介紹)。
                * sin_addr
                        保存32位(4字節(jié))IP地址信息,以網(wǎng)絡(luò)字節(jié)序保存。雖然是個(gè)結(jié)構(gòu)體,但是結(jié)構(gòu)體下只有一個(gè)整型變量,所以可以直接理解成32位整數(shù)即可。
                * sin_zero
                        保留字段,只是為了和sockaddr字節(jié)對(duì)齊。引入sockaddr_in的原因是當(dāng)協(xié)議族不是PF_INET時(shí),情況不一樣。比如IPv6的情況,IP地址是16個(gè)字節(jié)的,sin_addr明顯不夠用。
            4)主機(jī)字節(jié)序/網(wǎng)絡(luò)字節(jié)序
        主機(jī)字節(jié)序主要有兩種:大端序:高字節(jié)放低位地址、小端序:高字節(jié)放高位地址。
        例如,一個(gè)2字節(jié)的數(shù)字0x1234,存放的基地址為0x10。如果采用大端序,0x10存放0x12,0x11存放0x34。如果采用小端序,0x10存放0x34,0x11存放0x12。
        網(wǎng)絡(luò)傳輸數(shù)據(jù)時(shí)統(tǒng)一采用大端序。所以必須先將數(shù)據(jù)轉(zhuǎn)化成大端序格式再進(jìn)行網(wǎng)絡(luò)傳輸。轉(zhuǎn)換函數(shù)操作系統(tǒng)已經(jīng)給出了,總共有如下四個(gè):htons、ntohs、htonl、ntohl。
        我去!這是什么鬼?
        不用擔(dān)心,其實(shí)這四個(gè)函數(shù)很容易識(shí)別,它是由h、to、n、l、s這幾個(gè)詞拼出來(lái)的。h代表host,n則是network,l是long,s是short。
        主要用于對(duì)IP和端口進(jìn)行主機(jī)字節(jié)序和網(wǎng)絡(luò)字節(jié)序的轉(zhuǎn)換。那么也許有人會(huì)問(wèn),那實(shí)際數(shù)據(jù)傳輸?shù)臅r(shí)候也需要這么干嗎?答案是否定的!
            5)綁定IP和端口
        實(shí)際綁定過(guò)程如下代碼所示:
                #define IP "156.123.122.11"
                #define PORT 10101
                struct sockaddr_in serv_addr;

                memset(&serv_addr, 0, sizeof(serv_addr));
                serv_addr.sin_family = PF_INET;
                serv_addr.sin_addr.s_addr = inet_addr(IP);
                serv_addr.sin_port = htons(PORT);
                bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
        其中inet_addr是系統(tǒng)函數(shù),將點(diǎn)分十進(jìn)制的IP地址轉(zhuǎn)換成32位大端序的整型值,失敗返回 INADDR_NONE。
 二、基于TCP的服務(wù)器端
        1、TCP/IP協(xié)議
        根據(jù)數(shù)據(jù)傳輸方式的不同,基于網(wǎng)絡(luò)協(xié)議的套接字一般分為T(mén)CP套接字和UDP套接字。還記得創(chuàng)建套接字時(shí)指定的第二個(gè)參數(shù)嗎?當(dāng)它為SOCK_STREAM時(shí),即為T(mén)CP套接字。TCP是Transmission Control Protocol(傳輸控制協(xié)議)的簡(jiǎn)稱(chēng)。TCP和UDP處于網(wǎng)絡(luò)四層協(xié)議棧的傳輸層(網(wǎng)絡(luò)接口層-IP層-傳輸層-應(yīng)用層)。之所以要分層,是為了通過(guò)標(biāo)準(zhǔn)化操作設(shè)計(jì)開(kāi)放式系統(tǒng),路由器用來(lái)完成IP層的交互任務(wù),不同公司生產(chǎn)的路由器可以進(jìn)行互相替換,因?yàn)樯a(chǎn)商會(huì)按照IP層的標(biāo)準(zhǔn)制造。而網(wǎng)卡則是遵循了網(wǎng)絡(luò)接口層的協(xié)議標(biāo)準(zhǔn)制造的。
        2、TCP服務(wù)端主流程

圖二-2-1
        如圖二-2-1所示,為T(mén)CP服務(wù)端的函數(shù)調(diào)用順序。調(diào)用socket創(chuàng)建套接字,然后利用bind為套接字分配地址。利用listen使對(duì)應(yīng)套接字進(jìn)入等待連接請(qǐng)求狀態(tài)。如果有新的請(qǐng)求,則調(diào)用accept接收新的客戶(hù)端連接。接著使用read(接收)和write(發(fā)送)實(shí)現(xiàn)數(shù)據(jù)交換。數(shù)據(jù)交換完畢調(diào)用close關(guān)閉套接字。
        3、等待連接請(qǐng)求
        只有當(dāng)服務(wù)端調(diào)用了listen函數(shù)等待連接請(qǐng)求,客戶(hù)端才有機(jī)會(huì)接入。listen的調(diào)用比較簡(jiǎn)單。
                int listen(int sockfd, int backlog);
                          成功時(shí)返回0,失敗時(shí)返回-1。
        sockfd代表了希望進(jìn)入等待連接請(qǐng)求的文件描述符,傳遞完畢后該套接字成為服務(wù)器端套接字(監(jiān)聽(tīng)套接字)。backlog為指定的連接請(qǐng)求等待隊(duì)列的長(zhǎng)度。
        對(duì)于一個(gè)TCP連接,服務(wù)器端與客戶(hù)端需要通過(guò)三次握手來(lái)建立網(wǎng)絡(luò)連接。當(dāng)三次握手成功后,端口狀態(tài)由LISTEN轉(zhuǎn)變?yōu)镋STABLISHED,接著這條鏈路上就可以開(kāi)始傳送數(shù)據(jù)了。每一個(gè)處于監(jiān)聽(tīng)(Listen)狀態(tài)的端口,都有自己的監(jiān)聽(tīng)隊(duì)列。監(jiān)聽(tīng)隊(duì)列的長(zhǎng)度與如下兩方面有關(guān):
            a. proc/sys/net/core/somaxconn
            b. listen 的第二個(gè)參數(shù)backlog
        隊(duì)列大小為兩者的小值,可以通過(guò) echo 1000 > proc/sys/net/core/somaxconn 來(lái)修改前者。第二個(gè)參數(shù)backlog設(shè)置太小會(huì)導(dǎo)致高并發(fā)情況下,客戶(hù)端connect的時(shí)候隊(duì)列滿(mǎn)了,服務(wù)器端就不會(huì)受理了,客戶(hù)端繼續(xù)嘗試...如果還是滿(mǎn)的...就這樣惡性循環(huán),最后導(dǎo)致連接超時(shí)。
        4、受理請(qǐng)求
        服務(wù)端受理客戶(hù)端請(qǐng)求是由以下函數(shù)完成的:
                int accept(int sockfd, struct sockaddr* addr, socket_t* addrlen);
                          成功時(shí)返回創(chuàng)建的套接字文件描述符,失敗時(shí)返回-1。
        函數(shù)調(diào)用成功后,操作系統(tǒng)將產(chǎn)生用于數(shù)據(jù)I/O的套接字。當(dāng)客戶(hù)端發(fā)起connect(稍后第三節(jié)會(huì)講到)請(qǐng)求,服務(wù)端不會(huì)馬上受理,而是將這些請(qǐng)求放在listen所對(duì)應(yīng)套接字的等待隊(duì)列中,accept的作用就是將隊(duì)列首的請(qǐng)求取出來(lái)進(jìn)行受理。accept的第二個(gè)參數(shù)addr就保存了客戶(hù)端的地址信息,因?yàn)檎{(diào)用前我們并不知道是哪個(gè)客戶(hù)端接入的,所以我們并不需要填充addr指向的結(jié)構(gòu)。當(dāng)accept返回時(shí),自然就把地址填在*addr(注意前面的*,表示解引用,因?yàn)閍ddr是個(gè)指針)了。
        并且如果沒(méi)有特殊指定,accept是同步阻塞的,即當(dāng)?shù)却?duì)列為空時(shí),accept函數(shù)不會(huì)返回。
        5、數(shù)據(jù)交換
        所謂數(shù)據(jù)交換,就是我們通常所說(shuō)的I/O(Input/Output)。對(duì)于TCP連接(UDP的先不介紹),主要有兩個(gè)系列的函數(shù):
        a、read/write      (Linux)
        b、recv/send       (Windows/Linux)
        限于篇幅問(wèn)題,這里簡(jiǎn)單介紹下read和write,剩余內(nèi)容待到日后再詳細(xì)研究。
                int read(int fd, void *buf, size_t nbytes);
                          成功時(shí)返回接收到的字節(jié)數(shù)(遇到文件結(jié)尾返回0),失敗時(shí)返回-1。
        read用于讀取(接收)數(shù)據(jù)。fd代表數(shù)據(jù)接收對(duì)象的文件描述符,可以是文件也可以是套接字;buf保存了接收數(shù)據(jù)的首地址;nbytes代表將要接收的最大字節(jié)數(shù)。
                int write(int fd, const void *buf, size_t nbytes);
                          成功時(shí)返回發(fā)送的字節(jié)數(shù),失敗時(shí)返回-1。
        write用于寫(xiě)入(發(fā)送)數(shù)據(jù)。fd代表數(shù)據(jù)發(fā)送對(duì)象的文件描述符,可以是文件也可以是套接字;buf傳入的是發(fā)送數(shù)據(jù)的首地址;nbytes代表發(fā)送的最大字節(jié)數(shù)。
        read和write相呼應(yīng),一端進(jìn)行write,另一端進(jìn)行read。這里涉及到I/O緩沖的問(wèn)題,有必要解釋一下。對(duì)于TCP而言,數(shù)據(jù)收發(fā)無(wú)邊界,意味著服務(wù)端調(diào)用3次write函數(shù),每次10字節(jié)(數(shù)據(jù)收發(fā)以字節(jié)為單位),客戶(hù)端有可能通過(guò)一次read調(diào)用直接接收30字節(jié)的數(shù)據(jù),反之亦然。那么這個(gè)是如何做到的呢?發(fā)送出去的字節(jié)在沒(méi)有接收的情況下是寄存在哪里的?
        如圖二-5-1,write函數(shù)調(diào)用后并非立即發(fā)送數(shù)據(jù),read函數(shù)調(diào)用后也并非立即接收數(shù)據(jù)。write調(diào)用瞬間,數(shù)據(jù)將移至輸出緩沖;read調(diào)用瞬間,從輸入緩沖讀取數(shù)據(jù)。并且在適當(dāng)?shù)臅r(shí)候,會(huì)將本地的輸出緩沖的數(shù)據(jù)傳送到對(duì)方的輸入緩沖中區(qū)。
圖二-5-1
        這里需要理清幾個(gè)知識(shí)點(diǎn):
        a.每個(gè)TCP套接字都有自己的I/O緩沖,并且在創(chuàng)建該套接字時(shí)自動(dòng)生成(即用戶(hù)不需要關(guān)心它的創(chuàng)建和銷(xiāo)毀)。
        b.如果輸出緩沖中還有數(shù)據(jù),關(guān)閉套接字,這些數(shù)據(jù)還是會(huì)傳送出去。
        c.如果輸入緩沖中還有數(shù)據(jù),關(guān)閉套接字,這些數(shù)據(jù)就丟失了。
        那么write函數(shù)何時(shí)返回?等到對(duì)方read之后才返回?答案是否定的。我們之前提到了輸出緩沖,沒(méi)錯(cuò),write函數(shù)就是負(fù)責(zé)將數(shù)據(jù)移動(dòng)到輸出緩沖,然后就返回了。那么如果輸出緩沖滿(mǎn)了怎么辦?這個(gè)又是一個(gè)值得討論的問(wèn)題。需要分阻塞socket和非阻塞socket,限于篇幅先不考慮,以后專(zhuān)門(mén)講這個(gè)問(wèn)題。對(duì)于read的時(shí)候緩沖區(qū)為空時(shí),同樣需要考慮阻塞和非阻塞的情況。
        6、關(guān)閉套接字
        直接給出關(guān)閉函數(shù):
                int close(int sockfd);
                          成功時(shí)返回0,失敗時(shí)返回-1。
        關(guān)閉函數(shù)還有一個(gè)叫shutdown的,屬于“半關(guān)閉”。這里先介紹到這里。
        最后,附上只處理一個(gè)請(qǐng)求的服務(wù)端源碼:https://pan.baidu.com/s/1qXGuhB2
        7、調(diào)試工具
        這里假設(shè)我們的服務(wù)器是架設(shè)在Linux上的,可以利用gcc(GNU Compiler Collection)對(duì)源文件進(jìn)行編譯和鏈接。
            gcc test.c -o test
                    將test.c預(yù)處理、匯編、編譯并鏈接形成可執(zhí)行文件test。-o選項(xiàng)用來(lái)指定輸出文件的文件名。
            gcc -g test.c -o test
                    增加-g選項(xiàng),便于用gdb進(jìn)行調(diào)試。
        程序出現(xiàn)錯(cuò)誤后,會(huì)生成一個(gè)core.test.30212的dump文件(其中30212為當(dāng)時(shí)運(yùn)行時(shí)候的進(jìn)程ID),可以利用gdb進(jìn)行堆棧查看。
            gdb test core.test.30212
        運(yùn)行指令后,進(jìn)入gdb調(diào)試界面。輸入where、backtrace、info stack都可以查看到發(fā)生錯(cuò)誤的堆棧。
        注意:寫(xiě)代碼的時(shí)候可以在核心代碼的前后打上printf用于測(cè)試代碼是否正確運(yùn)行到這里,注意Linux下printf最后需要加上'\n',因?yàn)閜rintf是行緩沖。
 三、基于TCP的客戶(hù)端
        1、TCP客戶(hù)端主流程
        客戶(hù)端的結(jié)構(gòu)相對(duì)于服務(wù)端比較簡(jiǎn)單,創(chuàng)建socket之后調(diào)用connect進(jìn)行連接,剩下的流程就一樣了。
圖三-1-1
        2、請(qǐng)求連接
        請(qǐng)求連接的函數(shù)如下:
                int connect(int sockfd, struct sockaddr* servaddr, socket_t addrlen);
                          成功時(shí)返回0,失敗時(shí)返回-1。
        第一個(gè)參數(shù)為客戶(hù)端的文件描述符,第二個(gè)參數(shù)為服務(wù)端的網(wǎng)絡(luò)地址(同上文中的bind)的指針,第三個(gè)參數(shù)傳入sizeof(sockaddr)即可。
        上文說(shuō)到服務(wù)端調(diào)用listen函數(shù)后創(chuàng)建連接請(qǐng)求的等待隊(duì)列,這時(shí)候客戶(hù)端調(diào)用connect進(jìn)行請(qǐng)求。connect函數(shù)的返回條件是服務(wù)端接收連接請(qǐng)求或者發(fā)生斷網(wǎng)等異常情況,這里的服務(wù)端“接收連接“并不是代表服務(wù)端調(diào)用accept,其實(shí)服務(wù)端把請(qǐng)求記錄到等待隊(duì)列里。
        客戶(hù)端進(jìn)行本次連接的IP和端口號(hào)并不需要應(yīng)用程序去分配,操作系統(tǒng)已經(jīng)把這點(diǎn)做好了。
        同樣附上一個(gè)簡(jiǎn)單的客戶(hù)端連接源碼:https://pan.baidu.com/s/1nuRV8Zf

四、回顧主流程
        最后我們來(lái)回顧下,今天學(xué)到的主要內(nèi)容。用偽代碼來(lái)描述下整個(gè)過(guò)程。
        服務(wù)端:
        int main() {
            server_sock = socket();
            bind(server_sock);
            listen(server_sock, 5);
            client_sock = accept(server_sock);
            read(client_sock, buf);
            write(client_sock, buf);
            close(client_sock);
            close(server_sock);
        }
        客戶(hù)端:
        int main() {
            sock = socket();
            connect(sock);
            write(sock, buf);
            read(sock, buf);
            close(sock);
        }
    
        (未完待續(xù)...)

posted on 2017-12-20 20:36 英雄哪里出來(lái) 閱讀(1499) 評(píng)論(0)  編輯 收藏 引用


只有注冊(cè)用戶(hù)登錄后才能發(fā)表評(píng)論。
網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問(wèn)   Chat2DB   管理


青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲无限av看| 欧美一区二区三区免费观看| 99精品视频免费观看| 亚洲成人在线免费| 国产伦精品一区二区三区照片91| 欧美一区二区三区免费视| 欧美高清视频一二三区| 欧美在线播放高清精品| 久久久亚洲一区| 久久综合影音| 欧美三级乱码| 国内成+人亚洲+欧美+综合在线| 午夜综合激情| 久久精品综合网| 欧美激情精品久久久久久变态| 亚洲毛片在线看| 欧美一区国产在线| 久久久久久国产精品一区| 久久久久久一区| 香蕉国产精品偷在线观看不卡| 国产日韩欧美综合精品| 国产一区二区三区久久 | 亚洲无限乱码一二三四麻| 性色av一区二区怡红| 欧美国产日韩一区二区在线观看| 久久精品人人爽| 亚洲欧美日韩国产成人精品影院| 亚洲国产综合在线| 黄色日韩网站| 亚洲视频在线播放| 久久视频这里只有精品| 亚洲人成毛片在线播放| 亚洲视频中文字幕| 欧美福利在线| 一区三区视频| 欧美在线www| 亚洲美女视频在线免费观看| 久久久精品日韩| 久久亚洲图片| 国产日韩一级二级三级| 一区二区三区国产在线观看| 鲁大师成人一区二区三区| 亚洲视频网在线直播| 欧美电影在线观看| 在线日韩日本国产亚洲| 欧美一区二区在线观看| 亚洲欧洲视频| 亚洲一区在线免费观看| 久久久国产精彩视频美女艺术照福利| 久久综合九九| 一本色道久久88综合亚洲精品ⅰ| 亚洲精品久久久久久久久久久| 欧美高清在线一区| 欧美一区国产在线| 国产伦精品一区二区三区免费迷 | 亚洲精品中文字幕女同| 亚洲茄子视频| 欧美电影在线观看完整版| 亚洲丰满在线| 欧美xxx在线观看| 久久久久国产免费免费| 国产尤物精品| 亚洲成色777777女色窝| 欧美亚洲综合久久| 亚洲欧美国产三级| 国产日韩精品一区| 久久精品中文字幕一区二区三区 | 亚洲欧洲一区二区三区| 日韩午夜视频在线观看| 亚洲国产精品精华液2区45| 久久这里只有精品视频首页| 亚洲二区在线视频| 91久久一区二区| 欧美三级电影网| 久久成人国产精品| 久久精品午夜| 亚洲国产精品悠悠久久琪琪| 亚洲激情国产| 国产精品一区二区三区乱码| 久久青青草原一区二区| 老司机亚洲精品| 在线亚洲一区观看| 午夜精品免费| 91久久极品少妇xxxxⅹ软件| 欧美三级网址| 久久久夜夜夜| 亚洲一区二区视频在线| 国产日韩欧美一区二区三区四区| 99成人在线| 欧美国产一区二区在线观看 | 欧美激情一区在线| 国产精品99久久久久久有的能看| 米奇777在线欧美播放| 一区二区日韩免费看| 国产日韩欧美在线| 91久久线看在观草草青青| 久久精品中文字幕一区| 亚洲精品综合精品自拍| 亚洲女人天堂成人av在线| 性18欧美另类| 一本色道精品久久一区二区三区| 久久综合九色综合网站| 亚洲激情综合| 亚洲尤物影院| 亚洲裸体视频| 久久久久久久久伊人| 国产日韩欧美a| 欧美激情国产高清| 国产一区二区高清不卡| 欧美一区二区三区日韩视频| 免费亚洲婷婷| 久久全球大尺度高清视频| 欧美日一区二区三区在线观看国产免| 亚洲美女诱惑| 久久蜜桃资源一区二区老牛 | 日韩性生活视频| 欧美一区午夜精品| 亚洲欧美一区二区原创| 欧美国产第一页| 一本色道久久综合一区| 亚洲精品综合在线| 1024精品一区二区三区| 久久精品人人做人人综合| 中文欧美在线视频| 欧美福利电影在线观看| 美日韩精品视频| 激情视频一区二区三区| 欧美一区三区二区在线观看| 激情另类综合| 久久精品卡一| 久久夜色精品一区| 国内精品一区二区| 久久精品中文字幕一区二区三区 | 在线不卡中文字幕| 欧美激情一区二区三区在线| 在线成人激情视频| 久久久久久久综合| 久久综合99re88久久爱| 好吊日精品视频| 久久九九久精品国产免费直播| 亚洲精品1区2区| 一本色道久久综合亚洲精品不卡| 国产农村妇女精品| 亚洲欧美一区二区三区久久| 欧美在线播放一区| 国产一级揄自揄精品视频| 久久精品99国产精品| 麻豆freexxxx性91精品| 伊人成人开心激情综合网| 亚洲乱亚洲高清| 亚洲午夜小视频| 国产精品一区免费观看| 久久se精品一区二区| 欧美成人一区二区在线| aa日韩免费精品视频一| 欧美日韩三级在线| 午夜免费电影一区在线观看| 亚洲精品久久久久久久久久久久| 在线亚洲一区二区| 欧美一级大片在线观看| 亚洲激情一区二区| 在线成人av.com| 毛片一区二区| 在线综合+亚洲+欧美中文字幕| 在线看片一区| 欧美激情在线狂野欧美精品| 一本色道久久综合狠狠躁的推荐| 亚洲第一精品久久忘忧草社区| 亚洲一区日本| 男同欧美伦乱| 亚洲伊人一本大道中文字幕| 欧美成人午夜视频| 亚洲免费av片| 久久天堂精品| 亚洲无限av看| 欧美女人交a| 欧美综合二区| 一区二区三区国产盗摄| 在线亚洲国产精品网站| 国产亚洲精品aa午夜观看| 99re66热这里只有精品4| 久久国产免费| 在线一区二区三区做爰视频网站| 欧美精品高清视频| 欧美一级一区| 一区二区三区欧美视频| 亚洲欧美一区二区原创| 一区二区三区亚洲| 国产免费成人av| 欧美日韩免费在线观看| 亚洲精品久久久久久久久久久久| 亚洲肉体裸体xxxx137| 国产精自产拍久久久久久| 亚洲一区二区三区影院| 亚洲福利视频一区二区| 久久久水蜜桃| 日韩亚洲国产欧美| 亚洲第一福利在线观看| 伊人久久噜噜噜躁狠狠躁| 国产免费亚洲高清|