基于socket通訊的方式,無論用http或者udp或者自定義的協議,程序結構都是類似的。這個以UDP協議為例簡要說明。
#include <stdio.h> // 標準輸入輸出庫
#include <sys/types.h> // 提供了一些數據類型,如ssize_t
#include <sys/socket.h> // 提供socket編程的接口
#include <netinet/in.h> // 提供IPv4和IPv6地址的結構體定義
#include <arpa/inet.h> // 提供網絡地址轉換的函數,如inet_pton和inet_ntop(注意:這里應該是<arpa/inet.h>的拼寫錯誤,正確的是<arpa/inet.h>,但您已經寫對了)
#include <unistd.h> // 提供對POSIX操作系統API的訪問,如close函數
#include <stdlib.h> // 標準庫,提供內存分配、程序退出等函數
#include <sys/stat.h> // 提供對文件狀態的操作,本程序中未使用
#include <fcntl.h> // 提供對文件控制的操作,如文件描述符的設置,本程序中未使用
#include <string.h> // 提供字符串處理的函數,如bzero#define N 64 // 定義緩沖區的大小int main(int argc, char const *argv[]) // 程序的主入口
{int sockfd; // 聲明socket文件描述符sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 創建一個UDP socketif(sockfd < 0) // 檢查socket是否創建成功{perror("sock err"); // 如果創建失敗,打印錯誤信息return -1; // 并返回-1表示錯誤}// 綁定套接字(ip+port)struct sockaddr_in addr; // 聲明一個IPv4地址的結構體addr.sin_family = AF_INET; // 設置地址族為IPv4addr.sin_port = htons(atoi(argv[2])); // 將命令行參數轉換為整數,并轉換為網絡字節序后設置為端口號// 自動綁定所有的本機網卡的地址addr.sin_addr.s_addr = INADDR_ANY; // 設置IP地址為INADDR_ANY,表示綁定到所有可用的網絡接口int addrlen = sizeof(addr); // 獲取地址結構體的長度if(bind(sockfd, (struct sockaddr *)&addr, addrlen) < 0) // 綁定socket到指定的地址和端口{perror("bind err"); // 如果綁定失敗,打印錯誤信息return -1; // 并返回-1表示錯誤}ssize_t len; // 聲明一個變量來存儲接收到的數據長度char buf[N] = {0}; // 聲明并初始化一個緩沖區來存儲接收到的數據struct sockaddr_in cliaddr; // 聲明一個結構體來存儲客戶端的地址信息// cliaddr接收客戶端的地址while (1) // 進入一個無限循環來等待客戶端的數據{bzero(buf, N); // 清空緩沖區len = recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&cliaddr, &addrlen); // 從socket接收數據if(len > 0) // 如果成功接收到數據{printf("recv data=%s\n", buf); // 打印接收到的數據sendto(sockfd, buf, len, 0, (struct sockaddr *)&cliaddr, addrlen); // 將接收到的數據發送回客戶端(注意:這里應該使用len而不是N)}}// 關閉socket(注意:由于有無限循環,這行代碼實際上永遠不會被執行)close(sockfd); // 關閉socket以釋放資源return 0; // 程序正常結束
}
程序功能
這個程序實現了一個簡單的UDP服務器,它監聽一個指定的端口,接收來自客戶端的數據,并將接收到的數據原封不動地發送回客戶端(回顯服務器)。
程序結構
- 初始化:創建socket,設置地址和端口,綁定socket。
- 接收數據:進入一個無限循環,等待并接收來自客戶端的數據。
- 處理數據:打印接收到的數據,并將數據發送回客戶端。
- 關閉socket(實際上不會被執行):在循環結束后關閉socket。
UDP發送和接收函數的參數
recvfrom()
函數:sockfd
:socket文件描述符。buf
:指向存儲接收數據的緩沖區的指針。len
:緩沖區的大小。flags
:標志位,通常設置為0。src_addr
:指向存儲發送方地址信息的結構體的指針。addrlen
:指向存儲地址結構體長度的變量的指針。
sendto()
函數:sockfd
:socket文件描述符。buf
:指向要發送的數據的緩沖區的指針。len
:要發送的數據的長度(注意:這里應該使用實際接收到的數據長度,而不是緩沖區的大小)。flags
:標志位,通常設置為0。dest_addr
:指向存儲接收方地址信息的結構體的指針。addrlen
:地址結構體的長度。
其中:
sockaddr_in
?結構體在 IPv4 網絡編程中用于表示一個 Internet 地址。這個結構體定義在?<netinet/in.h>
?頭文件中(在 POSIX 兼容的系統中),并且它通常用于?bind()
,?connect()
,?sendto()
,?recvfrom()
?等網絡相關的系統調用中,以指定或接收網絡地址信息。
?sockaddr_in
?結構體的定義:
struct sockaddr_in {sa_family_t sin_family; // 地址族,對于 IPv4 來說是 AF_INETuint16_t sin_port; // 端口號,使用網絡字節序(大端模式)struct in_addr sin_addr; // IPv4 地址,也使用網絡字節序// 在某些實現中,可能有一個用于填充的數組,以確保結構體大小與 sockaddr 一致// char sin_zero[8]; // 這通常用于保持結構體大小的一致性,但現代代碼通常不直接使用它
};
-
sin_family
:這是一個?sa_family_t
?類型的字段,用于指定地址族。對于 IPv4 地址,它應該被設置為?AF_INET
。 -
sin_port
:這是一個?uint16_t
?類型的字段,用于指定端口號。端口號應該以網絡字節序(大端模式)存儲,這通常意味著在將主機字節序(小端模式或大端模式,取決于具體的系統架構)的端口號傳遞給網絡之前,需要使用?htons()
?函數進行轉換。 -
sin_addr
:這是一個?struct in_addr
?類型的字段,它包含了一個 IPv4 地址。IPv4 地址也應該以網絡字節序存儲。struct in_addr
?通常定義為一個包含單個?uint32_t
?類型字段?s_addr
?的結構體,用于存儲 32 位的 IPv4 地址。 -
sin_zero
:在某些實現中,sockaddr_in
?結構體可能包含一個名為?sin_zero
?的字符數組字段,用于填充,以確保結構體的大小與更通用的?sockaddr
?結構體一致。然而,在現代的網絡編程實踐中,這個字段通常不被直接使用,而且可能在一些實現中根本不存在。如果你的系統定義中包含了這個字段,你通常不需要關心它,只需要確保在初始化?sockaddr_in
?結構體時將其清零(盡管這通常不是必需的,因為系統調用通常只關心?sin_family
,?sin_port
, 和?sin_addr
?字段)。
在使用?sockaddr_in
?結構體時,你需要確保正確地設置?sin_family
,?sin_port
, 和?sin_addr
?字段,并且如果?sin_zero
?字段存在,也最好將其清零(盡管這通常不是錯誤源)。然后,你可以將這個結構體的地址作為參數傳遞給網絡相關的系統調用。
?