Socket 網(wǎng)絡(luò)編程框架
Socket(套接字)是一個(gè)網(wǎng)絡(luò)編程概念,描述了一個(gè)通信端點(diǎn)(Endpoint),用于建立網(wǎng)絡(luò)連接(Connection)并傳輸數(shù)據(jù)。
Linux Kernel 提供了一套面向 Socket 的網(wǎng)絡(luò)編程框架,并通過提供一組標(biāo)準(zhǔn)的 System call APIs,使得開發(fā)者可以在 Userspace 中便捷的開發(fā)各種 Network Applications,例如:基于 HTTP 協(xié)議的 Web 服務(wù)器、基于 SMTP 協(xié)議的郵件服務(wù)器、基于 FTP 協(xié)議的文件服務(wù)器等等。
Linux Socket 網(wǎng)絡(luò)編程框架主要由 3 大模塊組成:
- BSD Socket APIs
- Socket Abstraction Layer
- VFS Layer
BSD Socket APIs 概覽
BSD Socket APIs(Berkeley Software Distribution Socket APIs),是面向 Userspace Application 的接口封裝層,提供了一套兼容絕大部分網(wǎng)絡(luò)通信協(xié)議族的標(biāo)準(zhǔn) Socket APIs。
- socket():創(chuàng)建一個(gè)新的 socket,返回一個(gè) int 類型的 socket fd(File Descriptor,套接字文件描述符),用于后續(xù)的網(wǎng)絡(luò)連接操作。
- bind():將 socket 與一個(gè)本地 IP:Port 綁定,通常用于服務(wù)端,以便在本地監(jiān)聽網(wǎng)絡(luò)連接。
- connect():建立與遠(yuǎn)程主機(jī)的連接,通常用于客戶端,以便連接到遠(yuǎn)程服務(wù)器。
- listen():開始監(jiān)聽來自遠(yuǎn)程主機(jī)的連接請求,通常用于服務(wù)器端,等待來自客戶端的連接請求。
- accept():接受一個(gè)連接請求,返回一個(gè)新的 socket fd,通常用于服務(wù)器端,用于接收客戶端的連接請求。
- send():向 socket 發(fā)送數(shù)據(jù)。
- recv():從 socket 接收數(shù)據(jù)。
- close():關(guān)閉 socket 連接。
Socket API 的使用通常可以分為以下幾個(gè)步驟:
- 創(chuàng)建套接字:使用 socket() 函數(shù)創(chuàng)建一個(gè)新的 socket fd。
- 配置套接字:使用一些其他的 Socket API 函數(shù),例如 bind()、connect() 和 listen() 來配置 socket,使其能夠接收和發(fā)送數(shù)據(jù)。
- 數(shù)據(jù)傳輸:使用 send() 和 recv() 函數(shù)進(jìn)行數(shù)據(jù)傳輸。
- 關(guān)閉套接字:使用 close() 函數(shù)關(guān)閉 socket 連接。
需要注意的是,Socket API 并不是線程安全的,如果有多個(gè)線程同時(shí)使用了同一個(gè) socket fd,則可能會(huì)導(dǎo)致數(shù)據(jù)傳輸錯(cuò)誤或其他問題。為了避免這種情況,Application 需要進(jìn)行適當(dāng)?shù)耐胶蛿?shù)據(jù)處理。
Socket Abstraction Layer
Socket Abstraction Layer(Socket 抽象層),是 Socket API 的底層支撐,主要負(fù)責(zé)以下工作:
- 實(shí)現(xiàn)了 Socket File System(套接字文件系統(tǒng)),用于管理 User Process 和 socket fd 之間的關(guān)系,包括 socket fd 的創(chuàng)建、打開、讀寫等操作。
- 實(shí)現(xiàn)了 Struct Socket、Struct Sock、Protocol Family(協(xié)議族)、Address Family(地址族)等數(shù)據(jù)結(jié)構(gòu)。
- 實(shí)現(xiàn)了 TCP/IP 協(xié)議棧,包括:TCP、UDP、ICMP 等協(xié)議。
- 實(shí)現(xiàn)了 L4 傳輸層功能,處理傳輸層協(xié)議的連接建立、數(shù)據(jù)傳輸、連接維護(hù)等操作。
Socket & Sock
- Struct Socket 是在 Socket Layer 中定義的數(shù)據(jù)結(jié)構(gòu),面向上層 Socket API,包含了一個(gè) Socket 所具有的各種屬性,例如:狀態(tài)、類型、標(biāo)記、關(guān)聯(lián)的 Sock 等。
- Struct Sock 是在 Sock Layer 中定義的數(shù)據(jù)結(jié)構(gòu),面向底層協(xié)議棧實(shí)現(xiàn),表示一個(gè) Socket 對(duì)應(yīng)的 PCB(Protocol Control Block,協(xié)議控制塊),即:與某種網(wǎng)絡(luò)協(xié)議相關(guān)的一些信息和狀態(tài),例如:TCP PCB 就包括了 TCP 連接狀態(tài)、發(fā)送緩沖區(qū)、接收緩沖區(qū)、擁塞窗口等。
Socket Layer 與 Network Driver(網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序)之間通過 Socket Buffer(skb_buff)進(jìn)行交互,當(dāng) Socket Layer 接收到 Application 的數(shù)據(jù)時(shí),會(huì)將數(shù)據(jù)存儲(chǔ)在 Socket Buffer 中,并將 Socket Buffer 傳遞給對(duì)應(yīng)的 Sock Layer 進(jìn)行處理。Struct Socket 和 Struct Sock 之間通過指針進(jìn)行關(guān)聯(lián)綁定,共同實(shí)現(xiàn) Socket API 的功能。
Socket Layer
// linux/include/linux/net.h
/**
- struct socket - general BSD socket
- @state: socket state (%SS_CONNECTED, etc)
- @type: socket type (%SOCK_STREAM, etc)
- @flags: socket flags (%SOCK_NOSPACE, etc)
- @ops: protocol specific socket operations
- @file: File back pointer for gc
- @sk: internal networking protocol agnostic socket representation
- @wq: wait queue for several uses
*/
struct socket {
socket_state state;
short type; // 套接字類型,如 SOCK_STREAM、SOCK_DGRAM 等;
unsigned long flags; // 套接字標(biāo)志,如 O_NONBLOCK、O_ASYNC 等;
struct file *file; // 套接字對(duì)應(yīng)的文件結(jié)構(gòu)體;
struct sock *sk; // 指向套接字對(duì)應(yīng)的 Sock 結(jié)構(gòu)體;
const struct proto_ops *ops; // 套接字對(duì)應(yīng)的操作函數(shù)集,如 inet_stream_ops、inet_dgram_ops 等;
struct socket_wq wq; // 套接字等待隊(duì)列;
};
typedef enum
{
SS_FREE=0; // 未分配
SS_UNCONNECTED; // 未連接到任何套接字
SS_CONNECTING; // 處于連接過程中
SS_CONNECTED; // 已經(jīng)連接到另一個(gè)套接字
SS_DISCONNECTING; // 處于斷開連接過程中
} socket_state;
Sock Layer
Struct Sock 包含了 Socket 的各種底層執(zhí)行狀態(tài)和操作信息,例如:接收和發(fā)送緩沖區(qū)、套接字隊(duì)列、套接字協(xié)議信息等。
// linux/include/net/sock.h
struct sock {
/* Socket family and type */
unsigned short family; // 協(xié)議族,如 AF_INET、AF_PACKET 等;
__u16 type; // 套接字類型,如 SOCK_STREAM、SOCK_DGRAM 等;
unsigned long flags; // 套接字標(biāo)志,如 O_NONBLOCK、O_ASYNC 等;
/* Protocol specific elements of the socket */
struct proto *ops; // 協(xié)議特定操作函數(shù)集;
struct net_device *sk_net; // 套接字所在的網(wǎng)絡(luò)設(shè)備;
/* Memory allocation cache */
kmem_cache_t *sk_slab; // 套接字內(nèi)存分配緩存;
/* Socket state */
atomic_t refcnt; // 套接字引用計(jì)數(shù);
struct mutex sk_lock; // 套接字鎖,用于保護(hù)套接字狀態(tài)的一致性;
/* Send and receive buffers */
struct sk_buff_head sk_receive_queue; // 接收隊(duì)列,保存了等待接收的數(shù)據(jù)包;
struct sk_buff_head sk_write_queue; // 發(fā)送隊(duì)列,保存了等待發(fā)送的數(shù)據(jù)包;
struct sk_buff *sk_send_head; // 發(fā)送緩沖區(qū)的頭指針;
struct sk_buff *sk_send_tail; // 發(fā)送緩沖區(qū)的尾指針;
/* Receive queue */
struct sk_buff *sk_receive_skb; // 當(dāng)前正在接收的數(shù)據(jù)包;
/* Transport specific fields */
__u32 sk_priority; // 套接字優(yōu)先級(jí);
struct dst_entry *sk_dst_cache; // 緩存的目標(biāo)設(shè)備;
struct dst_entry *sk_dst_pending_confirm;
struct flowi sk_fl; // Flowi 結(jié)構(gòu)體,保存了套接字相關(guān)的流信息;
struct sk_filter *sk_filter; // 過濾器;
struct sk_buff_head sk_async_wait_queue; // 異步等待隊(duì)列;
/* Socket buffer allocations */
unsigned long sk_wmem_alloc; // 發(fā)送緩沖區(qū)已分配的內(nèi)存;
unsigned long sk_omem_alloc;
/* User and kernel buffers */
struct socket_wq *sk_wq; // 套接字等待隊(duì)列;
struct page_frag sk_frag; // 內(nèi)存分配器的頁片段;
int sk_forward_alloc; // 前向分配的字節(jié)數(shù);
int sk_rxhash; // 套接字是否支持接收哈希。
};
Protocol Family
Socket 支持廣泛的 PFs,主要有以下 4 類:
- PF_INETv4v6 sockets(IP Socket):基于 IPv4v6 網(wǎng)絡(luò)層協(xié)議,支持 TCP、UDP 傳輸層協(xié)議。
- SOCK_STREAM:TCP 字節(jié)流式傳輸。
- SOCK_DGRAM:UDP 數(shù)據(jù)包式傳輸。
- SOCK_RAW:原始套接字,可以處理 IPv4、ICMP、IGMP 等報(bào)文,常用于網(wǎng)絡(luò)監(jiān)聽、檢驗(yàn)新的協(xié)議或者訪問新的設(shè)備。
- PF_PACKET sockets(Packet Socket):基于 Device Driver(設(shè)備驅(qū)動(dòng)),支持對(duì)底層數(shù)據(jù)包的捕獲和注入,常用于網(wǎng)絡(luò)安全、網(wǎng)絡(luò)監(jiān)測等場景,例如:網(wǎng)絡(luò)嗅探、協(xié)議分析、數(shù)據(jù)包過濾等。
- PF_NETLINK sockets(Netlink Socket):支持 Kernel Space 和 User Space 之間的通信,常用于網(wǎng)絡(luò)管理和網(wǎng)絡(luò)監(jiān)測等場景,例如:獲取內(nèi)核中的網(wǎng)絡(luò)信息、配置內(nèi)核的網(wǎng)絡(luò)參數(shù)、監(jiān)控網(wǎng)絡(luò)狀態(tài)等。
- PF_UNIX sockets(UNIX socket):用于 Unix-like 系統(tǒng)中的多進(jìn)程之間通信。
值得注意的是,雖然不同的協(xié)議族都使用了同一套 Socket API,但也可能會(huì)存在一些特有的函數(shù)或者數(shù)據(jù)結(jié)構(gòu),用于實(shí)現(xiàn)協(xié)議族特有的功能。例如:
- PF_PACKET 協(xié)議族可以使用 pcap 庫來進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)包捕獲和注入;
- PF_NETLINK 協(xié)議族可以使用 netlink 庫來進(jìn)行內(nèi)核和用戶空間之間的通信。
但是,這些特有的函數(shù)和數(shù)據(jù)結(jié)構(gòu)通常不會(huì)影響套接字編程接口的基本使用方式和語法。
VFS Layer
VFS Layer 屬于 Linux VFS sub-system(虛擬文件子系統(tǒng)),提供了一組通用的 Linux File System Call APIs(SCI),使得 Application 可以使用相同的 API 來完成文件 I/O。
當(dāng) Application 使用 Socket API 發(fā)送或接收數(shù)據(jù)時(shí),Socket Abstraction Layer 會(huì)借助 VFS Layer 來完成 Socket File System 的管理。例如:
- Application 調(diào)用 Socket API socket() 創(chuàng)建 socket 時(shí):在 VFS I/O Layer 中,Socket FD 文件句柄被創(chuàng)建。
- Application 調(diào)用 Socket API close() 關(guān)閉 socket 時(shí):在 VFS I/O Layer 中,文件句柄被釋放,并釋放相關(guān)資源。
PF_INET sockets
PF_INET sockets 基于 IPv4v6 網(wǎng)絡(luò)層協(xié)議,支持 TCP、UDP 等傳輸層協(xié)議。是 Linux 網(wǎng)絡(luò)編程中最常用到的協(xié)議族。
1、創(chuàng)建套接字
socket()
函數(shù)功能:創(chuàng)建一個(gè)新的套接字,返回一個(gè) int 類型的套接字文件描述符(作為 Linux 文件操作的句柄),用于后續(xù)的網(wǎng)絡(luò)連接操作。
函數(shù)原型:
- af 參數(shù):指定 Socket AF(Address Family,地址族),對(duì)于 PF_INETv4v6 sockets 而言,可選:
- AF_INET
- AF_INET6
- type 參數(shù):指定數(shù)據(jù)傳輸方式,可選:
- SOCK_STREAM(面向連接的 TCP)
- SOCK_DGRAM(無連接的 UDP)
- SOCK_RAW(原始 IP 數(shù)據(jù)包)
- protocol:指定具體的傳輸層協(xié)議,可選:
- IPPROTO_TCP
- IPPTOTO_UDP
- 函數(shù)返回值:
- 成功:返回 Socket fd。
- 失敗:返回 -1。
#include
int socket(int af, int type, int protocol);
示例:
// 創(chuàng)建 TCP 套接字
int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 創(chuàng)建 UDP 套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
setsockopt()
函數(shù)功能:用于設(shè)置 Socket 的選項(xiàng)值。
函數(shù)原型:
- sockfd 參數(shù):指定 socket fd。
- level 參數(shù):指定選項(xiàng)的協(xié)議層,可選 SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP 等。
- optname 參數(shù):指定要設(shè)置的選項(xiàng)名。
- SO_REUSEADDR:int 類型,表示重用 IP 地址。
- SO_KEEPALIVE:int 類型,用于啟用/禁用 Keepalive(保持連接)功能。
- SO_LINGER:struct linger 類型,用于指定關(guān)閉套接字時(shí)的行為。
- TCP_NODELAY:int 類型,用于禁用 Nagle 算法,從而實(shí)現(xiàn)數(shù)據(jù)的實(shí)時(shí)傳輸。
- optval 參數(shù):指定存放選項(xiàng)值的緩沖區(qū)入口。
- optlen 參數(shù):指定選項(xiàng)值緩沖區(qū)的長度。
- 函數(shù)返回值:
- 成功:0。
- 失敗:-1,并設(shè)置了 errno 錯(cuò)誤碼。
#include
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
2、配置套接字
bind()
將 Socket 與主機(jī)中的某個(gè) IP:Port 綁定起來。
函數(shù)作用:將套接字與一個(gè)本地 IP:Port 綁定。通常用于服務(wù)端,以便在本地監(jiān)聽網(wǎng)絡(luò)連接。函數(shù)原型:
- sock 參數(shù):指定 Server socket 文件描述符。
- addr 參數(shù):指定 Server sockaddr 結(jié)構(gòu)體變量,指針類型。
- addrlen 參數(shù):指定 addr 變量的大小,使用 sizeof() 計(jì)算得出。
- 函數(shù)返回值:
- 失敗:返回 -1。
#include
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
示例:
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in tcp_socket_addr; // 定義 Server Socket Address
memset(&tcp_socket_addr, 0, sizeof(tcp_socket_addr)); // 初始化結(jié)構(gòu)體內(nèi)存
tcp_socket_addr.sin_family = PF_INET;
tcp_socket_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 定義本地 IP 地址
tcp_socket_addr.sin_port = htons(1314); // 定義本地 Port
bind(tcp_socket, (sockaddr *)&tcp_socket_addr, sizeof(sockaddr)); // 綁定
其中 sockaddr_in 結(jié)構(gòu)類型的聲明如下。使用時(shí),需要先定義并初始化 sockaddr_in,然后再將它強(qiáng)制轉(zhuǎn)化成 sockaddr 來使用。2 個(gè)結(jié)構(gòu)體長度均為 16B,其中,sockaddr_in.sin_family 的 2B 存入 sockaddr.sa_family,剩下的 14B 存入 sockaddr.sa_data。
這樣做是為了在后續(xù)的各種操作中可以更方便的處理 IP 地址和 Port 號(hào)。
#include
struct in_addr {
unsigned long a_addr;
}
struct sockaddr_in {
unsigned short sin_family; // 地址類型(2B)
unsigned short int sin_port; // 端口號(hào)(2B)
struct in_addr sin_addr; // IP 地址(4B)
unsigned char sin_zero[8]; // 填充空間(8B)
}
struct sockaddr {
unsigned short sa_family; // 地址類型(2B)
char sa_data[14]; // 協(xié)議地址(14B)
}
另外,IPv6 的結(jié)構(gòu)體聲明如下:
struct sockaddr_in6
{
sa_family_t sin6_family; // 地址類型,取值為 AF_INET6
in_port_t sin6_port; // 16 位端口號(hào)
uint32_t sin6_flowinfo; // IPv6 流信息
struct in6_addr sin6_addr; // 具體的 IPv6 地址
uint32_t sin6_scope_id; // 接口范圍 ID
};
如果 sock_addr.sin_port 賦值為 0,或者沒有調(diào)用 bind(),而直接調(diào)用 listen(),那么 Kernel 會(huì)自動(dòng)為 Socket 臨時(shí)分配一個(gè) Port。此時(shí)需要調(diào)用 getsockname() 來獲取具體的端口信息。
getsockname(httpd, (struct sockaddr *)&name, &namelen);
ntohs(name.sin_port);
listen()
函數(shù)作用:開始監(jiān)聽來自遠(yuǎn)程主機(jī)的連接請求。通常用于服務(wù)器端,在套接字上等待來自客戶端的連接請求。
函數(shù)原型:
- sock 參數(shù):指定需要進(jìn)入監(jiān)聽狀態(tài)的 Server socket 文件描述符。
- backlog 參數(shù):指定請求隊(duì)列的最大長度,當(dāng)隊(duì)列滿了之后,就不再接收請求。
- 函數(shù)返回值:
- 失敗:返回 -1。
#include
int listen(int sock, int backlog);
connect()
函數(shù)作用:建立與遠(yuǎn)程主機(jī)的連接。通常用于客戶端,以便連接到遠(yuǎn)程服務(wù)器。函數(shù)原型:
- sock 參數(shù):指定 Client socket 文件描述符。
- serv_addr 參數(shù):指定 Server sockaddr 結(jié)構(gòu)體變量,指針類型。
- addrlen 參數(shù):指定 addr 變量的大小,使用 sizeof() 計(jì)算得出。
- 函數(shù)返回值:
- 失敗:返回 -1。
#include
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
示例:
int cli_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in server_sock_addr; // 定義 Server Socket Address
memset(&server_sock_addr, 0, sizeof(server_sock_addr)); // 初始化結(jié)構(gòu)體內(nèi)存
server_sock_addr.sin_family = PF_INET;
server_sock_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 定義本地 IP 地址
server_sock_addr.sin_port = htons(1314); // 定義本地 Port
connect(cli_socket, (sockaddr *)&server_sock_addr, sizeof(sockaddr));
accept()
函數(shù)作用:接受一個(gè)連接請求,返回一個(gè)新的、表示客戶端的 Socket 文件描述符,作為服務(wù)端和客戶端之間發(fā)送與接收操作的句柄。通常用于服務(wù)器端,用于接收客戶端的連接請求。
函數(shù)原型:
- sock 參數(shù):指定 Server socket 文件描述符。
- addr 參數(shù):指定 Client sockaddr 結(jié)構(gòu)體變量,指針類型。
- addrlen 參數(shù):指定 addr 變量的大小,使用 sizeof() 計(jì)算得出。
- 函數(shù)返回值:
- 成功:返回 Client socket fd。
- 失敗:返回 -1。
#include
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
示例:
// 返回一個(gè)新的套接字,用于后續(xù)的發(fā)送和接收
int cli_socket = accept(server_socket, (sockeraddr *)&cli_socket_addr, &len);
getnameinfo()
函數(shù)作用:用于將一個(gè) Sock Addr 轉(zhuǎn)換為對(duì)應(yīng)的 Hostname 或 Service name,以便于記錄日志或者顯示給用戶。
函數(shù)原型:
- addr:表示需要轉(zhuǎn)換的 Sock Addr;
- addrlen:表示該 Socket addr址的長度;
- host:輸出 Hostname 的存儲(chǔ)空間。
- serv:輸出 Service name 的存儲(chǔ)空間。
- hostlen:Hostname 存儲(chǔ)空間的大小。
- servlen:Service name 存儲(chǔ)空間的大小。
- flags:標(biāo)志參數(shù),通常設(shè)置為 0。
- 函數(shù)返回值:
- 成功:返回 0。
- 失敗:返回非 0,并更新 errno 全局變量。
#include
int getnameinfo(const struct sockaddr *addr, socklen_t addrlen,
char *host, socklen_t hostlen,
char *serv, socklen_t servlen, int flags);
3、數(shù)據(jù)傳輸
recv() 和 send()
recv() 和 send() 函數(shù),用于在 TCP Socket 中進(jìn)行數(shù)據(jù)讀寫,屬于阻塞式 I/O(Blocking I/O)模式,即:如果沒有可讀數(shù)據(jù)或者對(duì)端的接收緩沖區(qū)已滿,則函數(shù)將一直等待直到有數(shù)據(jù)可讀或者對(duì)端緩沖區(qū)可寫。
recv():從套接字接收數(shù)據(jù)。
- sockfd 參數(shù):指定要接收 TCP 數(shù)據(jù)的 Socket 文件描述符。
- buf 參數(shù):指定接收數(shù)據(jù)緩沖區(qū)的入口地址。
- len 參數(shù):指定要接收的數(shù)據(jù)的 Byte 數(shù)目。
- flags:指定接收數(shù)據(jù)時(shí)的選項(xiàng),常設(shè)為 0。
- 函數(shù)返回值:
- 成功:返回接收的字節(jié)數(shù)。
- 失敗:返回 -1。
#include
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
send():向套接字發(fā)送數(shù)據(jù)。
- sockfd 參數(shù):指定要發(fā)送 TCP 數(shù)據(jù)的 Socket 文件描述符。
- buf 參數(shù):指定發(fā)送數(shù)據(jù)緩沖區(qū)入的口地址。
- len 參數(shù):指定要發(fā)送數(shù)據(jù)的 Byte 數(shù)目。
- flags 參數(shù):指定發(fā)送數(shù)據(jù)時(shí)的選項(xiàng),常設(shè)為 0。
- 函數(shù)返回值:
- 成功:返回發(fā)送的字節(jié)數(shù)。
- 失敗:返回 -1。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
recvfrom() 和 sendto()
recvfrom() 和 sendto() 函數(shù),用于在 UDP Socket 中進(jìn)行數(shù)據(jù)讀寫以及獲取對(duì)端地址。這兩個(gè)函數(shù)在使用時(shí)需要指定對(duì)端的 IP:Port。
recvfrom():
- sock 參數(shù):指定要接收 UDP 數(shù)據(jù)的 Socket 文件描述符。
- buf 參數(shù):指定接收數(shù)據(jù)緩沖區(qū)的入口地址。
- nbytes 參數(shù):指定要接收數(shù)據(jù)的 Byte 數(shù)目。
- flags 參數(shù):指定接收數(shù)據(jù)時(shí)的選項(xiàng),常設(shè)為 0。
- from 參數(shù):指定源地址 sockaddr 結(jié)構(gòu)體變量的地址。
- addrlen 參數(shù):指定 from 參數(shù)使用的長度,使用 sizeof() 獲取。
- 函數(shù)返回值:
- 成功:返回接收的字節(jié)數(shù)。
- 失敗:返回 -1。
#include
ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags, struct sockadr *from, socklen_t *addrlen);
sendto():
- sock 參數(shù):指定要發(fā)送 UDP 數(shù)據(jù)的 Socket 文件描述符。
- buf 參數(shù):指定發(fā)送數(shù)據(jù)緩沖區(qū)的入口地址。
- nbytes 參數(shù):指定要發(fā)送數(shù)據(jù)的 Byte 數(shù)目。
- flags 參數(shù):指定發(fā)送數(shù)據(jù)時(shí)的選項(xiàng),常設(shè)為 0。
- to 參數(shù):指定目標(biāo)地址 sockaddr 結(jié)構(gòu)體變量的地址。
- addrlen 參數(shù):指定 to 參數(shù)使用的長度,使用 sizeof() 獲取。
- 函數(shù)返回值:
- 成功:返回發(fā)送的字節(jié)數(shù)。
- 失敗:返回 -1。
#include
ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
recvmsg() 和 sendmsg()
recvmsg() 和 sendmsg() 函數(shù),用于在 TCP 和 UDP Socket 中進(jìn)行數(shù)據(jù)讀寫,不僅可以讀寫數(shù)據(jù),還可以讀寫對(duì)端地址、輔助數(shù)據(jù)等信息。
recvmsg():
- sock 參數(shù):指定要接收 TCP 或 UDP 數(shù)據(jù)的 Socket 文件描述符。
- msg 參數(shù):指示將接收的數(shù)據(jù)存儲(chǔ)到 msghdr 結(jié)構(gòu)體中。
- flags 參數(shù):支持函數(shù)的行為,可選 0 或者 MSG_DONTWAIT 等標(biāo)志位。
- 函數(shù)返回值:
- 成功:返回接收的字節(jié)數(shù)。
- 失敗:返回 -1。
#include
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
sendmsg()
- sock 參數(shù):指定要發(fā)送 TCP 或 UDP 數(shù)據(jù)的 Socket 文件描述符。
- msg 參數(shù):指示 msghdr 結(jié)構(gòu)體中包含了要發(fā)送的數(shù)據(jù)、數(shù)據(jù)長度等信息。
- flags 參數(shù):支持函數(shù)的行為,可選 0 或者 MSG_DONTWAIT 等標(biāo)志位。
- 函數(shù)返回值:
- 成功:返回發(fā)送的字節(jié)數(shù)。
- 失敗:返回 -1。
#include
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
msghdr 結(jié)構(gòu)體定義如下:
struct msghdr {
/* 指定接收或發(fā)送數(shù)據(jù)的對(duì)端地址,可以為 NULL 或 0,表示不需要使用對(duì)端地址。*/
void msg_name; / optional address /
socklen_t msg_namelen; / size of address */
/* 指定接收或發(fā)送數(shù)據(jù)的緩沖區(qū)和緩沖區(qū)大小,可以使用多個(gè)緩沖區(qū)同時(shí)接收或發(fā)送數(shù)據(jù)。*/
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
/* 指定一些附加的控制信息,可以為 NULL 或 0。*/
void msg_control; / ancillary data, see below /
size_t msg_controllen; / ancillary data buffer len */
/* 指定函數(shù)的行為,例如是否需要接收帶外數(shù)據(jù)等。/
int msg_flags; / flags on received message */
};
flags 參數(shù)類型
- MSG_PEEK:允許從接收隊(duì)列中查看數(shù)據(jù)而不將其刪除。這意味著,如果接收隊(duì)列中有數(shù)據(jù),recv() 函數(shù)將返回?cái)?shù)據(jù)的一個(gè)副本,但是該數(shù)據(jù)仍將留在接收隊(duì)列中。這對(duì)于查看接收隊(duì)列中的數(shù)據(jù)而不實(shí)際處理它們非常有用。此外,使用 MSG_PEEK 選項(xiàng),我們可以檢查套接字緩沖區(qū)中是否有足夠的數(shù)據(jù)可供讀取,以便稍后調(diào)用 recv() 函數(shù)。
- MSG_WAITALL:如果套接字緩沖區(qū)中沒有足夠的數(shù)據(jù),則 recv() 函數(shù)將一直等待,直到收到請求的數(shù)據(jù)量。
- MSG_DONTWAIT:指定此標(biāo)志后,recv() 函數(shù)將立即返回,即使沒有收到數(shù)據(jù)也不會(huì)阻塞。如果沒有數(shù)據(jù)可用,則 recv() 將返回 -1,并將 errno 設(shè)置為 EAGAIN 或 EWOULDBLOCK。
- MSG_OOB:用于處理帶外數(shù)據(jù),即緊急數(shù)據(jù)。帶外數(shù)據(jù)不遵循正常的傳輸控制協(xié)議(如 TCP),可以使用此標(biāo)志將其標(biāo)記為緊急數(shù)據(jù)并將其與其他數(shù)據(jù)分開處理。
- MSG_TRUNC:如果接收緩沖區(qū)中的數(shù)據(jù)比接收緩沖區(qū)長度長,則截?cái)鄶?shù)據(jù)并返回。
- MSG_CTRUNC:如果接收緩沖區(qū)中的控制消息(例如帶外數(shù)據(jù)或錯(cuò)誤消息)比接收緩沖區(qū)長度長,則截?cái)嘞⒉⒎祷亍?/li>
4、關(guān)閉套接字
close()
函數(shù)作用:關(guān)閉套接字連接。函數(shù)原型:
- fd:指定要關(guān)閉的 Socket 的文件描述符。
- 函數(shù)返回值:
- 失敗:返回 -1。
#include
int close(int fd);
TCP Socket 編程示例
服務(wù)端
#include
#include
#include
#include
#include
#include
#include
#define ERR_MSG(err_code) do {
err_code = errno;
fprintf(stderr, "ERROR code: %d n", err_code);
perror("PERROR message");
} while (0)
const int BUF_LEN = 100;
int main(void)
{
/* 配置 Server Sock 信息。*/
struct sockaddr_in srv_sock_addr;
memset(&srv_sock_addr, 0, sizeof(srv_sock_addr));
srv_sock_addr.sin_family = AF_INET;
srv_sock_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 即 0.0.0.0 表示監(jiān)聽本機(jī)所有的 IP 地址。
srv_sock_addr.sin_port = htons(6666);
/* 創(chuàng)建 Server Socket。*/
int srv_socket_fd = 0;
if (-1 == (srv_socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))) {
printf("Create socket file descriptor ERROR.n");
ERR_MSG(errno);
exit(EXIT_FAILURE);
}
/* 設(shè)置 Server Socket 選項(xiàng)。*/
int optval = 1;
if (setsockopt(srv_socket_fd,
SOL_SOCKET, // 表示套接字選項(xiàng)的協(xié)議層。
SO_REUSEADDR, // 表示在綁定地址時(shí)允許重用本地地址。這樣做的好處是,當(dāng)服務(wù)器進(jìn)程崩潰或被關(guān)閉時(shí),可以更快地重新啟動(dòng)服務(wù)器,而不必等待一段時(shí)間來釋放之前使用的套接字。
&optval,
sizeof(optval)) < 0)
{
printf("Set socket options ERROR.n");
ERR_MSG(errno);
exit(EXIT_FAILURE);
}
/* 綁定 Socket 與 Sock Address 信息。*/
if (-1 == bind(srv_socket_fd,
(struct sockaddr *)&srv_sock_addr,
sizeof(srv_sock_addr)))
{
printf("Bind socket ERROR.n");
ERR_MSG(errno);
exit(EXIT_FAILURE);
}
/* 開始監(jiān)聽 Client 發(fā)出的連接請求。*/
if (-1 == listen(srv_socket_fd, 10))
{
printf("Listen socket ERROR.n");
ERR_MSG(errno);
exit(EXIT_FAILURE);
}
/* 初始化 Client Sock 信息存儲(chǔ)變量。*/
struct sockaddr cli_sock_addr;
memset(&cli_sock_addr, 0, sizeof(cli_sock_addr));
int cli_sockaddr_len = sizeof(cli_sock_addr);
int cli_socket_fd = 0;
int recv_len = 0;
char buff[BUF_LEN] = {0};
/* 永遠(yuǎn)接受 Client 的連接請求。*/
while (1)
{
if (-1 == (cli_socket_fd = accept(srv_socket_fd,
(struct sockaddr *)(&cli_sock_addr), // 填充 Client Sock 信息。
(socklen_t *)&cli_sockaddr_len)))
{
printf("Accept connection from client ERROR.n");
ERR_MSG(errno);
exit(EXIT_FAILURE);
}
/* 接收指定 Client Socket 發(fā)出的數(shù)據(jù),*/
if ((recv_len = recv(cli_socket_fd, buff, BUF_LEN, 0)) < 0)
{
printf("Receive from client ERROR.n");
ERR_MSG(errno);
exit(EXIT_FAILURE);
}
printf("Recevice data from client: %sn", buff);
/* 將收到的數(shù)據(jù)重新發(fā)送給指定的 Client Socket。*/
send(cli_socket_fd, buff, recv_len, 0);
printf("Send data to client: %sn", buff);
/* 每處理完一次 Client 請求,即關(guān)閉連接。*/
close(cli_socket_fd);
memset(buff, 0, BUF_LEN);
}
close(srv_socket_fd);
return EXIT_SUCCESS;
}
客戶端
#include
#include
#include
#include
#include
#include
#include
#define ERR_MSG(err_code) do {
err_code = errno;
fprintf(stderr, "ERROR code: %d n", err_code);
perror("PERROR message");
} while (0)
const int BUF_LEN = 100;
int main(void)
{
/* 配置 Server Sock 信息。*/
struct sockaddr_in srv_sock_addr;
memset(&srv_sock_addr, 0, sizeof(srv_sock_addr));
srv_sock_addr.sin_family = AF_INET;
srv_sock_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
srv_sock_addr.sin_port = htons(6666);
int cli_socket_fd = 0;
char send_buff[BUF_LEN];
char recv_buff[BUF_LEN];
/* 永循環(huán)從終端接收輸入,并發(fā)送到 Server。*/
while (1) {
/* 創(chuàng)建 Client Socket。*/
if (-1 == (cli_socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
{
printf("Create socket ERROR.n");
ERR_MSG(errno);
exit(EXIT_FAILURE);
}
/* 連接到 Server Sock 信息指定的 Server。*/
if (-1 == connect(cli_socket_fd,
(struct sockaddr *)&srv_sock_addr,
sizeof(srv_sock_addr)))
{
printf("Connect to server ERROR.n");
ERR_MSG(errno);
exit(EXIT_FAILURE);
}
/* 從 stdin 接收輸入,再發(fā)送到建立連接的 Server Socket。*/
fputs("Send to server> ", stdout);
fgets(send_buff, BUF_LEN, stdin);
send(cli_socket_fd, send_buff, BUF_LEN, 0);
memset(send_buff, 0, BUF_LEN);
/* 從建立連接的 Server 接收數(shù)據(jù)。*/
recv(cli_socket_fd, recv_buff, BUF_LEN, 0);
printf("Recevice from server: %sn", recv_buff);
memset(recv_buff, 0, BUF_LEN);
/* 每次 Client 請求和響應(yīng)完成后,關(guān)閉連接。*/
close(cli_socket_fd);
}
return EXIT_SUCCESS;
}
測試
編譯:
$ gcc -g -std=c99 -Wall tcp_server.c -o tcp_server
$ gcc -g -std=c99 -Wall tcp_client.c -o tcp_client
運(yùn)行:
- 先啟動(dòng) TCP Server:
$ ./tcp_server
- 查看監(jiān)聽 Socket 是否綁定成功:
$ netstat -lpntu | grep 6666
tcp 0 0 0.0.0.0:6666 0.0.0.0:* LISTEN 28675/./tcp_server
- 啟動(dòng) TCP Client
$ ./tcp_client
UDP Socket 編程示例
·
服務(wù)端
#include
#include
#include
#include
#include
#include
#include
#define BUF_LEN 100
int main(void)
{
int ServerFd;
char Buf[BUF_LEN] = {0};
struct sockaddr ClientAddr;
struct sockaddr_in ServerSockAddr;
int addr_size = 0;
int optval = 1;
/* 創(chuàng)建 UDP 服務(wù)端 Socket */
if ( -1 == (ServerFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
{
printf("socket error!n");
exit(1);
}
/* 設(shè)置服務(wù)端信息 */
memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 給結(jié)構(gòu)體ServerSockAddr清零
ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址
ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 自動(dòng)獲取IP地址
ServerSockAddr.sin_port = htons(1314); // 端口
// 設(shè)置地址和端口號(hào)可以重復(fù)使用
if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
{
printf("setsockopt error!n");
exit(1);
}
/* 綁定操作,綁定前加上上面的socket屬性可重復(fù)使用地址 /
if (-1 == bind(ServerFd, (struct sockaddr)&ServerSockAddr, sizeof(ServerSockAddr)))
{
printf("bind error!n");
exit(1);
}
addr_size = sizeof(ClientAddr);
while (1)
{
/* 接受客戶端的返回?cái)?shù)據(jù) */
int str_len = recvfrom(ServerFd, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);
printf("客戶端發(fā)送過來的數(shù)據(jù)為:%sn", Buf);
/* 發(fā)送數(shù)據(jù)到客戶端 */
sendto(ServerFd, Buf, str_len, 0, &ClientAddr, addr_size);
/* 清空緩沖區(qū) */
memset(Buf, 0, BUF_LEN);
}
close(ServerFd);
return 0;
}
客戶端
#include
#include
#include
#include
#include
#include
#define BUF_LEN 100
int main(void)
{
int ClientFd;
char Buf[BUF_LEN] = {0};
struct sockaddr ServerAddr;
int addr_size = 0;
struct sockaddr_in ServerSockAddr;
/* 創(chuàng)建客戶端socket */
if (-1 == (ClientFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
{
printf("socket error!n");
exit(1);
}
/* 向服務(wù)器發(fā)起請求 */
memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));
ServerSockAddr.sin_family = PF_INET;
ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
ServerSockAddr.sin_port = htons(1314);
addr_size = sizeof(ServerAddr);
while (1)
{
printf("請輸入一個(gè)字符串,發(fā)送給服務(wù)端:");
gets(Buf);
/* 發(fā)送數(shù)據(jù)到服務(wù)端 /
sendto(ClientFd, Buf, strlen(Buf), 0, (struct sockaddr)&ServerSockAddr, sizeof(ServerSockAddr));
/* 接受服務(wù)端的返回?cái)?shù)據(jù) */
recvfrom(ClientFd, Buf, BUF_LEN, 0, &ServerAddr, &addr_size);
printf("服務(wù)端發(fā)送過來的數(shù)據(jù)為:%sn", Buf);
memset(Buf, 0, BUF_LEN); // 重置緩沖區(qū)
}
close(ClientFd); // 關(guān)閉套接字
return 0;
}
測試
運(yùn)行:
$ netstat -lpntu | grep 1314
udp 0 0 0.0.0.0:1314 0.0.0.0:* 29729/./udp_server
審核編輯 黃宇
-
服務(wù)器
+關(guān)注
關(guān)注
12文章
9017瀏覽量
85182 -
Socket
+關(guān)注
關(guān)注
0文章
211瀏覽量
34632 -
網(wǎng)絡(luò)編程
+關(guān)注
關(guān)注
0文章
71瀏覽量
10067 -
BSD
+關(guān)注
關(guān)注
0文章
30瀏覽量
10397
發(fā)布評(píng)論請先 登錄
相關(guān)推薦
評(píng)論