1. 程式人生 > 其它 >網路程式設計之socket套接字詳解

網路程式設計之socket套接字詳解

技術標籤:# 網路IO管理

目錄:

  1. socket
  2. bind
  3. listen
  4. connect
  5. 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;

如果domain = AF_INET, type = SOCK_DGRAM, 則 protocol = IPPROTO_UDP

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:

核心為一個監聽套接字維護兩個佇列:(注意一定是監聽套接字,服務端等待連線的套接字)

  1. SYN佇列:
    在三次握手中,收到了客戶端的第一個SYN,此時連線放入SYN佇列中(未完成連線佇列);
  2. 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:
(待補充)


參考內容:

socket函式的domain、type、protocol解析