linux 的 C 庫路徑為 /usr/include,可以直接查看源碼,也可以通過 "man 頭文件名" 來學習,需要查看某個函數如 bind() ,則只需要 man 2 bind 即可。
如:<stdint.h> 定義了 int8_t、int16_t、int32_t、int64_t、uint8_t、uint16_t、uint32_t、uint64_t
詳見:/usr/include/stdint.h 或 man stdint.h
IPv4套接口地址結構:
struct sockaddr_in { short int sin_family; /* Address family 2個字節 */ unsigned short int sin_port; /* Port number 2個字節 */ struct in_addr sin_addr; /* Internet address 4個字節 */ unsigned char sin_zero[8]; /* Same size as struct sockaddr 8個字節,暫不使用,一般設為0*/ };
sim_family 決定地址家族,IPv4 必須設置為 AF_INET,其它還有 AF_INET6 (IPv6協議)、AF_ROUTE(路由套接口)
in_addr 這個結構體里只含有一個成員:
struct in_addr { uint32_t s_addr; };
sin_addr.s_addr 使用網絡字節序,可以使用 inet_addr 方法將字符串格式 IP 轉換成網絡字節序。
通用套接口地址結構:
struct sockaddr { unsigned short sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */ };
connect、 bind、 accept 方法使用的都是通用套接口地址結構,需要把 sockaddr_in 結構強轉為 sockaddr 即可。
不同的主機可能有不同的字節序,其中x86 為小端字節序。網絡字節序規定為大端字節序。
#include <stdio.h> #include <arpa/inet.h> int main() { unsigned int x = 0x12345678; unsigned char *p = (unsigned char*)&x; printf("%0x %0x %0x %0x\n",p[0],p[1],p[2],p[3]); unsigned y = htonl(x); p = (unsigned char*)&y; printf("%0x %0x %0x %0x\n",p[0],p[1],p[2],p[3]); }
輸出結果為:
字節序轉換函數:
#include <arpa/inet.h>
uint32_t htonl(unit32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint32_t ntohs(uint16_t netshort);
地址轉換函數:
#include <netinet/in.h> #include <arpa/inet.h> in_addr_t inet_addr(const char *cp); //點分十進制轉換成32位整型地址 int inet_aton(const char *cp, struct in_addr *inp); //同上 char *inet_ntoa(struct in_addr in); //32位整型地址轉換成點分十進制地址
套接字類型:
SOCK_STREAM 流式套接字
SOCK_DGRAM 數據報套接字
SOCK_RAW 原始套接字
int socket(int domain, int type, int protocol);
如:socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
也可以寫成:socket(AF_INET, SOCK_STREAM, 0); //第三個參數為0,表示由系統自動選擇協議,而 AF_INET 和 SOCK_STREAM 組合一定是 IPPROTO_TCP 協議。
指定本機地址:
server.sin_addr.s_addr = htonl(INADDR_ANY); //推薦 server.sin_addr.s_addr = inet_addr("127.0.0.1"); inet_aton("127.0.0.1",&server.sin_addr);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
在綁定之前,盡量使用 setsockopt() 來設置 SO_REUSEADDR 套接字選項,可以使得不必等待 TIME_WAIT 狀態消失就可以重啟服務器。如:
int on = 1; setsockopt(sockfd,SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
level:(級別): 指定選項代碼的類型。
SOL_SOCKET: 基本套接口
IPPROTO_IP: IPv4套接口
IPPROTO_IPV6: IPv6套接口
IPPROTO_TCP: TCP套接口
optname(選項名): 選項名稱
optval(選項值): 是一個指向變量的指針 類型:整形,套接口結構, 其他結構類型:linger{}, timeval{ }
optlen(選項長度) :optval 的大小
返回值:標志打開或關閉某個特征的二進制選項
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
int listen(int sockfd, int backlog);
第二個參數表示監聽隊列最大連接數(包括未完成連接隊列和已完成連接隊列的大小之和),可以使用 SOMAXCONN 這個宏。
int accept(int listenfd, struct sockaddr* client, socklen_t *addlen);
client 和 addlen 是用來返回客戶端的套接字地址結構和對應的結構長度的。如果不關心這兩個值,可以使用 accept(int sockfd, NULL, NULL);
否則為:
sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); //這時必須要有初始值,否則 accept() 失敗 accept(sockfd, (sockaddr*)&client_addr, &client_addr_len));
使用 socket() 創建一個套接字時,默認情況下它是一個主動套接字,即將調用 connect() 發起連接的客戶端套接字,對於服務器,必須調用 listen() 函數,將這個未連接的套接字轉換成被動套接字,即監聽套接字,負責接受每個客戶的連接請求。一個服務器常常只有一個監聽套接字,而且會一直存在,直到服務器關閉。accept() 函數可以返回已連接套接字描述符,已連接套接字是內核為每個被接受的客戶端分別創建的,負責與對應的客戶端進行數據傳輸。當服務器完成與該客戶端的數據傳輸時,需要關閉該已連接描述符。
數據傳輸函數:
因為套接字描述符也是一種文件描述符,所以可以使用文件讀寫函數 read() 和 write():
#include <unistd.h>
int write(int sockfd, char *buf, int len); //出錯返回 -1,成功返回大於0的整數,為發送的字節數
int read(int sockfd, char *buf, int len); //出錯返回 -1,成功返回大於0的整數,為接收的字節數
此外,還有 TCP套接字提供的 send() 和 recv() 函數:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void* buf, size_t len, int flags); //前三個參數同 write() ,第四個參數為傳輸控制標志,一般設為0,也可以為其它值如:MSG_OOB 等
ssize_t recv(int sockfd, void *buf, size_t len, int flags); //前三個參數同 read() ,第四個參數一般設為0,也可以為其它值如:MSG_PEEK 等
關閉套接字:
#include <unistd.h>
close(int sockfd);
UDP的數據傳輸函數:
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr *to, int addrlen);
ssize_t recvfrom(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr *from, int addrlen);
前四個參數完全等同 TCP 的 send 和 recv 方法,第五和第六個參數類似於 accept() 的后兩個參數。這兩個函數,也可以用於TCP協議,但是一般不這么使用。
UDP的套接字不需要監聽,只要 bind() 需要監聽的端口就可以接收數據了。
exit() 退出當前進程。
父進程退出,程序結束,但子進程卻沒有退出,可以通過 "px aux | grep 二進制文件名" 來查看。所以需要通過信號通知的方式,來通知子進程退出。
sysconf(_SC_NPROCESSORS_CONF) 獲取服務器處理器數目(核心數)
關於下面的寫法,見於:http://www.spongeliu.com/415.html
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)