天天看點

linux socket recv函數如何判斷收完一包_Linux網絡程式設計——端口複用(多個套接字綁定同一個端口)

實際上,預設的情況下,如果一個網絡應用程式的一個套接字 綁定了一個端口( 占用了 8000 ),這時候,别的套接字就無法使用這個端口( 8000 ), 驗證例子如下:

#include #include #include #include #include #include #include  int main(int argc, char *argv[]){int sockfd_one;int err_log;sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //建立UDP套接字oneif(sockfd_one < 0){perror("sockfd_one");exit(-1);} // 設定本地網絡資訊struct sockaddr_in my_addr;bzero(&my_addr, sizeof(my_addr));my_addr.sin_family = AF_INET;my_addr.sin_port = htons(8000);// 端口為8000my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 綁定,端口為8000err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));if(err_log != 0){perror("bind sockfd_one");close(sockfd_one);exit(-1);} int sockfd_two;sockfd_two = socket(AF_INET, SOCK_DGRAM, 0);  //建立UDP套接字twoif(sockfd_two < 0){perror("sockfd_two");exit(-1);} // 新套接字sockfd_two,繼續綁定8000端口,綁定失敗// 因為8000端口已被占用,預設情況下,端口沒有釋放,無法綁定err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));if(err_log != 0){perror("bind sockfd_two");close(sockfd_two);exit(-1);} close(sockfd_one);close(sockfd_two); return 0;}           

程式編譯運作後結果如下:

linux socket recv函數如何判斷收完一包_Linux網絡程式設計——端口複用(多個套接字綁定同一個端口)

那如何讓sockfd_one, sockfd_two兩個套接字都能成功綁定8000端口呢?這時候就需要要到端口複用了。端口複用允許在一個應用程式可以把 n 個套接字綁在一個端口上而不出錯。

設定socket的SO_REUSEADDR選項,即可實作端口複用:

int opt = 1;// sockfd為需要端口複用的套接字setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt));           

SO_REUSEADDR可以用在以下四種情況下。

  1. 當有一個有相同本地位址和端口的socket1處于TIME_WAIT狀态時,而你啟動的程式的socket2要占用該位址和端口,你的程式就要用到該選項。
  2. SO_REUSEADDR允許同一port上啟動同一伺服器的多個執行個體(多個程序)。但每個執行個體綁定的IP位址是不能相同的。在有多塊網卡或用IP Alias技術的機器可以測試這種情況。
  3. SO_REUSEADDR允許單個程序綁定相同的端口到多個socket上,但每個socket綁定的ip位址不同。這和2很相似,差別請看UNPv1。
  4. SO_REUSEADDR允許完全相同的位址和端口的重複綁定。但這隻用于UDP的多點傳播,不用于TCP。

需要注意的是,設定端口複用函數要在綁定之前調用,而且隻要綁定到同一個端口的所有套接字都得設定複用:

// sockfd_one, sockfd_two都要設定端口複用// 在sockfd_one綁定bind之前,設定其端口複用int opt = 1;setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, (const void *)&opt, sizeof(opt) );err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr)); // 在sockfd_two綁定bind之前,設定其端口複用opt = 1;setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR,(const void *)&opt, sizeof(opt) );err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));           

