Socket套接字通信 TCP UDP详解(网络通信)
文章目錄
- 一 什么是套接字Socket
- 1.Socket簡介
- 2.Socket的域(domain)
- 3.Socket主要類型(type)
- 4.Socket基本工作流程
- 二 創(chuàng)建套接字Socket
- 1.socket函數(shù)
- 三 綁定套接字Socket與主機網(wǎng)絡(luò)地址
- 1.bind函數(shù)
- 2.struct sockaddr與struct sockaddr_in
- 3.常用填充地址信息的方法
- 4.主機字節(jié)序與網(wǎng)絡(luò)字節(jié)序
- 四 UDP通信的實現(xiàn)
- 1.recvfrom函數(shù)
- 2.sendto函數(shù)
- 3.示例
- 五 TCP通信的實現(xiàn)
- 1.listen函數(shù)(server端)
- 2.accept函數(shù)(server端)
- 3.connect函數(shù)(client端)
- 4.write與read函數(shù)
- 5.send與recv函數(shù)
- 6.示例
- 六 套接字的緩沖區(qū)以及阻塞模式
- 1.緩沖區(qū)
- 2.使用write/send發(fā)送數(shù)據(jù)
- 3.使用read/recv讀取數(shù)據(jù)
- 七 總結(jié)套接字收發(fā)數(shù)據(jù)的過程
一 什么是套接字Socket
1.Socket簡介
所謂套接字(Socket),就是對網(wǎng)絡(luò)中不同主機上的應(yīng)用進(jìn)程之間進(jìn)行雙向通信的端點的抽象。一個套接字就是網(wǎng)絡(luò)上進(jìn)程通信的一端,提供了應(yīng)用層進(jìn)程利用網(wǎng)絡(luò)協(xié)議交換數(shù)據(jù)的機制。從所處的地位來講,套接字上聯(lián)應(yīng)用進(jìn)程,下聯(lián)網(wǎng)絡(luò)協(xié)議棧,是應(yīng)用程序通過網(wǎng)絡(luò)協(xié)議進(jìn)行通信的接口,是應(yīng)用程序與網(wǎng)絡(luò)協(xié)議根進(jìn)行交互的接口 。
Socket(套接字)可以看成是兩個網(wǎng)絡(luò)應(yīng)用程序進(jìn)行通信時,各自通信連接中的端點,這是一個邏輯上的概念。它是網(wǎng)絡(luò)環(huán)境中進(jìn)程間通信的API(應(yīng)用程序編程接口),也是可以被命名和尋址的通信端點,使用中的每一個套接字都有其類型和一個與之相連進(jìn)程。通信時其中一個網(wǎng)絡(luò)應(yīng)用程序?qū)⒁獋鬏數(shù)囊欢涡畔懭胨谥鳈C的 Socket中,該 Socket通過與網(wǎng)絡(luò)接口卡(NIC)相連的傳輸介質(zhì)將這段信息送到另外一臺主機的 Socket中,使對方能夠接收到這段信息。 Socket是由IP地址和端口結(jié)合的,提供向應(yīng)用層進(jìn)程傳送數(shù)據(jù)包的機制 。
2.Socket的域(domain)
域指定套接字通信中使用的網(wǎng)絡(luò)介質(zhì)。最常見的套接字域是 AF_INET(IPv4)或者AF_INET6(IPV6),它是指 Internet 網(wǎng)絡(luò),許多 Linux 局域網(wǎng)使用的都是該網(wǎng)絡(luò),當(dāng)然,因特網(wǎng)自身用的也是它。
3.Socket主要類型(type)
流套接字用于提供面向連接、可靠的數(shù)據(jù)傳輸服務(wù)。該服務(wù)將保證數(shù)據(jù)能夠?qū)崿F(xiàn)無差錯、無重復(fù)送,并按順序接收。流套接字之所以能夠?qū)崿F(xiàn)可靠的數(shù)據(jù)服務(wù),原因在于其使用了傳輸控制協(xié)議,即TCP(The Transmission Control Protocol)協(xié)議 。
數(shù)據(jù)報套接字提供一種無連接的服務(wù)。該服務(wù)并不能保證數(shù)據(jù)傳輸?shù)目煽啃?數(shù)據(jù)有可能在傳輸過程中丟失或出現(xiàn)數(shù)據(jù)重復(fù),且無法保證順序地接收到數(shù)據(jù)。數(shù)據(jù)報套接字使用UDP( User DatagramProtocol)協(xié)議進(jìn)行數(shù)據(jù)的傳輸。由于數(shù)據(jù)報套接字不能保證數(shù)據(jù)傳輸?shù)目煽啃?#xff0c;對于有可能出現(xiàn)的數(shù)據(jù)丟失情況,需要在程序中做相應(yīng)的處理 。
原始套接字與標(biāo)準(zhǔn)套接字(標(biāo)準(zhǔn)套接字指的是前面介紹的流套接字和數(shù)據(jù)報套接字)的區(qū)別在于:原始套接字可以讀寫內(nèi)核沒有處理的IP數(shù)據(jù)包,而流套接字只能讀取TCP協(xié)議的數(shù)據(jù),數(shù)據(jù)報套接字只能讀取UDP協(xié)議的數(shù)據(jù)。因此,如果要訪問其他協(xié)議發(fā)送的數(shù)據(jù)必須使用原始套接 。
4.Socket基本工作流程
要通過互聯(lián)網(wǎng)進(jìn)行通信,至少需要一對套接字,其中一個運行于客戶端,我們稱之為 Client Socket,另一個運行于服務(wù)器端,我們稱之為 Server Socket 。根據(jù)連接啟動的方式以及本地套接字要連接的目標(biāo),套接字之間的連接過程可以分為三個步驟 :
所謂服務(wù)器監(jiān)聽,是指服務(wù)器端套接字并不定位具體的客戶端套接字,而是處于等待連接的狀態(tài),實時監(jiān)控網(wǎng)絡(luò)狀態(tài) 。
所謂客戶端請求,是指由客戶端的套接字提出連接請求,要連接的目標(biāo)是服務(wù)器端的套接字。為此,客戶端的套接字必須首先描述它要連接的服務(wù)器的套接字,指出服務(wù)器端套接字的地址和端口號,然后就向服務(wù)器端接字提出連接請求 。
所謂連接確認(rèn),是指當(dāng)服務(wù)器端套接字監(jiān)聽到或者說接收到客戶端套接字的連接請求,就會響應(yīng)客戶端套接字的請求,建立一個新的線程,并把服務(wù)器端套接字的描述發(fā)送給客戶端。一旦客戶端確認(rèn)了此描述,連接就建立好了。而服務(wù)器端套接字繼續(xù)處于監(jiān)聽狀態(tài),接收其他客戶端套接字的連接請求 。
二 創(chuàng)建套接字Socket
1.socket函數(shù)
int socket(int domain, int type, int protocol); /* 1.函數(shù)功能:創(chuàng)建套接字 2.參數(shù):int domain:套接字的域通常為 AF_INET(IPv4)或者AF_INET6(IPV6)int type:套接字類型通常為 SOCK_STREAM、SOCK_DGRAMint protocol:0 :使用默認(rèn)協(xié)議IPPROTO_TCP:使用TCP協(xié)議IPPROTO_UDP:使用UDP協(xié)議 3.返回值:成功:返回套接字描述符失敗:-1 */三 綁定套接字Socket與主機網(wǎng)絡(luò)地址
1.bind函數(shù)
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen); /* 1.函數(shù)功能:綁定套接字Socket與主機網(wǎng)絡(luò)地址信息 2.參數(shù):int sockfd: 套接字描述符const struct sockaddr *addr:主機地址信息,下文詳解socklen_t addrlen: 參數(shù)2的長度(字節(jié)) 3.返回值:成功:0失敗:-1 */2.struct sockaddr與struct sockaddr_in
//以下主要摘自LINUX手冊 typedef unsigned short int sa_family_t; /* Structure describing a generic socket address.翻譯:描述通用套接字地址的結(jié)構(gòu) */ struct sockaddr { sa_family_t sa_family;//地址族char sa_data[14];//14字節(jié),包含套接字中的目標(biāo)地址和端口信息 }/* Structure describing an Internet socket address.翻譯:描述Internet套接字地址的結(jié)構(gòu) */ struct sockaddr_in {sa_family_t sin_family; /* address family: AF_INET */in_port_t sin_port; /* port in network byte order */struct in_addr sin_addr; /* internet address */char sin_zero[8];//占位不使用,用來與struct sockaddr對齊}; /* Internet address */ struct in_addr {/*uint32_t*/ in_addr_t s_addr;/* address in network byte order地址的網(wǎng)絡(luò)字節(jié)序 */};/*sin_addr is the IP host address. The s_addr member of struct in_addr contains the host interface address in network byte order. 翻譯:sin_addr為主機IP地址。struct in_addr的s_addr成員以網(wǎng)絡(luò)字節(jié)順序包含主機接口地址*/sockaddr結(jié)構(gòu)體中sa_data成員融合了端口與地址信息,而sockaddr_in結(jié)構(gòu)體用兩個成員sin_port和sin_addr分別表示端口號和地址信息
示例:
int sockfd; struct sockaddr_in serverAddr; sockfd = socket(AF_INET, SOCK_STREAM, 0);/* 填充struct sockaddr_in */ bzero(&serverAddr, sizeof(serverAddr));//初始化為0狀態(tài) 主要是對成員sin_zero[8]清0 serverAddr.sin_family = AF_INET; //設(shè)置地址家族 serverAddr.sin_port = htons(SERV_PORT);//端口號1024-65535 serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); /* 強制轉(zhuǎn)換成struct sockaddr */ bind(sockfd, (struct sockaddr *) &serverAddr, sizeof(serverAddr));3.常用填充地址信息的方法
//填充IP地址 serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);//0.0.0.0 等號后面可以是htonl(0)或者0 serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); inet_aton("127.0.0.1",&serverAddr.sin_addr); inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);//填充端口 serverAddr.sin_port = htons(1234);//端口號1024-65535 serverAddr.sin_port = htons(0);//隨機端口 等號后面可以 0相關(guān)函數(shù):
1. inet_addr
2.inet_ntoa 、inet_aton
char *inet_ntoa (struct in_addr in) //net to ascii /* 功能:網(wǎng)絡(luò)字節(jié)序地址轉(zhuǎn)點分字符串格式地址 參數(shù):傳入通用的網(wǎng)絡(luò)字節(jié)序地址struct in_addr sin_addr 返回值:成功:返回指針指向IPv4點分字符串格式地址 例如"127.0.0.1"失敗:0 */ int inet_aton(const char *cp, struct in_addr *inp); //ascii to net /* 功能:點分字符串格式地址轉(zhuǎn)網(wǎng)絡(luò)格式地址 參數(shù):cp:IPv4點分字符串格式地址inp:網(wǎng)絡(luò)字節(jié)序地址struct in_addr sin_addr 返回值:成功:非0失敗:0 */4.htons、htonl
uint16_t htons(uint16_t hostshort);//h host n net s short uint32_t htonl(uint32_t hostlong);//h host n net l long /* 功能:將主機字節(jié)序的short/long類型數(shù)據(jù)轉(zhuǎn)為網(wǎng)絡(luò)字節(jié)序類型數(shù)據(jù) 參數(shù):short類型數(shù)據(jù)/long類型 返回值:成功:網(wǎng)絡(luò)字節(jié)序類型數(shù)據(jù)失敗:-1 */5.inet_pton、inet_ntop
這兩個函數(shù)是隨IPv6出現(xiàn)的函數(shù),對于IPv4地址和IPv6地址都適用,函數(shù)中p和n分別代表表達(dá)(presentation)和數(shù)值(numeric)。地址的表達(dá)格式通常是ASCII字符串,數(shù)值格式則是存放到套接字地址結(jié)構(gòu)的二進(jìn)制值。
4.主機字節(jié)序與網(wǎng)絡(luò)字節(jié)序
NBO : 網(wǎng)絡(luò)字節(jié)序
HBO : 主機字節(jié)序
LE little-endian:小端
BE big-endian:大端
網(wǎng)絡(luò)數(shù)據(jù)流的地址規(guī)定:先發(fā)出的數(shù)據(jù)是低地址,后發(fā)出的數(shù)據(jù)是高地址。
發(fā)送主機通常將發(fā)送緩沖區(qū)中的數(shù)據(jù)按內(nèi)存地址從低到高的順序發(fā)出,為了不使數(shù)據(jù)流亂序,接收主機也會把從網(wǎng)絡(luò)上接收的數(shù)據(jù)按內(nèi)存地址從低到高的順序保存在接收緩沖區(qū)中。
TCP/IP協(xié)議規(guī)定:網(wǎng)絡(luò)數(shù)據(jù)流應(yīng)采用大端字節(jié)序,即低地址高字節(jié)。
tcp/ip規(guī)定它們的網(wǎng)絡(luò)字節(jié)序都是大端字節(jié)序。主機字節(jié)序可能是大端也可能是小端,與主機的cpu有關(guān),與操作系統(tǒng)無關(guān)考慮到與協(xié)議的一致以及與同類其它平臺產(chǎn)品的互通,在程序中發(fā)數(shù)據(jù)包時,將主機字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序,收數(shù)據(jù)包處將網(wǎng)絡(luò)字 節(jié)序轉(zhuǎn)換為主機字節(jié)序。網(wǎng)絡(luò)程序開發(fā)時 或是跨平臺開發(fā)時 應(yīng)該注意保證只用一種字節(jié)序 不然兩方的解釋不一樣就會產(chǎn)生bug。數(shù)據(jù)在傳輸?shù)倪^程中,一定有一個標(biāo)準(zhǔn)化的過程,也就是說:
從主機a到主機b進(jìn)行通信:a的主機字節(jié)序——網(wǎng)絡(luò)字節(jié)序——b的主機字節(jié)序
大端字節(jié)序存儲時值的高位存儲在較小的地址,值的低位存儲在較大的地址。
小端字節(jié)序存儲時值的高位存儲在較大的地址,值的低位存儲在較小的地址。
以0x12345678為例:
地址:0x1000 ?0x1001? 0x1002 ?0x1003
小端: 78 ???56 ???34 ???12
大端: 12 ???34 ???56 ???78
四 UDP通信的實現(xiàn)
在創(chuàng)建并綁定套接字之后,我們就可以嘗試TCP、UDP通信了。
TCP/IP協(xié)議是一個協(xié)議簇。里面包括很多協(xié)議,UDP只是其中的一個。
- UDP(User Datagram Protocol用戶數(shù)據(jù)報協(xié)議)是一個非連接的協(xié)議,傳輸數(shù)據(jù)之前源端和終端不建立連接, 當(dāng)它想傳送時就簡單地去抓取來自應(yīng)用程序的數(shù)據(jù),并盡可能快地把它扔到網(wǎng)絡(luò)上。 在發(fā)送端,UDP傳送數(shù)據(jù)的速度僅僅是受應(yīng)用程序生成數(shù)據(jù)的速度、 計算機的能力和傳輸帶寬的限制; 在接收端,UDP把每個消息段放在隊列中,應(yīng)用程序每次從隊列中讀一個消息段。
- UDP 是不具有可靠性的數(shù)據(jù)報協(xié)議。細(xì)微的處理它會交給上層的應(yīng)用去完成。在 UDP 的情況下,雖然可以確保發(fā)送消息的大小,卻不能保證消息一定會到達(dá)。因此,應(yīng)用有時會根據(jù)自己的需要進(jìn)行重發(fā)處理。
1.recvfrom函數(shù)
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen); /* 功能:接收數(shù)據(jù) 參數(shù):int sockfd:socket函數(shù)的返回值,套接字描述符void *buf:存放收到的數(shù)據(jù)size_t len:參數(shù)2的大小int flags:如果沒有數(shù)據(jù)到來 阻塞等待還是不等待 0表示阻塞 MSG_DONTWAIT 不等待struct sockaddr *src_addr:用于獲取發(fā)送方的地址信息socklen_t *addrlen:發(fā)送方地址信息長度 注意:傳的實參必須初始化 返回值:成功:返回實際收到的字節(jié)數(shù)失敗:-1 */2.sendto函數(shù)
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen); /* 功能:發(fā)送數(shù)據(jù)給對端 參數(shù):int sockfd:socket函數(shù)的返回值,套接字描述符const void *buf:要發(fā)送的數(shù)據(jù)存放的地址size_t len:參數(shù)2的大小int flags:套接字緩存滿 阻塞還是不阻塞 0表示阻塞 MSG_DONTWAIT 不阻塞const struct sockaddr *dest_addr:目標(biāo)端的地址信息socklen_t *addrlen:目標(biāo)端的地址信息 返回值:成功:返回實際發(fā)送的字節(jié)數(shù)失敗:-1 */3.示例
實現(xiàn)服務(wù)器端與客戶端聊天
運行效果:
五 TCP通信的實現(xiàn)
- TCP(Transmission Control Protocol,傳輸控制協(xié)議)是面向連接的協(xié)議,也就是說,在收發(fā)數(shù)據(jù)前,必須和對方建立可靠的連接。
- TCP 是面向連接的、可靠的流協(xié)議。流就是指不間斷的數(shù)據(jù)結(jié)構(gòu),當(dāng)應(yīng)用程序采用 TCP 發(fā)送消息時,雖然可以保證發(fā)送的順序,但還是猶如沒有任何間隔的數(shù)據(jù)流發(fā)送給接收端。TCP 為提供可靠性傳輸,實行“順序控制”或“重發(fā)控制”機制。此外還具備“流控制(流量控制)”、“擁塞控制”、提高網(wǎng)絡(luò)利用率等眾多功能。
1.listen函數(shù)(server端)
- 對于服務(wù)器端程序,使用bind 函數(shù)綁定套接字后,還需要使用listen 函數(shù)讓套接字進(jìn)入被動監(jiān)聽狀態(tài),再調(diào)用accept 函數(shù),就可以隨時響應(yīng)客戶端的請求了。
- 所謂被動監(jiān)聽,是指當(dāng)沒有客戶端請求時,套接字處于“睡眠”狀態(tài),只有當(dāng)接收到客戶端請求時,套接字才會被“喚醒”來響應(yīng)請求。
- 當(dāng)套接字正在處理客戶端請求時,如果有新的請求進(jìn)來,套接字是沒法處理的,只能把它放進(jìn)緩沖區(qū),待當(dāng)前請求處理完畢后,再從緩沖區(qū)中讀取出來處理。如果不斷有新的請求進(jìn)來,它們就按照先后順序在緩沖區(qū)中排隊,直到緩沖區(qū)滿。這個緩沖區(qū),就稱為 請求隊列(Request Queue)
- 當(dāng)請求隊列滿時,就不再接收新的請求,對于 Linux,客戶端會收到 ECONNREFUSED 錯誤
- listen只是讓套接字處于監(jiān)聽狀態(tài),并沒有接收請求。接收請求需要使用 accept函數(shù)
2.accept函數(shù)(server端)
- 當(dāng)套接字處于監(jiān)聽狀態(tài)時,可以通過 accept函數(shù)來接收客戶端請求。
- listen只是讓套接字進(jìn)入監(jiān)聽狀態(tài),并沒有真正接收客戶端請求,listen后面的代碼會繼續(xù)執(zhí)行,直到遇到 accept
- accept 會阻塞程序執(zhí)行,直到有新的請求到來。
3.connect函數(shù)(client端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); /* 功能:客戶端發(fā)起連接服務(wù)器請求 參數(shù):int sockfd:client套接字const struct sockaddr *addr:對端(服務(wù)器)的地址信息socklen_t *addrlen:參數(shù)2的大小 返回值:成功:0失敗:-1 */4.write與read函數(shù)
建立好了 TCP 連接之后,我們就可以把得到的 sockfd 當(dāng)作文件描述符來使用。
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);需要注意 read 函數(shù)的返回值:
- retval > 0 :實際讀到的字節(jié)數(shù)
- retval = 0 :
?????普通文件 — 到達(dá)文件末尾
?????管道文件 — 管道寫端關(guān)閉
?????套接字文件 — 對端關(guān)閉,網(wǎng)絡(luò)斷開 - retval < 0 :出錯
5.send與recv函數(shù)
recv 和 send 函數(shù)提供了和 read 和 write 差不多的功能。前3個參數(shù)同read、write,第4
個參數(shù)用來控制讀寫操作。
6.示例
實現(xiàn)客戶端與服務(wù)器端的對話,服務(wù)器端運行時命令行傳參端口號,客戶端運行時命令行傳參服務(wù)器的IP和端口號
關(guān)于程序中用到的IO多路復(fù)用select函數(shù),參考select函數(shù)詳解
運行效果:
六 套接字的緩沖區(qū)以及阻塞模式
參考socket套接字及緩沖區(qū)詳解
1.緩沖區(qū)
- 每個 socket 被創(chuàng)建后,都會分配兩個緩沖區(qū),輸入緩沖區(qū) 和 輸出緩沖區(qū)
- write()/send()/send to() 函數(shù)并不立即向網(wǎng)絡(luò)中傳輸數(shù)據(jù),而是先將數(shù)據(jù)寫入緩沖區(qū)中,再由TCP協(xié)議將數(shù)據(jù)從緩沖區(qū)發(fā)送到目標(biāo)機器。一旦將數(shù)據(jù)寫入到緩沖區(qū),函數(shù)就可以成功返回,不管它們有沒有到達(dá)目標(biāo)機器,也不管它們何時被發(fā)送到網(wǎng)絡(luò),這些都是TCP協(xié)議負(fù)責(zé)的事情。
- read()/recv()/recefrom() 函數(shù)也是如此,也從輸入緩沖區(qū)中讀取數(shù)據(jù),而不是直接從網(wǎng)絡(luò)中讀取。
- 每個套接字的I/O緩沖區(qū)單獨存在。
- 即使一端關(guān)閉套接字,也會繼續(xù)傳送這端套接字輸出緩沖區(qū)中遺留的數(shù)據(jù)。
- 如果一端關(guān)閉套接字,這端將丟失輸入緩沖區(qū)中的數(shù)據(jù)。
- 默認(rèn)情況下,套接字為阻塞模式
2.使用write/send發(fā)送數(shù)據(jù)
阻塞模式下:
- 首先會檢查輸出緩沖區(qū),如果緩沖區(qū)的可用空間長度小于要發(fā)送的數(shù)據(jù),那么 write()/send() 會被阻塞(暫停執(zhí)行),直到緩沖區(qū)中的數(shù)據(jù)被發(fā)送到目標(biāo)機器,騰出足夠的空間,才喚醒 write()/send() 函數(shù)繼續(xù)寫入數(shù)據(jù);
- 如果要寫入的數(shù)據(jù)大于緩沖區(qū)的最大長度,那么將分批寫入。直到所有數(shù)據(jù)被寫入緩沖區(qū) write()/send() 才能返回。
- 如果TCP協(xié)議正在向網(wǎng)絡(luò)發(fā)送數(shù)據(jù),那么輸出緩沖區(qū)會被鎖定,不允許寫入,write()/send() 也會被阻塞,直到數(shù)據(jù)發(fā)送完畢緩沖區(qū)解鎖,write()/send() 才會被喚醒。
- send()函數(shù)默認(rèn)情況下會使用Nagle算法。Nagle算法通過將未確認(rèn)的數(shù)據(jù)存入緩沖區(qū)直到積攢到一定數(shù)量一起發(fā)送的方法,來降低主機發(fā)送零碎小數(shù)據(jù)包的數(shù)目。所以假設(shè)send()函數(shù)發(fā)送數(shù)據(jù)過快的話,該算法會將一些數(shù)據(jù)打包后統(tǒng)一發(fā)出去。通過setsockopt()的TCP_NODELAY選項來禁用Nagle算法。
非阻塞模式下:
- write/send不做等待立即返回;
- write()/send()函數(shù)的過程僅僅是將數(shù)據(jù)拷貝到協(xié)議棧的緩沖區(qū)而已,如果緩沖區(qū)可用空間不夠,則盡可能拷貝,返回成功拷貝的大小;
- 如果緩存區(qū)可用空間為0,則返回-1,同時設(shè)置errno為EWOULDBLOCK。
3.使用read/recv讀取數(shù)據(jù)
read/recv函數(shù)返回其實際copy的字節(jié)數(shù),如果recv在copy時出錯,那么它返回SOCKET_ERROR;如果recv函數(shù)在等待協(xié)議接收數(shù)據(jù)時網(wǎng)絡(luò)中斷了,那么它返回0。
阻塞模式下:
- 首先會檢查輸入緩沖區(qū),如果緩沖區(qū)中有數(shù)據(jù),那么就讀取,否則函數(shù)會被阻塞,直到網(wǎng)絡(luò)上有數(shù)據(jù)到來;(如果數(shù)據(jù)正在從輸入緩沖區(qū)拷貝到用戶空間,read/recv也會被阻塞)
- 如果要讀取的數(shù)據(jù)長度小于緩沖區(qū)中的數(shù)據(jù)長度,那么就不能一次性將緩沖區(qū)中的所有數(shù)據(jù)讀出,剩余數(shù)據(jù)將不斷積壓,直到輸入緩沖區(qū)滿,協(xié)議棧不能再接收數(shù)據(jù)。
非阻塞模式下:
- write/send不做等待立即返回;
- 成功返回實際讀到的字節(jié)數(shù);
- 如果輸入緩沖區(qū)中沒有數(shù)據(jù),返回錯誤EWOULDBLOCK。
七 總結(jié)套接字收發(fā)數(shù)據(jù)的過程
TCP發(fā)送數(shù)據(jù)的過程:首先,TCP是有鏈接的可靠傳輸協(xié)議,所謂可靠也就是說保證客戶端發(fā)送的數(shù)據(jù)服務(wù)端都能夠收到,并且是按序收到。
數(shù)據(jù)首先由應(yīng)用程序緩沖區(qū)復(fù)制到發(fā)送端的輸出緩沖區(qū)(位于內(nèi)核),注意這個過程是用類似write功能的函數(shù)完成的。有的人通常看到write成功就以為數(shù)據(jù)發(fā)送到了對端主機,其實這是錯誤的,write成功僅僅表示數(shù)據(jù)成功的由應(yīng)用進(jìn)程緩沖區(qū)復(fù)制到了輸出緩沖區(qū)。
然后內(nèi)核協(xié)議棧將輸出緩沖區(qū)中的數(shù)據(jù)發(fā)送到對端主機,注意這個過程不受應(yīng)用程序控制,而是發(fā)送端內(nèi)核協(xié)議棧完成,其中包括使用滑動窗口、擁塞控制等功能。
數(shù)據(jù)到達(dá)接收端主機的輸入緩沖區(qū),注意這個接收過程也不受應(yīng)用程序控制,而是由接收端內(nèi)核協(xié)議棧完成,其中包括發(fā)送ack確認(rèn)等。
數(shù)據(jù)由套接字接收緩沖區(qū)復(fù)制到接收端應(yīng)用程序緩沖區(qū),注意這個過程是由類似read等函數(shù)來完成。
思考:如果TCP服務(wù)端一直sleep,客戶端一直發(fā)送數(shù)據(jù),會出現(xiàn)什么情況?
如果服務(wù)端一直sleep不接收數(shù)據(jù),而客戶端一直write,也就是只能執(zhí)行上述過程中的前三步,這樣最終結(jié)果肯定是接收端的輸入緩沖區(qū)和發(fā)送端的輸出緩沖區(qū)都被填滿,這樣write就無法繼續(xù)將數(shù)據(jù)從應(yīng)用程序復(fù)制到發(fā)送端的輸出緩沖區(qū)了,從而使進(jìn)程進(jìn)入睡眠。
服務(wù)端一直sleep,隨著客戶端write,接收端的輸入緩沖區(qū)和發(fā)送端的輸出緩沖區(qū)會被填滿。當(dāng)發(fā)送端的輸出緩沖區(qū)的可用空間為0時,write立即返回-1,并將errno置為EWOULDBLOCK。
“華清遠(yuǎn)見” http://www.hqyj.com/學(xué)習(xí)更多編程知識
總結(jié)
以上是生活随笔為你收集整理的Socket套接字通信 TCP UDP详解(网络通信)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Unity游戏开发中ECS思想介绍
- 下一篇: 分享下通过开淘宝网店挣钱的经验,更激励下