Linux网络编程中tcp_server和tcp_client函数的封装
2019獨角獸企業重金招聘Python工程師標準>>>
本文的主要目的是將server套接字和client套接字的獲取,做一個簡易的封裝,使用C語言完成。
?
tcp_server
?
服務器端fd的獲取主要分為以下幾步:
1.創建socket,這一步僅僅創建一個socket,沒有任何特性的屬性。
2.綁定網卡和port,一塊主機可能有多塊網卡,如果我們使用INADDR_ANY,意味著后面接受的TCP連接可以綁定在任意一塊網卡上。
例如某臺主機的ip地址有兩個:192.168.44.136、10.1.1.4,假設綁定的ip采用INADDR_ANY,端口采用9981,那么當接收一個TCP連接時,可能存在192.168.44.136:9981/10.1.1.4:9981/127.0.0.1:9981三種可能性。
如果我們綁定192.168.44.136,那么這個fd接收的連接都是在這個ip上。
有一處特殊的地方,如果主機綁定的是127.0.0.1,那么客戶端也必須是本機上的進程。(為什么?)
3.監聽端口listen,實際上從這一步開始接收TCP連接。listen函數的第二個參數表示TCP待接受隊列的大小,這個隊列存儲了已經完成三次握手,但還未被accept的連接。如果你設置為3,然后開啟4個client,但是server端不進行accept,通過netstat –an | grep 9981,可以查看到只有三條server端的連接處于ESTABLISHED狀態,其余的一個處于SYN_RCVD狀態。
所以,很多人誤以為accept完成三次握手,這是一種錯誤的想法,三次握手是由底層的協議棧完成的。
然后我們需要使用幾個套接字選項,后面會專門寫一篇文章總結套接字選項:
1.地址復用SO_REUSEADDR,代碼如下:
//地址復用 void set_reuseaddr(int sockfd, int optval) {int on = (optval != 0) ? 1 : 0;if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)ERR_EXIT("setsockopt SO_REUSEADDR"); }2.端口復用SO_REUSEPORT,有的機器不支持,所以需要判斷
void set_reuseport(int sockfd, int optval) { #ifdef SO_REUSEPORTint on = (optval != 0) ? 1 : 0;if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0)ERR_EXIT("setsockopt SO_REUSEPORT"); #elsefprintf(stderr, "SO_REUSEPORT is not supported.\n"); #endif //SO_REUSEPORT }3.禁用Nagle算法,降低局域網的延遲
//設置nagle算法是否可用 void set_tcpnodelay(int sockfd, int optval) {int on = (optval != 0) ? 1 : 0;if(setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == -1)ERR_EXIT("setsockopt TCP_NODELAY"); }4.禁用keepAlive機制
void set_keepalive(int sockfd, int optval) {int on = (optval != 0) ? 1 : 0;if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1)ERR_EXIT("setsockopt SO_KEEPALIVE"); }?
我們還需要為我們的tcp_server增加域名解析功能,我們可以綁定”localhost”之類的主機名,而不僅僅是ip地址,所以我們可能使用gethostbyname。
所以tcp_server代碼如下:
int tcp_server(const char *host, uint16_t port) {//處理SIGPIPE信號 handle_sigpipe();int listenfd = socket(PF_INET, SOCK_STREAM, 0);if(listenfd == -1)ERR_EXIT("socket");set_reuseaddr(listenfd, 1);set_reuseport(listenfd, 1);set_tcpnodelay(listenfd, 0);set_keepalive(listenfd, 0);SAI addr;memset(&addr, 0, sizeof addr);addr.sin_family = AF_INET;//addr.sin_addr.s_addr = inet_addr("127.0.0.1");addr.sin_port = htons(port);if(host == NULL){addr.sin_addr.s_addr = INADDR_ANY;}else{//int inet_aton(const char *cp, struct in_addr *inp);if(inet_aton(host, &addr.sin_addr) == 0){//DNS//struct hostent *gethostbyname(const char *name);struct hostent *hp = gethostbyname(host);if(hp == NULL)ERR_EXIT("gethostbyname");addr.sin_addr = *(struct in_addr*)hp->h_addr;}}if(bind(listenfd, (SA*)&addr, sizeof addr) == -1)ERR_EXIT("bind");if(listen(listenfd, SOMAXCONN) == -1)ERR_EXIT("listen");return listenfd; }?
tcp_client
?
客戶端也可以綁定port,只不過它不是必須的。代碼較簡單,如下:
int tcp_client(uint16_t port) {int peerfd = socket(PF_INET, SOCK_STREAM, 0);if(peerfd == -1)ERR_EXIT("socket");set_reuseaddr(peerfd, 1);set_reuseport(peerfd, 1);set_keepalive(peerfd, 0);set_tcpnodelay(peerfd, 0);//如果port為0,則不去綁定if(port == 0)return peerfd;SAI addr;memset(&addr, 0, sizeof addr);addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(get_local_ip());if(bind(peerfd, (SA*)&addr, sizeof(addr)) == -1)ERR_EXIT("bind client");return peerfd; }上面需要獲取本機的ip地址,代碼如下:
const char *get_local_ip() {static char ip[16];int sockfd;if((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1){ERR_EXIT("socket");}struct ifreq req;bzero(&req, sizeof(struct ifreq));strcpy(req.ifr_name, "eth0");if(ioctl(sockfd, SIOCGIFADDR, &req) == -1)ERR_EXIT("ioctl");struct sockaddr_in *host = (struct sockaddr_in*)&req.ifr_addr;strcpy(ip, inet_ntoa(host->sin_addr));close(sockfd);return ip; }?
完畢。
轉載于:https://my.oschina.net/inevermore/blog/388642
總結
以上是生活随笔為你收集整理的Linux网络编程中tcp_server和tcp_client函数的封装的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ionic - 先进的 HTML5 移动
- 下一篇: Java_io体系之RandomAcce