需要C/C++ Linux伺服器架構師學習資料私信“資料”(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享

linux socket recv函數如何判斷收完一包_Linux網絡程式設計——端口複用(多個套接字綁定同一個端口)

端口複用完整代碼如下:

#include #include #include #include #include #include #include  int main(int argc, char *argv[]){int sockfd_one;int err_log;sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //建立UDP套接字oneif(sockfd_one < 0){perror("sockfd_one");exit(-1);} // 設定本地網絡資訊struct sockaddr_in my_addr;bzero(&my_addr, sizeof(my_addr));my_addr.sin_family = AF_INET;my_addr.sin_port = htons(8000);// 端口為8000my_addr.sin_addr.s_addr = htonl(INADDR_ANY);// 在sockfd_one綁定bind之前,設定其端口複用int opt = 1;setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, (const void *)&opt, sizeof(opt) ); // 綁定,端口為8000err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));if(err_log != 0){perror("bind sockfd_one");close(sockfd_one);exit(-1);} int sockfd_two;sockfd_two = socket(AF_INET, SOCK_DGRAM, 0);  //建立UDP套接字twoif(sockfd_two < 0){perror("sockfd_two");exit(-1);} // 在sockfd_two綁定bind之前,設定其端口複用opt = 1;setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR, (const void *)&opt, sizeof(opt) );// 新套接字sockfd_two,繼續綁定8000端口,成功err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));if(err_log != 0){perror("bind sockfd_two");close(sockfd_two);exit(-1);} close(sockfd_one);close(sockfd_two); return 0;}           

端口複用允許在一個應用程式可以把 n 個套接字綁在一個端口上而不出錯。同時,這 n 個套接字發送資訊都正常,沒有問題。但是,這些套接字并不是所有都能讀取資訊,隻有最後一個套接字會正常接收資料。

下面,我們在之前的代碼上,添加兩個線程,分别負責接收sockfd_one,sockfd_two的資訊:

#include #include #include #include #include #include #include #include  // 線程1的回調函數void *recv_one(void *arg){printf("===========recv_one==============");int sockfd = (int )arg;while(1){int recv_len;char recv_buf[512] = "";struct sockaddr_in client_addr;char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16socklen_t cliaddr_len = sizeof(client_addr);recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);printf("ip:%s ,port:%d",cli_ip, ntohs(client_addr.sin_port));printf("sockfd_one =========== data(%d):%s",recv_len,recv_buf);} return NULL;} // 線程2的回調函數void *recv_two(void *arg){printf("+++++++++recv_two++++++++++++++");int sockfd = (int )arg;while(1){int recv_len;char recv_buf[512] = "";struct sockaddr_in client_addr;char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16socklen_t cliaddr_len = sizeof(client_addr);recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);printf("ip:%s ,port:%d",cli_ip, ntohs(client_addr.sin_port));printf("sockfd_two @@@@@@@@@@@@@@@ data(%d):%s",recv_len,recv_buf);} return NULL;} int main(int argc, char *argv[]){int err_log;/sockfd_oneint sockfd_one;sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //建立UDP套接字oneif(sockfd_one < 0){perror("sockfd_one");exit(-1);} // 設定本地網絡資訊struct sockaddr_in my_addr;bzero(&my_addr, sizeof(my_addr));my_addr.sin_family = AF_INET;my_addr.sin_port = htons(8000);// 端口為8000my_addr.sin_addr.s_addr = htonl(INADDR_ANY);// 在sockfd_one綁定bind之前,設定其端口複用int opt = 1;setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, (const void *)&opt, sizeof(opt) ); // 綁定,端口為8000err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));if(err_log != 0){perror("bind sockfd_one");close(sockfd_one);exit(-1);}//接收資訊線程1pthread_t tid_one;pthread_create(&tid_one, NULL, recv_one, (void *)sockfd_one);/sockfd_twoint sockfd_two;sockfd_two = socket(AF_INET, SOCK_DGRAM, 0);  //建立UDP套接字twoif(sockfd_two < 0){perror("sockfd_two");exit(-1);} // 在sockfd_two綁定bind之前,設定其端口複用opt = 1;setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR, (const void *)&opt, sizeof(opt) );// 新套接字sockfd_two,繼續綁定8000端口,成功err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));if(err_log != 0){perror("bind sockfd_two");close(sockfd_two);exit(-1);}//接收資訊線程2pthread_t tid_two;pthread_create(&tid_two, NULL, recv_two, (void *)sockfd_two);while(1){// 讓程式阻塞在這,不結束NULL;} close(sockfd_one);close(sockfd_two); return 0;}           

接着,通過網絡調試助手給這個伺服器發送資料,結果顯示,隻有最後一個套接字sockfd_two會正常接收資料:

linux socket recv函數如何判斷收完一包_Linux網絡程式設計——端口複用(多個套接字綁定同一個端口)

我們上面的用法,實際上沒有太大的意義。端口複用最常用的用途應該是防止伺服器重新開機時之前綁定的端口還未釋放或者程式突然退出而系統沒有釋放端口。這種情況下如果設定了端口複用,則新啟動的伺服器程序可以直接綁定端口。如果沒有設定端口複用,綁定會失敗,提示ADDR已經在使用中——那隻好等等再重試了,麻煩!

繼續閱讀