網路程式設計之socket套接字詳解
技術標籤:# 網路IO管理
目錄:
- socket
- bind
- listen
- connect
- accept
1. socket:
(1)函式原型:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
(2)引數:
socket函式用於建立一個套接字,這是在應用層呼叫的,所以應用程式需要在呼叫socket建立套接字時根據應用層的業務型別,指定它所希望採用的網路層、傳輸層所使用的的協議。
核心中套接字是一層一層進行抽象展示的,把共性的東西抽取出來,這樣對外提供的介面可以儘量統一。
核心中把套接字的定義會抽象出來展示,如:struct sock -> struct inet_sock -> struct tcp_sock 從抽象到具體.
socket函式的這三個引數其實就是把抽象的socket具體化的條件,domain
引數決定圖中所示的第二層通訊域
,type
決定了第三層的通訊模式
,protocol
決定了第四層真正的通訊協議
。
domain
引數的幾個常用值:
AF_UNIX / AF_LOCAL : local communication, 本地通訊
AF_INET : IPv4 網路通訊
AF_INET6 : IPv6 網路通訊
type
SOCK_STREAM : 流套接字:提供序列化的、可靠的、雙工的、基於連線的位元組流式服務
SOCK_DGRAM : 資料報套接字:提供資料報式的服務(無連線、不可靠、有最大長度限制)
SOCK_RAW : 原始套接字:提供原始套接字服務
此外,type
還可以設定以下幾種型別:
SOCK_NONBLOCK : 設定為非阻塞式IO,functl(O_NONBLOCK) 是一樣的效果
SOCK_CLOEXEC : 與open(O_CLOEXEC)一樣,open開啟時關閉檔案描述符,防止父程序洩漏開啟的檔案給子程序
protocol
引數
確定socket到底支援哪個協議,例如如果domain=AF_INET, type = SOCK_STREAM,那麼核心就會自動設定Protocol = IPPROTO_TCP;
socket(AF_INET, SOCK_STREAM, 0); //等價於 :
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
socket(AF_INET, SOCK_DGRAM, 0); //等價於:
socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
(3)返回值:
成功時返回sockfd套接字描述符,失敗返回-1,並設定errno。
常見的errno:
EACCES: 沒有許可權建立sockfd套接字
EINVAL:指定的協議錯誤
2. bind:
(1)bind 函式原型:
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
使用舉例:
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//
servaddr.sin_port = htons(65535);
bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
(2)bind 引數:
socket函式建立了一個sockfd套接字描述符,想要進行網路通訊還要將套接字與埠號、IP地址繫結起來(埠號+IP地址用來描述TCP連線的一端),Unix中用套接字地址結構
這樣的一個結構體在核心與應用程式之間傳遞IP地址和埠號:
服務端(客戶端)應用程序將自己想要繫結(連線)的IP地址和埠後寫道套接字地址結構這樣的結構體中,然後傳遞給核心;服務端核心在接收到新的客戶端連線後將客戶端的IP地址、埠號通過此結構體傳遞給應用程序。
另外,由於歷史原因,因為套接字地址結構是在ANSI C之前定義的,當時函式還沒有void *這樣的引數型別,所以類似於bind的函式如果想要接受 IPv4、IPv6等多種型別的結構體,就要有一個通配的結構體型別,因此而產生了通用套接字地址結構
。
IPv4 套接字地址結構:
#include <netinet/in.h>
struct sockadrr_in {
uint8_t sin_len; //length of structure(16)
sa_family_t sin_family; //AF_INET
in_port_t sin_prot; //`16` bit TCP or UDP port number
struct in_addr sin_addr; //`32` bit IPv4 Address
char sin_zero[8]; //unused
};
struct in_addr {
in_addr_t s_addr;
};
通用套接字地址結構:
#include <sys/socket.h>
struct sockaddr {
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
位元組序轉換函式:
主機位元組序可能與網路位元組序不相同,因此在傳入地址、埠號等時需要先進行位元組序轉換:
#include <netinet/in.h>
unit16_t htons(uint16_t val); //將16 bit位的整數從主機序轉換為網路序,返回值為網路序
uint32_t htonl(uint32_t val);
uint16_t ntohs(uint16_t val);
uint32_t ntohl(uint32_t val);
地址轉換函式:
由於網路IP地址的表達方式有多種(ASCII字串、網路位元組序的二進位制),因此需要轉換。
#include <arpa/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr);
//成功則返回1,格式無效返回0,失敗返回-1
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
//成功則返回指向結果的指標,失敗返回NULL
(3)bind 返回值:
成功返回0,失敗返回-1,並設定errno
常見的errno:
EADDRINUSE: Address already in use,所以要繫結的地址已使用
3. listen:
(1)listen 函式原型:
#include <sys/socket.h>
int listen(int sockfd, int backlog);
(2)listen 引數:
backlog:
核心為一個監聽套接字維護兩個佇列:(注意一定是監聽套接字,服務端等待連線的套接字)
- SYN佇列:
在三次握手中,收到了客戶端的第一個SYN,此時連線放入SYN佇列中(未完成連線佇列); - ACCEPT佇列:
在三次握手中,收到了客戶端的第三個ACK,此時連線從SYN佇列中取出,放入到ACCEPT佇列中。服務端呼叫accept,就是從ACCEPT佇列中取出一個已完成連線。
backlog到底指的是哪個佇列的大小?
在《TCP/IP詳解(卷一)》中這樣描述:
SYN佇列的大小由核心引數net.ipv4.tcp_max_syn_backlog
決定(預設1000);
ACCEPT佇列取 min(backlog, net.core.someconn=128)
的最小值來決定。
也就是說listen函式的backlog引數是用來配置ACCEPT全連線佇列的,但是ACCEPT佇列同時要受另一個核心引數的限制。
(3)listen 返回值:
成功返回0,失敗返回-1,並設定errno
常見的errno:
EADDRINUSE: Another socket is already listening on the same port. 另一個套接字已經在監聽這個埠
ENOTSOCK: The file descriptor sockfd does not refer to a socket. 傳入的fd檔案描述符不是套接字
4. connect:
(1)connect 函式原型:
#include <sys/socket.h>
#include <sys/types.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
(2)connect 引數:
略
(3)connect 返回值:
成功返回0,失敗返回-1,並設定errno
常見的errno:
EINPROGRESS: sockfd為非阻塞,且資料未就緒時connect返回(待補充)
ECONNREFUSED: No-one listening on the remote address. 所連線的IP地址上沒有套接字監聽
ETIMEOUT: Timeout while attempting connection. The server may be too busy to accept new connections. 連線超時(TCP三次握手超時,可能是伺服器繁忙沒有響應)
5. accept:
(1)accept 函式原型:
#include <sys/socket.h>
int accept(int listenfd, struct sockaddr *cliaddr, socklen_t *addrlen);
(2)accept 引數:
略
(3)accept 返回值:
成功返回非負描述符(connfd),失敗返回-1,並設定errno
常見的errno:
(待補充)
參考內容: