那曲檬骨新材料有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Socket 網絡編程框架介紹

科技綠洲 ? 來源:Linux開發架構之路 ? 作者:Linux開發架構之路 ? 2023-11-09 14:19 ? 次閱讀

Socket 網絡編程框架

Socket(套接字)是一個網絡編程概念,描述了一個通信端點(Endpoint),用于建立網絡連接(Connection)并傳輸數據。

Linux Kernel 提供了一套面向 Socket 的網絡編程框架,并通過提供一組標準的 System call APIs,使得開發者可以在 Userspace 中便捷的開發各種 Network Applications,例如:基于 HTTP 協議的 Web 服務器、基于 SMTP 協議的郵件服務器、基于 FTP 協議的文件服務器等等。

Linux Socket 網絡編程框架主要由 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 的接口封裝層,提供了一套兼容絕大部分網絡通信協議族的標準 Socket APIs。

  • socket():創建一個新的 socket,返回一個 int 類型的 socket fd(File Descriptor,套接字文件描述符),用于后續的網絡連接操作。
  • bind():將 socket 與一個本地 IP:Port 綁定,通常用于服務端,以便在本地監聽網絡連接。
  • connect():建立與遠程主機的連接,通常用于客戶端,以便連接到遠程服務器。
  • listen():開始監聽來自遠程主機的連接請求,通常用于服務器端,等待來自客戶端的連接請求。
  • accept():接受一個連接請求,返回一個新的 socket fd,通常用于服務器端,用于接收客戶端的連接請求。
  • send():向 socket 發送數據。
  • recv():從 socket 接收數據。
  • close():關閉 socket 連接。

Socket API 的使用通常可以分為以下幾個步驟:

  1. 創建套接字:使用 socket() 函數創建一個新的 socket fd。
  2. 配置套接字:使用一些其他的 Socket API 函數,例如 bind()、connect() 和 listen() 來配置 socket,使其能夠接收和發送數據。
  3. 數據傳輸:使用 send() 和 recv() 函數進行數據傳輸。
  4. 關閉套接字:使用 close() 函數關閉 socket 連接。

需要注意的是,Socket API 并不是線程安全的,如果有多個線程同時使用了同一個 socket fd,則可能會導致數據傳輸錯誤或其他問題。為了避免這種情況,Application 需要進行適當的同步和數據處理。

Socket Abstraction Layer

Socket Abstraction Layer(Socket 抽象層),是 Socket API 的底層支撐,主要負責以下工作:

  1. 實現了 Socket File System(套接字文件系統),用于管理 User Process 和 socket fd 之間的關系,包括 socket fd 的創建、打開、讀寫等操作。
  2. 實現了 Struct Socket、Struct Sock、Protocol Family(協議族)、Address Family(地址族)等數據結構。
  3. 實現了 TCP/IP 協議棧,包括:TCP、UDP、ICMP 等協議。
  4. 實現了 L4 傳輸層功能,處理傳輸層協議的連接建立、數據傳輸、連接維護等操作。

Socket & Sock

圖片

  • Struct Socket 是在 Socket Layer 中定義的數據結構,面向上層 Socket API,包含了一個 Socket 所具有的各種屬性,例如:狀態、類型、標記、關聯的 Sock 等。
  • Struct Sock 是在 Sock Layer 中定義的數據結構,面向底層協議棧實現,表示一個 Socket 對應的 PCB(Protocol Control Block,協議控制塊),即:與某種網絡協議相關的一些信息和狀態,例如:TCP PCB 就包括了 TCP 連接狀態、發送緩沖區、接收緩沖區、擁塞窗口等。

Socket Layer 與 Network Driver(網絡設備驅動程序)之間通過 Socket Buffer(skb_buff)進行交互,當 Socket Layer 接收到 Application 的數據時,會將數據存儲在 Socket Buffer 中,并將 Socket Buffer 傳遞給對應的 Sock Layer 進行處理。Struct Socket 和 Struct Sock 之間通過指針進行關聯綁定,共同實現 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; // 套接字標志,如 O_NONBLOCK、O_ASYNC 等;
    struct file *file; // 套接字對應的文件結構體;
    struct sock *sk; // 指向套接字對應的 Sock 結構體;
    const struct proto_ops *ops; // 套接字對應的操作函數集,如 inet_stream_ops、inet_dgram_ops 等;
    struct socket_wq wq; // 套接字等待隊列;
    };

