精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Socket 網(wǎng)絡(luò)編程框架介紹

科技綠洲 ? 來源:Linux開發(fā)架構(gòu)之路 ? 作者:Linux開發(fā)架構(gòu)之路 ? 2023-11-09 14:19 ? 次閱讀

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 大模塊組成:

  1. BSD Socket APIs
  2. Socket Abstraction Layer
  3. 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è)步驟:

  1. 創(chuàng)建套接字:使用 socket() 函數(shù)創(chuàng)建一個(gè)新的 socket fd。
  2. 配置套接字:使用一些其他的 Socket API 函數(shù),例如 bind()、connect() 和 listen() 來配置 socket,使其能夠接收和發(fā)送數(shù)據(jù)。
  3. 數(shù)據(jù)傳輸:使用 send() 和 recv() 函數(shù)進(jìn)行數(shù)據(jù)傳輸。
  4. 關(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é)以下工作:

  1. 實(shí)現(xiàn)了 Socket File System(套接字文件系統(tǒng)),用于管理 User Process 和 socket fd 之間的關(guān)系,包括 socket fd 的創(chuàng)建、打開、讀寫等操作。
  2. 實(shí)現(xiàn)了 Struct Socket、Struct Sock、Protocol Family(協(xié)議族)、Address Family(地址族)等數(shù)據(jù)結(jié)構(gòu)。
  3. 實(shí)現(xiàn)了 TCP/IP 協(xié)議棧,包括:TCP、UDP、ICMP 等協(xié)議。
  4. 實(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 類:

  1. 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)行:

  1. 先啟動(dòng) TCP Server:

$ ./tcp_server

  1. 查看監(jiān)聽 Socket 是否綁定成功:

$ netstat -lpntu | grep 6666
tcp 0 0 0.0.0.0:6666 0.0.0.0:* LISTEN 28675/./tcp_server

  1. 啟動(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

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 服務(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
    BSD
    +關(guān)注

    關(guān)注

    0

    文章

    30

    瀏覽量

    10397
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    基于UDP協(xié)議的Socket網(wǎng)絡(luò)編程模式的實(shí)現(xiàn)

    傳輸層進(jìn)行網(wǎng)絡(luò)通信編程的接口是Socket&它是通用的開發(fā)網(wǎng)絡(luò)通信程序標(biāo)準(zhǔn)) 本文介紹了基于UDP協(xié)議S
    發(fā)表于 09-09 15:10 ?42次下載

    Linux下基于Socket網(wǎng)絡(luò)編程

    linux開發(fā)編程教程資料——Linux下基于Socket網(wǎng)絡(luò)編程,感興趣的小伙伴們可以看一看。
    發(fā)表于 08-23 16:23 ?0次下載

    Linux-socket網(wǎng)絡(luò)編程

    linux開發(fā)編程教程資料——Linux-socket網(wǎng)絡(luò)編程,感興趣的小伙伴們可以看一看。
    發(fā)表于 08-23 16:23 ?0次下載

    Linux下Socket網(wǎng)絡(luò)編程

    linux開發(fā)編程教程資料——Linux下Socket網(wǎng)絡(luò)編程,感興趣的小伙伴們可以看一看。
    發(fā)表于 08-23 16:23 ?0次下載

    linux-socket網(wǎng)絡(luò)編程詳解

    linux開發(fā)編程教程資料——linux-socket網(wǎng)絡(luò)編程詳解,感興趣的小伙伴們可以看一看。
    發(fā)表于 08-23 16:23 ?0次下載

    linux-網(wǎng)絡(luò)編程-socket編程

    linux開發(fā)編程教程資料——linux-網(wǎng)絡(luò)編程-socket編程,感興趣的小伙伴們可以看一看。
    發(fā)表于 08-23 16:23 ?0次下載

    Socket網(wǎng)絡(luò)編程

    計(jì)算機(jī)學(xué)習(xí)相關(guān)知識(shí)學(xué)習(xí)教程之Socket網(wǎng)絡(luò)編程
    發(fā)表于 09-01 15:01 ?0次下載

    TCP-IP_Socket網(wǎng)絡(luò)編程

    網(wǎng)絡(luò)編程的基礎(chǔ)知識(shí)--TCP-IP_Socket網(wǎng)絡(luò)編程
    發(fā)表于 09-01 15:01 ?0次下載

    vc-網(wǎng)絡(luò)編程(socket)詳解

    vc編程---網(wǎng)絡(luò)編程(socket)詳解,感興趣的可以看看。
    發(fā)表于 09-01 15:27 ?0次下載

    Windows-Socket-網(wǎng)絡(luò)編程詳解

    Windows-Socket-網(wǎng)絡(luò)編程詳解,感興趣的可以看看。
    發(fā)表于 09-01 15:27 ?0次下載

    Windows Socket網(wǎng)絡(luò)編程(二) —套接字編程原理

    Windows-Socket-網(wǎng)絡(luò)編程教程,感興趣的可以看看。
    發(fā)表于 09-01 15:27 ?0次下載

    Socket網(wǎng)絡(luò)基礎(chǔ)編程

    10.2.1 socket概述 1.socket定義 在Linux中的網(wǎng)絡(luò)編程是通過socket接口來進(jìn)行的。人們常說的
    發(fā)表于 10-18 17:13 ?3次下載

    如何理解socket編程接口

    Socket編程接口是一種網(wǎng)絡(luò)編程的基本概念,它提供了一種在不同計(jì)算機(jī)之間進(jìn)行通信的方法。 Socket
    的頭像 發(fā)表于 08-16 10:48 ?354次閱讀

    什么是socket編程 socket與tcp/ip協(xié)議的關(guān)系

    什么是Socket編程 Socket編程是一種網(wǎng)絡(luò)編程技術(shù),它允許程序之間進(jìn)行通信。在計(jì)算機(jī)科學(xué)
    的頭像 發(fā)表于 11-01 16:01 ?165次閱讀

    socket 編程基礎(chǔ)入門

    Socket 編程基礎(chǔ)入門 在計(jì)算機(jī)網(wǎng)絡(luò)中,Socket 是一個(gè)抽象層,它將網(wǎng)絡(luò)通信的細(xì)節(jié)隱藏起來,為開發(fā)者提供了一個(gè)簡單的接口來發(fā)送和接收
    的頭像 發(fā)表于 11-12 14:03 ?174次閱讀