一统抬、基礎(chǔ)
1.1 套接字定義
套接字是網(wǎng)絡(luò)數(shù)據(jù)傳輸?shù)能浖O(shè)備。我們可以通過套接字完成數(shù)據(jù)傳輸灭贷。
1.2 相關(guān)函數(shù)
網(wǎng)絡(luò)編程中接受連接請求的套接字創(chuàng)建過程如下:
- 調(diào)用socket函數(shù)創(chuàng)建套接字
- 調(diào)用bind函數(shù)分配IP和端口號
- 調(diào)用listen函數(shù)轉(zhuǎn)換為可接受請求狀態(tài)
- 調(diào)用accept受理連接請求
- Linux平臺相關(guān)函數(shù)
#include <sys/socket.h> int socket(int domain, int type, int protocol); 成功返回文件描述符温学,失敗返回-1 int bind(int socketfd, struct sockadr* myaddr, socklen_t addrlen); int listen(int socketfd, int backlog); int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen); int connect(int sockdf, struct sockaddr* serv_addr, socklen_t addrlen); 成功返回0,失敗返回-1
- Windows平臺相關(guān)函數(shù)
#include <winsock2.h> //需要鏈接ws2_32.lib //初始化 int WSAStartup(WORD wVersionRequested,LPWSADATA lpwSAData); 成功返回0甚疟,失敗返回錯誤代碼 wVersionRequested 使用的Winsock版本信息 lpwSAData WSADATA結(jié)構(gòu)體變量的地址 MAKEWORD(1仗岖,2); //宏函數(shù),主版本為1览妖,副版本2轧拄,返回0x0201 //注銷 int WSACleanup(); 成功返回0,失敗返回SOCKET_ERROR //套接字 SOCKET socket(int af, int type, int protocol); 成功時返回套接字句柄讽膏,失敗返回INVALID_SOCKET int bind(SOCKET s, const struct sockaddr* name, int namelen); 成功返回0檩电,失敗返回SOCKET_ERROR int listen(SOCKET s, int backlog); 成功返回0,失敗返回SOCKET_ERROR SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen); 成功時返回套接字句柄府树,失敗返回INVALID_SOCKET int connect(SOCKET s, const struct sockaddr* name, int namelen); 成功返回0是嗜,失敗返回SOCKET_ERROR int closesocket(SOCKET s); 成功返回0,失敗返回SOCKET_ERROR
1.3 類型和設(shè)置
1.3.1 協(xié)議(Protocol)
- 協(xié)議族分類
- PF_INET IPV4協(xié)議族
- PF_INET6 IPV6協(xié)議族
- PF_LOCAL 本地通信的Unix協(xié)議族
- PF_PACKET 底層套接字的協(xié)議族
- PF_IPX IPX Novell協(xié)議族
通過套接字的第一個參數(shù)傳遞使用的協(xié)議分類信息挺尾,domain決定使用的protocol鹅搪。
- 套接字類型
類型指的是套接字的數(shù)據(jù)傳輸方式,決定了協(xié)議族不能決定數(shù)據(jù)傳輸方式遭铺,如PF_INET可能存在多種數(shù)據(jù)傳輸方式丽柿。-
SOCK_STREAM(面向連接的套接字)
面向連接的套接字具有以下特點:- 傳輸過程中數(shù)據(jù)不會消失
- 按序傳輸數(shù)據(jù)
- 傳輸?shù)臄?shù)據(jù)不存在數(shù)據(jù)邊界
收發(fā)數(shù)據(jù)的內(nèi)部有緩沖(字節(jié)數(shù)組)。套接字傳輸?shù)臄?shù)據(jù)保存到該數(shù)組魂挂。在數(shù)組容量內(nèi)甫题,可能在充滿緩沖后一次讀取,也可能分多次讀取涂召。如果緩沖被填滿坠非,傳輸套接字將停止傳輸。
SOCK_DGRAM(面向消息的套接字)
面向消息的套接字特點: 不可靠的果正、不按序傳遞炎码、以數(shù)據(jù)的高速傳遞為目的的套接字盟迟。
-
1.4 地址族和字節(jié)序
1.4.1 地址族
IP是網(wǎng)絡(luò)協(xié)議的簡寫鸠儿,是為了收發(fā)網(wǎng)絡(luò)數(shù)據(jù)而分配給計算機的值浓体。只要有IP就能像數(shù)據(jù)發(fā)送到目標計算機,但無法發(fā)送給最終的應(yīng)用程序茎活。
端口號是為了區(qū)分程序中創(chuàng)建的套接字而分配給套接字的序號歉闰。NIC通過IP向計算機內(nèi)部發(fā)生數(shù)據(jù)辖众,操作系統(tǒng)利用端口號將數(shù)據(jù)分配給套接字。
IP地址分為IPV4(4字節(jié))和IPV6(16字節(jié))兩類和敬。IPV6是為了解決201年前后IP地址耗盡而提出的標準凹炸。IPV4分為A、B昼弟、C啤它、D四類,由網(wǎng)絡(luò)ID和主機主機ID構(gòu)成私杜。 IP網(wǎng)絡(luò)數(shù)據(jù)傳輸時蚕键,先基于網(wǎng)絡(luò)地址(網(wǎng)絡(luò)ID)把數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)。網(wǎng)絡(luò)收到數(shù)據(jù)后衰粹,根據(jù)主機ID將數(shù)據(jù)發(fā)送到目標計算機锣光。
地址分類
- A類地址首字節(jié) 0-127,首位以0開始
- B類地址首字節(jié)128-191铝耻,首位以10開始
- C類地址首字節(jié)192-223誊爹,首位以110開始
端口號
- 端口號由16位構(gòu)成,可分配端口號為0-65535
- 知名端口號:0-1023瓢捉。一般分配給特定程序频丘。
1.4.2 地址信息表示
struct sockaddr_in
{
sa_family_t sin_family; //地址族
uint16_t sin_port; //16位端口號
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用
}
in_addr結(jié)構(gòu)體聲明為uint32_t,只需要當作32位整數(shù)泡态。
sin_zero是使結(jié)構(gòu)體與socd_addr保持一致而插入的成員搂漠。
之所以使用sockaddr_in,而不直接使用sockaddr某弦,原因在于sockaddr結(jié)構(gòu)體桐汤,如下:
struct sock_addr
{
sa_family_t sin_family;
char sa_data[14];
}
sa_data中包含地址和端口號,其余部分為0靶壮。直接填充這些信息會比較麻煩怔毛,于是有了sockaddr_in,最后轉(zhuǎn)換為sockaddr型結(jié)構(gòu)體。
1.4.3 字節(jié)序
大端序: 高位字節(jié)放在地位地址
小端序: 高位字節(jié)放在高位地址
在通過網(wǎng)絡(luò)傳輸數(shù)據(jù)時約定統(tǒng)一方式腾降,即網(wǎng)絡(luò)字節(jié)序拣度。網(wǎng)絡(luò)字節(jié)序是大端序。
字節(jié)序轉(zhuǎn)換
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
//h--->host字節(jié)序
//n-->network字節(jié)序
//s-->2字節(jié)short,常用于端口號轉(zhuǎn)換
//l-->4字節(jié)抗果,常用于IP轉(zhuǎn)換
地址字符串的字節(jié)序轉(zhuǎn)換
#include <arpa/inet.h>
in_addr_t inet_addr(const char* string); //點分十進制字符串---> 大端序整數(shù)值
int inet_aton(const char* string, struct in_addr* addr); //string轉(zhuǎn)換結(jié)果保存到addr地址值中
char* inet_ntoa(struct in_addr adr); //轉(zhuǎn)換為字符串形式筋帖。長期保存時,需要對char*內(nèi)容進行復(fù)制
網(wǎng)絡(luò)地址初始化
struct sockaddr_in addr;
char* server_ip = "211.217.168.11"; //ip字符串
char* serv_port = "9190"; //端口號
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(server_ip);
addr.sin_port = htons(server_port);
INADDR_ANY
利用INADDR_ANY分配Ip地址窖张,將自動獲取計算機的地址幕随,若計算機有多個IP地址蚁滋,則可以從不同IP宿接、相同端口號中接收數(shù)據(jù)。服務(wù)器一般采用這種方式辕录。
1.5 域名和網(wǎng)絡(luò)地址
將容易記睦霎、易表達的域名分配并取代IP地址。域名是賦予服務(wù)器端的虛擬地址走诞,而非實際地址副女。通過DNS請求轉(zhuǎn)換地址。若DNS服務(wù)器無法解析蚣旱,會詢問其他DNS服務(wù)器碑幅,并提供給用戶。
//
#include <netdb.h>
struct hostent* gethostbyname(const char* hostname);
structhostent* gethostbyaddr(const char* addr, socklen_t len, int family);
1.6套接字 多種可選項
1.6.1 可選項
套接字可選項是分層的塞绿。IPPROTO_IP是IP協(xié)議相關(guān)選項沟涨,IPPROTO_TCP是tcp協(xié)議相關(guān)可選項,SOL_SOCKET是套接字相關(guān)通用可選項异吻。
設(shè)置可選項
#include <sys/socket.h>
int getsockopt(int sock, int level, int optname, void* optval, socklen_t* optlen);
sock 套接字文件描述符
level 協(xié)議層
optname 可選項名字
optval 保持查看結(jié)果的緩沖地址
optlen 緩沖大小
int setsockopt(int sock, int level, int optname, const void* optval, socklen_t optlen);
SO_TYPE 查看套接字類型
SO_SNDBUF/SO_RCVBUF 輸入輸出緩沖區(qū)大小
SO_REUSEADDR :默認為0(假),即無法分配Time-wait狀態(tài)下的套接字端口號, 為1(真)可將Time-wait狀態(tài)下的套接字端口號重新分配給新的套接字
套接字四次揮手后并非立即清除,二十經(jīng)過一段時間的Time-wait狀態(tài)裹赴。先斷開連接(發(fā)送FIN消息)的主機才經(jīng)過Time-wait狀態(tài)。因此服務(wù)器先斷開連接诀浪,無法立即重新運行棋返。
TCP_NODEALY:設(shè)置為1時,禁用Nagle算法雷猪。
tcp套接字默認使用Nagle算法(只有收到前一數(shù)據(jù)的ACK時,才發(fā)送下一數(shù)據(jù))交換數(shù)據(jù),因此最大限度的進行緩沖,直到收到ACK睛竣。為了提高網(wǎng)絡(luò)傳輸效率,必須使用Nagle算法求摇。但在網(wǎng)絡(luò)流量未受到太大影響時射沟,不使用Nagle算法會更快。如大文件數(shù)據(jù)月帝。
1.7 進程
1.8 IO復(fù)用與IO函數(shù)
無論連接多少客戶端躏惋,提供服務(wù)的進程只有一個。select是最具代表性的服務(wù)端復(fù)用方法嚷辅。
1.9 線程
1.10 信號處理
二簿姨、TCP
2.1 定義
IP層解決數(shù)據(jù)傳輸中的路徑選擇問題。TCP和UDP利用IP層提供的路徑信息完成實際的數(shù)據(jù)傳輸。
2.2 服務(wù)端
TCP服務(wù)端函數(shù)調(diào)用通常遵循如下順序:
- socket -- 創(chuàng)建套接字
- bind -- 分配套接字地址
- listen -- 等待連接請求狀態(tài)
- accept -- 允許連接
- read/wrote -- 數(shù)據(jù)交換
- close -- 斷開連接
listen后扁位,若有新的連接請求准潭,應(yīng)該按序處理。受理請求意味著進入可接受數(shù)據(jù)的狀態(tài)泼掠。
accept受理連接請求。其內(nèi)部產(chǎn)生用于數(shù)據(jù)IO的套接字择镇,并返回其文件描述符。
客戶端通過connect發(fā)起連接請求,當服務(wù)端接收連接請求或者發(fā)生異常中斷連接請求時返回苏携。服務(wù)端接收連接請求只是將請求記錄加入等待隊列衩侥,并不表示調(diào)用accept,不表示立即進行數(shù)據(jù)交換屡久。
2.3 迭代服務(wù)端
通過插入循環(huán)語句详幽,反復(fù)調(diào)用accept函數(shù)來持續(xù)受理客戶端連接請求的方式版姑。
2.4 迭代回聲服務(wù)/客戶端
TCP數(shù)據(jù)傳輸不存在邊界。服務(wù)端通過1次write函數(shù)傳輸數(shù)據(jù)健爬,但是如果數(shù)據(jù)太大夭拌,可能會分成多個數(shù)據(jù)包發(fā)送镶骗。另外,客戶端也可以未完全收到數(shù)據(jù)就read。解決方法是提前確定接收數(shù)據(jù)的大小。在無法預(yù)知數(shù)據(jù)大小的情況下,需要應(yīng)用層協(xié)議的定義。收發(fā)數(shù)據(jù)的過程中定義號規(guī)則以表示數(shù)據(jù)的邊界。
2.5 半關(guān)閉
IO緩沖特性:
- I/O緩沖在每個套接字中單獨存在
- 在創(chuàng)建套接字時自動生成
- 關(guān)閉套接字后巴元,也會繼續(xù)傳輸輸出緩沖中的遺留數(shù)據(jù)
- 關(guān)閉套接字修己,會丟失輸入緩沖中的數(shù)據(jù)
Linux的close和Windows的closescket意味著完全斷開連接尤辱,即無法傳輸和接受數(shù)據(jù)厢岂。這樣可能存在主機A發(fā)送數(shù)據(jù)后斷開連接光督,無法繼續(xù)接收B的數(shù)據(jù)结借。因此船老,Half-close產(chǎn)生堪置,即只關(guān)閉1個流坎匿。
半關(guān)閉函數(shù)
#include <sys/socket.h>
int shutdown(int sock, int howto);
//howto為斷開方式信息
斷開方式如下:
- SHUT_RD 斷開輸入流
- SHUT_WR 斷開輸出流
- SHUT_RWDR 同時斷開輸入輸出流
shutdown進行半關(guān)閉盾剩,同時發(fā)送EOF雷激。
2.6 并發(fā)服務(wù)器
2.7 IO分離
2.8 select服務(wù)端
2.8.1 select調(diào)用過程
- 設(shè)置文件描述符、指定監(jiān)視范圍驻粟、設(shè)置超時
- 調(diào)用select函數(shù)
- 查看調(diào)用結(jié)果
2.9 多線程服務(wù)端
三根悼、 UDP
3.1 定義
UDP提供不可靠數(shù)據(jù)傳輸。UDP不提供流控制機制蜀撑。根據(jù)端口號將傳到主機的數(shù)據(jù)交付給最終的UDP套接字挤巡。
TCP比UDP慢主要由于:
- 收發(fā)數(shù)據(jù)前后進行的連接設(shè)置和清楚操作
- 收發(fā)數(shù)據(jù)過程中為保證可靠性而添加的流控制
3.2 UDP服務(wù)端/客戶端
3.2.1 UDP數(shù)據(jù)IO函數(shù)
#include <sys/cosket.h>
ssize_t sendto(int sock, void* buf, size_t bnytes, int flags, struct sockaddr* to, socklen_t addrlen)
ssize_t recvfrom(int sock, void* buf, size_t nbytes, int flags, struct sockaddr* from, socklen_t* addrlen)
3.2.2 套接字地址分配
sendto前完成地址分配。如果sendto時未分配地址信息酷麦,首次調(diào)用時自動分配IP和端口號矿卑,分配結(jié)果保留到程序結(jié)束。IP為主機IP沃饶,端口號為任意未分配端口母廷。
此外,也可以用bind分配ip和端口號绍坝。
3.3 數(shù)據(jù)傳輸特性和connect
存在數(shù)據(jù)邊界徘意。通過snedto傳輸數(shù)據(jù)過程如下:
- 向套接字注冊目標IP和端口號
- 傳輸數(shù)據(jù)
- 刪除注冊信息
未注冊目標信息的套接字稱為未連接套接字。反之稱為connected套接字轩褐。
向同一目標主機進行長時間通信時,將UDP套接字變?yōu)閏onnected套接字會提高效率玖详。傳輸過程中把介,1和3占傳輸?shù)?/3時間。