typedef enum
{
SS_FREE=0; // 未分配
SS_UNCONNECTED; // 未連接到任何套接字
SS_CONNECTING; // 處于連接過程中
SS_CONNECTED; // 已經連接到另一個套接字
SS_DISCONNECTING; // 處于斷開連接過程中
} socket_state;

Sock Layer

Struct Sock 包含了 Socket 的各種底層執行狀態和操作信息,例如:接收和發送緩沖區、套接字隊列、套接字協議信息等。

// linux/include/net/sock.h

struct sock {
/* Socket family and type */
unsigned short family; // 協議族,如 AF_INET、AF_PACKET 等;
__u16 type; // 套接字類型,如 SOCK_STREAM、SOCK_DGRAM 等;
unsigned long flags; // 套接字標志,如 O_NONBLOCK、O_ASYNC 等;

/* Protocol specific elements of the socket */
struct proto        *ops;      // 協議特定操作函數集;
struct net_device   *sk_net;   // 套接字所在的網絡設備;

/* Memory allocation cache */
kmem_cache_t        *sk_slab;  // 套接字內存分配緩存;

/* Socket state */
atomic_t            refcnt;    // 套接字引用計數;
struct mutex        sk_lock;   // 套接字鎖,用于保護套接字狀態的一致性;

/* Send and receive buffers */

struct sk_buff_head sk_receive_queue; // 接收隊列,保存了等待接收的數據包;
struct sk_buff_head sk_write_queue; // 發送隊列,保存了等待發送的數據包;
struct sk_buff *sk_send_head; // 發送緩沖區的頭指針;
struct sk_buff *sk_send_tail; // 發送緩沖區的尾指針;

/* Receive queue */
struct sk_buff      *sk_receive_skb;   // 當前正在接收的數據包;

/* Transport specific fields */
__u32           sk_priority;           // 套接字優先級;
struct dst_entry    *sk_dst_cache;     // 緩存的目標設備;
struct dst_entry    *sk_dst_pending_confirm;
struct flowi        sk_fl;             // Flowi 結構體,保存了套接字相關的流信息;
struct sk_filter    *sk_filter;        // 過濾器;
struct sk_buff_head sk_async_wait_queue;  // 異步等待隊列;

/* Socket buffer allocations */
unsigned long       sk_wmem_alloc;  // 發送緩沖區已分配的內存;
unsigned long       sk_omem_alloc;

/* User and kernel buffers */
struct socket_wq    *sk_wq;   // 套接字等待隊列;
struct page_frag    sk_frag;  // 內存分配器的頁片段;
int         sk_forward_alloc; // 前向分配的字節數;
int         sk_rxhash;        // 套接字是否支持接收哈希。

};

Protocol Family

Socket 支持廣泛的 PFs,主要有以下 4 類:

  1. PF_INETv4v6 sockets(IP Socket):基于 IPv4v6 網絡層協議,支持 TCP、UDP 傳輸層協議。
  • SOCK_STREAM:TCP 字節流式傳輸。
  • SOCK_DGRAM:UDP 數據包式傳輸。
  • SOCK_RAW:原始套接字,可以處理 IPv4、ICMP、IGMP 等報文,常用于網絡監聽、檢驗新的協議或者訪問新的設備。
  • PF_PACKET sockets(Packet Socket):基于 Device Driver(設備驅動),支持對底層數據包的捕獲和注入,常用于網絡安全、網絡監測等場景,例如:網絡嗅探、協議分析、數據包過濾等。
  • PF_NETLINK sockets(Netlink Socket):支持 Kernel Space 和 User Space 之間的通信,常用于網絡管理和網絡監測等場景,例如:獲取內核中的網絡信息、配置內核的網絡參數、監控網絡狀態等。
  • PF_UNIX sockets(UNIX socket):用于 Unix-like 系統中的多進程之間通信。

