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