值得注意的是,雖然不同的協議族都使用了同一套 Socket API,但也可能會存在一些特有的函數或者數據結構,用于實現協議族特有的功能。例如:

  • PF_PACKET 協議族可以使用 pcap 庫來進行網絡數據包捕獲和注入;
  • PF_NETLINK 協議族可以使用 netlink 庫來進行內核和用戶空間之間的通信。

但是,這些特有的函數和數據結構通常不會影響套接字編程接口的基本使用方式和語法。

圖片

VFS Layer

VFS Layer 屬于 Linux VFS sub-system(虛擬文件子系統),提供了一組通用的 Linux File System Call APIs(SCI),使得 Application 可以使用相同的 API 來完成文件 I/O。

當 Application 使用 Socket API 發送或接收數據時,Socket Abstraction Layer 會借助 VFS Layer 來完成 Socket File System 的管理。例如:

  • Application 調用 Socket API socket() 創建 socket 時:在 VFS I/O Layer 中,Socket FD 文件句柄被創建。
  • Application 調用 Socket API close() 關閉 socket 時:在 VFS I/O Layer 中,文件句柄被釋放,并釋放相關資源。

PF_INET sockets

PF_INET sockets 基于 IPv4v6 網絡層協議,支持 TCP、UDP 等傳輸層協議。是 Linux 網絡編程中最常用到的協議族。

圖片

1、創建套接字

socket()

函數功能:創建一個新的套接字,返回一個 int 類型的套接字文件描述符(作為 Linux 文件操作的句柄),用于后續的網絡連接操作。

函數原型:

  • af 參數:指定 Socket AF(Address Family,地址族),對于 PF_INETv4v6 sockets 而言,可選:
  • AF_INET
  • AF_INET6
  • type 參數:指定數據傳輸方式,可選:
  • SOCK_STREAM(面向連接的 TCP)
  • SOCK_DGRAM(無連接的 UDP)
  • SOCK_RAW(原始 IP 數據包)
  • protocol:指定具體的傳輸層協議,可選:
  • IPPROTO_TCP
  • IPPTOTO_UDP
  • 函數返回值:
  • 成功:返回 Socket fd。
  • 失?。悍祷?-1。

#include

int socket(int af, int type, int protocol);

示例:

// 創建 TCP 套接字
int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

// 創建 UDP 套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

setsockopt()

函數功能:用于設置 Socket 的選項值。

函數原型:

  • sockfd 參數:指定 socket fd。
  • level 參數:指定選項的協議層,可選 SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP 等。
  • optname 參數:指定要設置的選項名。
  • SO_REUSEADDR:int 類型,表示重用 IP 地址。
  • SO_KEEPALIVE:int 類型,用于啟用/禁用 Keepalive(保持連接)功能。
  • SO_LINGER:struct linger 類型,用于指定關閉套接字時的行為。
  • TCP_NODELAY:int 類型,用于禁用 Nagle 算法,從而實現數據的實時傳輸。
  • optval 參數:指定存放選項值的緩沖區入口。
  • optlen 參數:指定選項值緩沖區的長度。
  • 函數返回值:
  • 成功:0。
  • 失敗:-1,并設置了 errno 錯誤碼。

#include

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

2、配置套接字

bind()

將 Socket 與主機中的某個 IP:Port 綁定起來。

函數作用:將套接字與一個本地 IP:Port 綁定。通常用于服務端,以便在本地監聽網絡連接。函數原型:

  • sock 參數:指定 Server socket 文件描述符。
  • addr 參數:指定 Server sockaddr 結構體變量,指針類型。
  • addrlen 參數:指定 addr 變量的大小,使用 sizeof() 計算得出。
  • 函數返回值:
  • 失敗:返回 -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)); // 初始化結構體內存

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 結構類型的聲明如下。使用時,需要先定義并初始化 sockaddr_in,然后再將它強制轉化成 sockaddr 來使用。2 個結構體長度均為 16B,其中,sockaddr_in.sin_family 的 2B 存入 sockaddr.sa_family,剩下的 14B 存入 sockaddr.sa_data。

這樣做是為了在后續的各種操作中可以更方便的處理 IP 地址和 Port 號。

#include

struct in_addr {
unsigned long a_addr;
}

struct sockaddr_in {
unsigned short sin_family; // 地址類型(2B)
unsigned short int sin_port; // 端口號(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]; // 協議地址(14B)
}

另外,IPv6 的結構體聲明如下:

struct sockaddr_in6
{
sa_family_t sin6_family; // 地址類型,取值為 AF_INET6
in_port_t sin6_port; // 16 位端口號
uint32_t sin6_flowinfo; // IPv6 流信息
struct in6_addr sin6_addr; // 具體的 IPv6 地址
uint32_t sin6_scope_id; // 接口范圍 ID
};

如果 sock_addr.sin_port 賦值為 0,或者沒有調用 bind(),而直接調用 listen(),那么 Kernel 會自動為 Socket 臨時分配一個 Port。此時需要調用 getsockname() 來獲取具體的端口信息。

getsockname(httpd, (struct sockaddr *)&name, &namelen);
ntohs(name.sin_port);

listen()

函數作用:開始監聽來自遠程主機的連接請求。通常用于服務器端,在套接字上等待來自客戶端的連接請求。

函數原型:

  • sock 參數:指定需要進入監聽狀態的 Server socket 文件描述符。
  • backlog 參數:指定請求隊列的最大長度,當隊列滿了之后,就不再接收請求。
  • 函數返回值:
  • 失?。悍祷?-1。

#include

int listen(int sock, int backlog);

connect()

函數作用:建立與遠程主機的連接。通常用于客戶端,以便連接到遠程服務器。函數原型:

  • sock 參數:指定 Client socket 文件描述符。
  • serv_addr 參數:指定 Server sockaddr 結構體變量,指針類型。
  • addrlen 參數:指定 addr 變量的大小,使用 sizeof() 計算得出。
  • 函數返回值:
  • 失?。悍祷?-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)); // 初始化結構體內存

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()

函數作用:接受一個連接請求,返回一個新的、表示客戶端的 Socket 文件描述符,作為服務端和客戶端之間發送與接收操作的句柄。通常用于服務器端,用于接收客戶端的連接請求。

函數原型:

  • sock 參數:指定 Server socket 文件描述符。
  • addr 參數:指定 Client sockaddr 結構體變量,指針類型。
  • addrlen 參數:指定 addr 變量的大小,使用 sizeof() 計算得出。
  • 函數返回值:
  • 成功:返回 Client socket fd。
  • 失?。悍祷?-1。

#include

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);

示例:

// 返回一個新的套接字,用于后續的發送和接收
int cli_socket = accept(server_socket, (sockeraddr *)&cli_socket_addr, &len);

getnameinfo()

函數作用:用于將一個 Sock Addr 轉換為對應的 Hostname 或 Service name,以便于記錄日志或者顯示給用戶。

函數原型:

  • addr:表示需要轉換的 Sock Addr;
  • addrlen:表示該 Socket addr址的長度;
  • host:輸出 Hostname 的存儲空間。
  • serv:輸出 Service name 的存儲空間。
  • hostlen:Hostname 存儲空間的大小。
  • servlen:Service name 存儲空間的大小。
  • flags:標志參數,通常設置為 0。
  • 函數返回值:
  • 成功:返回 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、數據傳輸

recv() 和 send()

recv() 和 send() 函數,用于在 TCP Socket 中進行數據讀寫,屬于阻塞式 I/O(Blocking I/O)模式,即:如果沒有可讀數據或者對端的接收緩沖區已滿,則函數將一直等待直到有數據可讀或者對端緩沖區可寫。

recv():從套接字接收數據。

  • sockfd 參數:指定要接收 TCP 數據的 Socket 文件描述符。
  • buf 參數:指定接收數據緩沖區的入口地址。
  • len 參數:指定要接收的數據的 Byte 數目。
  • flags:指定接收數據時的選項,常設為 0。
  • 函數返回值:
  • 成功:返回接收的字節數。
  • 失敗:返回 -1。

#include

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

send():向套接字發送數據。

  • sockfd 參數:指定要發送 TCP 數據的 Socket 文件描述符。
  • buf 參數:指定發送數據緩沖區入的口地址。
  • len 參數:指定要發送數據的 Byte 數目。
  • flags 參數:指定發送數據時的選項,常設為 0。
  • 函數返回值:
  • 成功:返回發送的字節數。
  • 失?。悍祷?-1。

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

recvfrom() 和 sendto()

recvfrom() 和 sendto() 函數,用于在 UDP Socket 中進行數據讀寫以及獲取對端地址。這兩個函數在使用時需要指定對端的 IP:Port。

recvfrom():

  • sock 參數:指定要接收 UDP 數據的 Socket 文件描述符。
  • buf 參數:指定接收數據緩沖區的入口地址。
  • nbytes 參數:指定要接收數據的 Byte 數目。
  • flags 參數:指定接收數據時的選項,常設為 0。
  • from 參數:指定源地址 sockaddr 結構體變量的地址。
  • addrlen 參數:指定 from 參數使用的長度,使用 sizeof() 獲取。
  • 函數返回值:
  • 成功:返回接收的字節數。
  • 失敗:返回 -1。

#include

ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags, struct sockadr *from, socklen_t *addrlen);

sendto():

  • sock 參數:指定要發送 UDP 數據的 Socket 文件描述符。
  • buf 參數:指定發送數據緩沖區的入口地址。
  • nbytes 參數:指定要發送數據的 Byte 數目。
  • flags 參數:指定發送數據時的選項,常設為 0。
  • to 參數:指定目標地址 sockaddr 結構體變量的地址。
  • addrlen 參數:指定 to 參數使用的長度,使用 sizeof() 獲取。
  • 函數返回值:
  • 成功:返回發送的字節數。
  • 失敗:返回 -1。

#include

ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);

recvmsg() 和 sendmsg()

recvmsg() 和 sendmsg() 函數,用于在 TCP 和 UDP Socket 中進行數據讀寫,不僅可以讀寫數據,還可以讀寫對端地址、輔助數據等信息。

recvmsg():

  • sock 參數:指定要接收 TCP 或 UDP 數據的 Socket 文件描述符。
  • msg 參數:指示將接收的數據存儲到 msghdr 結構體中。
  • flags 參數:支持函數的行為,可選 0 或者 MSG_DONTWAIT 等標志位。
  • 函數返回值:
  • 成功:返回接收的字節數。
  • 失?。悍祷?-1。

#include

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

sendmsg()

  • sock 參數:指定要發送 TCP 或 UDP 數據的 Socket 文件描述符。
  • msg 參數:指示 msghdr 結構體中包含了要發送的數據、數據長度等信息。
  • flags 參數:支持函數的行為,可選 0 或者 MSG_DONTWAIT 等標志位。
  • 函數返回值:
  • 成功:返回發送的字節數。
  • 失敗:返回 -1。

#include

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

msghdr 結構體定義如下:

struct msghdr {
/* 指定接收或發送數據的對端地址,可以為 NULL 或 0,表示不需要使用對端地址。*/
void msg_name; / optional address /
socklen_t msg_namelen; /
size of address */

/* 指定接收或發送數據的緩沖區和緩沖區大小,可以使用多個緩沖區同時接收或發送數據。*/
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 */

/* 指定函數的行為,例如是否需要接收帶外數據等。/
int msg_flags; /
flags on received message */
};

flags 參數類型

  • MSG_PEEK:允許從接收隊列中查看數據而不將其刪除。這意味著,如果接收隊列中有數據,recv() 函數將返回數據的一個副本,但是該數據仍將留在接收隊列中。這對于查看接收隊列中的數據而不實際處理它們非常有用。此外,使用 MSG_PEEK 選項,我們可以檢查套接字緩沖區中是否有足夠的數據可供讀取,以便稍后調用 recv() 函數。
  • MSG_WAITALL:如果套接字緩沖區中沒有足夠的數據,則 recv() 函數將一直等待,直到收到請求的數據量。
  • MSG_DONTWAIT:指定此標志后,recv() 函數將立即返回,即使沒有收到數據也不會阻塞。如果沒有數據可用,則 recv() 將返回 -1,并將 errno 設置為 EAGAIN 或 EWOULDBLOCK。
  • MSG_OOB:用于處理帶外數據,即緊急數據。帶外數據不遵循正常的傳輸控制協議(如 TCP),可以使用此標志將其標記為緊急數據并將其與其他數據分開處理。
  • MSG_TRUNC:如果接收緩沖區中的數據比接收緩沖區長度長,則截斷數據并返回。
  • MSG_CTRUNC:如果接收緩沖區中的控制消息(例如帶外數據或錯誤消息)比接收緩沖區長度長,則截斷消息并返回。

4、關閉套接字

close()

函數作用:關閉套接字連接。函數原型:

  • fd:指定要關閉的 Socket 的文件描述符。
  • 函數返回值:
  • 失敗:返回 -1。

#include

int close(int fd);

TCP Socket 編程示例

圖片

服務端

#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 表示監聽本機所有的 IP 地址。
srv_sock_addr.sin_port = htons(6666);

/* 創建 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);
}
/* 設置 Server Socket 選項。*/
int optval = 1;
if (setsockopt(srv_socket_fd,
               SOL_SOCKET,    // 表示套接字選項的協議層。
               SO_REUSEADDR,  // 表示在綁定地址時允許重用本地地址。這樣做的好處是,當服務器進程崩潰或被關閉時,可以更快地重新啟動服務器,而不必等待一段時間來釋放之前使用的套接字。
               &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);
}

/* 開始監聽 Client 發出的連接請求。*/
if (-1 == listen(srv_socket_fd, 10))
{
    printf("Listen socket ERROR.n");
    ERR_MSG(errno);
    exit(EXIT_FAILURE);
}

/* 初始化 Client Sock 信息存儲變量。*/
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};

/* 永遠接受 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 發出的數據,*/
    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);

    /* 將收到的數據重新發送給指定的 Client Socket。*/
    send(cli_socket_fd, buff, recv_len, 0);
    printf("Send data to client: %sn", buff);

    /* 每處理完一次 Client 請求,即關閉連接。*/
    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];

/* 永循環從終端接收輸入,并發送到 Server。*/
while (1) {

    /* 創建 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 接收輸入,再發送到建立連接的 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 接收數據。*/
    recv(cli_socket_fd, recv_buff, BUF_LEN, 0);
    printf("Recevice from server: %sn", recv_buff);
    memset(recv_buff, 0, BUF_LEN);

    /* 每次 Client 請求和響應完成后,關閉連接。*/
    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

運行:

  1. 先啟動 TCP Server:

$ ./tcp_server

  1. 查看監聽 Socket 是否綁定成功:

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

  1. 啟動 TCP Client

$ ./tcp_client

UDP Socket 編程示例

圖片

·

服務端

#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;

/* 創建 UDP 服務端 Socket */
if ( -1 == (ServerFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
{
printf("socket error!n");
exit(1);
}

/* 設置服務端信息 */
memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 給結構體ServerSockAddr清零
ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址
ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 自動獲取IP地址
ServerSockAddr.sin_port = htons(1314); // 端口

// 設置地址和端口號可以重復使用
if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
{
printf("setsockopt error!n");
exit(1);
}

/* 綁定操作,綁定前加上上面的socket屬性可重復使用地址 /
if (-1 == bind(ServerFd, (struct sockaddr
)&ServerSockAddr, sizeof(ServerSockAddr)))
{
printf("bind error!n");
exit(1);
}

addr_size = sizeof(ClientAddr);

while (1)
{
/* 接受客戶端的返回數據 */
int str_len = recvfrom(ServerFd, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);

printf("客戶端發送過來的數據為:%sn", Buf);

/* 發送數據到客戶端 */
sendto(ServerFd, Buf, str_len, 0, &ClientAddr, addr_size);

/* 清空緩沖區 */
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;

/* 創建客戶端socket */
if (-1 == (ClientFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
{
printf("socket error!n");
exit(1);
}

/* 向服務器發起請求 */
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("請輸入一個字符串,發送給服務端:");
gets(Buf);
/* 發送數據到服務端 /
sendto(ClientFd, Buf, strlen(Buf), 0, (struct sockaddr
)&ServerSockAddr, sizeof(ServerSockAddr));

/* 接受服務端的返回數據 */
recvfrom(ClientFd, Buf, BUF_LEN, 0, &ServerAddr, &addr_size);
printf("服務端發送過來的數據為:%sn", Buf);

memset(Buf, 0, BUF_LEN); // 重置緩沖區
}

close(ClientFd); // 關閉套接字

return 0;
}

測試

運行:

$ netstat -lpntu | grep 1314
udp 0 0 0.0.0.0:1314 0.0.0.0:* 29729/./udp_server

審核編輯 黃宇

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 服務器
    +關注

    關注

    12

    文章

    9306

    瀏覽量

    86069
  • Socket
    +關注

    關注

    0

    文章

    212

    瀏覽量

    34889
  • 網絡編程
    +關注

    關注

    0

    文章

    72

    瀏覽量

    10101
  • BSD
    BSD
    +關注

    關注

    0

    文章

    31

    瀏覽量

    10443
收藏 人收藏

    評論

    相關推薦

    基于UDP協議的Socket網絡編程模式的實現

    傳輸層進行網絡通信編程的接口是Socket&它是通用的開發網絡通信程序標準) 本文介紹了基于UDP協議S
    發表于 09-09 15:10 ?42次下載

    Linux下基于Socket網絡編程

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

    Linux-socket網絡編程

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

    Linux下Socket網絡編程

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

    linux-socket網絡編程詳解

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

    linux-網絡編程-socket編程

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

    Socket網絡編程

    計算機學習相關知識學習教程之Socket網絡編程
    發表于 09-01 15:01 ?0次下載

    TCP-IP_Socket網絡編程

    網絡編程的基礎知識--TCP-IP_Socket網絡編程
    發表于 09-01 15:01 ?0次下載

    vc-網絡編程(socket)詳解

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

    Windows-Socket-網絡編程詳解

    Windows-Socket-網絡編程詳解,感興趣的可以看看。
    發表于 09-01 15:27 ?0次下載

    Windows Socket網絡編程(二) —套接字編程原理

    Windows-Socket-網絡編程教程,感興趣的可以看看。
    發表于 09-01 15:27 ?0次下載

    Socket網絡基礎編程

    10.2.1 socket概述 1.socket定義 在Linux中的網絡編程是通過socket接口來進行的。人們常說的
    發表于 10-18 17:13 ?3次下載

    如何理解socket編程接口

    Socket編程接口是一種網絡編程的基本概念,它提供了一種在不同計算機之間進行通信的方法。 Socket
    的頭像 發表于 08-16 10:48 ?511次閱讀

    什么是socket編程 socket與tcp/ip協議的關系

    什么是Socket編程 Socket編程是一種網絡編程技術,它允許程序之間進行通信。在計算機科學
    的頭像 發表于 11-01 16:01 ?482次閱讀

    socket 編程基礎入門

    Socket 編程基礎入門 在計算機網絡中,Socket 是一個抽象層,它將網絡通信的細節隱藏起來,為開發者提供了一個簡單的接口來發送和接收
    的頭像 發表于 11-12 14:03 ?425次閱讀
    鼎尚百家乐的玩法技巧和规则| 二八杠游戏| 百家乐官网投注之对冲投注| 碧桂园太阳城二手房| 百家乐官网德州扑克发牌盒| 贵阳市| 云鼎百家乐的玩法技巧和规则| 百家乐官网tt娱乐场开户注册| 百家乐技巧| 威尼斯人娱乐场 送2688元礼金领取lrm64| 新世百家乐官网的玩法技巧和规则 | 百家乐技巧娱乐博彩| 金域百家乐官网娱乐城| 皇冠网热门小说| 大发888 dafa888游戏| 百家乐赢钱面面观| 百家乐不能视频| bet365娱乐| 百家乐筹码套装100片| 成人百家乐官网的玩法技巧和规则| 百家乐官网现实赌场| 安徽棋牌游戏中心| 百家乐统计软件| 百家乐赌博讨论群| 属虎和属龙合伙做生意| 百家乐官网中的概率| 香港六合彩信息| 百家乐博娱乐场| 凯旋门百家乐现金网| 真人百家乐技巧| 百家乐官网娱乐网77scs| 百家乐官网投注心得和技巧| 网上百家乐官网真实度| tt娱乐城备用| 老虎机干扰器| 大发888游戏下载官方| 大发888娱乐城xiazai| 利都百家乐国际娱乐平台| 百家乐视频游戏聊天| 做生意的摆件| 百家乐玩法与规则|