C/C++端口復用SO_REUSEADDR(setsockopt參數),test ok

端口復用最常用的用途應該是防止服務器重啟時之前綁定的端口還未釋放或者程序突然退出而系統沒有釋放端口。這種情況下如果設定了端口復用,則新啟動的服務器進程可以直接綁定端口。如果沒有設定端口復用,綁定會失敗,提示ADDR已經在使用中——那只好等等再重試了,麻煩!

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>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;
}
/*
ser@DESKTOP-MF4BDTB:/mnt/e/test_code$ g++ SO_REUSEADDR.cpp -o SO_REUSEADDR.O
user@DESKTOP-MF4BDTB:/mnt/e/test_code$ ./SO_REUSEADDR.O
bind sockfd_two: Address already in use
user@DESKTOP-MF4BDTB:/mnt/e/test_code$
*/


程序編譯運行后結果如下:


那如何讓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可以用在以下四種情況下。 (摘自《Unix網絡編程》卷一,即UNPv1)

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


端口復用完整代碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>// 線程1的回調函數
void *recv_one(void *arg)
{printf("===========recv_one==============\n");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("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port));printf("sockfd_one =========== data(%d):%s\n",recv_len,recv_buf);}return NULL;
}// 線程2的回調函數
void *recv_two(void *arg)
{printf("+++++++++recv_two++++++++++++++\n");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("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port));printf("sockfd_two @@@@@@@@@@@@@@@ data(%d):%s\n",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會正常接收數據:

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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/208650.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/208650.shtml
英文地址,請注明出處:http://en.pswp.cn/news/208650.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

前端學習--React(5)

一、useReducer 管理相對復雜的狀態數據 定義一個reducer函數&#xff0c;根據action值的不同返回不同的狀態 在組件中調用useReducer并傳入reducer函數和狀態的初始值 事件發生時&#xff0c;通過dispatch函數分派一個對象&#xff0c;即通知reducer具體返回哪個狀態對應的操…

STM32 寄存器配置筆記——USART DMA發送

一、DMA介紹 直接存儲器存取(DMA)用來提供在外設和存儲器之間或者存儲器和存儲器之間的高速數據傳 輸。無須 CPU 干預&#xff0c;數據可以通過 DMA 快速地移動&#xff0c;這就節省了 CPU 的資源來做其他操作。當產品對于時序要求較嚴格時&#xff0c;外設使用DMA的方式能夠減…

深入了解Java 8日期時間新玩法:DateTimeFormatter與ZoneOffset的使用

推薦語 在這篇文章中&#xff0c;我們將深入探討Java中的DateTimeFormatter和ZoneOffset類的功能和使用方法。這些類是在Java 8中引入的新的日期時間API的一部分&#xff0c;它們為我們提供了更靈活、更易用的日期和時間處理能力。盡管這些類在Java 8中已經出現&#xff0c;但…

ELK(六)—Filebeat安裝部署

目錄 一、介紹1.1特點1.2使用原因1.3結構圖1.4工作流程 二、安裝部署2.1下載2.2啟動2.3監控日志文件2.4自定義字段 三、連接Elasticsearch四、工作原理 一、介紹 Filebeat是一個輕量級的日志和文件數據收集器&#xff0c;屬于Elastic Stack&#xff08;ELK Stack&#xff09;中…

近期Chrome瀏覽器 不知哪個版本升級后原先http強制跳轉到https,導致服務端302強制跳轉到http也沒反應

關于Chrome更新http強制跳轉到https解決方法 近期Chrome瀏覽器 不知哪個版本升級后原先http強制跳轉到https&#xff0c;導致服務端302強制跳轉到http也沒反應一、F12檢查加載的Response Headers中有沒有Non-Authoritative-Reason二、找了資料后得到解決方案&#xff1a;三、找…

云原生數據庫是什么?它的作用是啥?

目前來說&#xff0c;各廠商的云原生數據庫在演進路線上分成了兩個略有不同的路徑來解決不同的問題。 一種是各大公有云廠商選擇的&#xff0c;優先保證上云兼容性的路線&#xff0c;就是基于存算分離架構對傳統數據庫進行改造的路線&#xff1a;通過把大量的日志操作放到后臺…

插入排序——直接插入排序和希爾排序(C語言實現)

文章目錄 前言直接插入排序基本思想特性總結代碼實現 希爾排序算法思想特性總結代碼實現 前言 本博客插入排序動圖和希爾排序視頻參考大佬java技術愛好者&#xff0c;如有侵權&#xff0c;請聯系刪除。 直接插入排序 基本思想 直接插入排序是一種簡單的插入排序法&#xff…

圖空圖床圖片外鏈系統源碼-支持自定義權限策略-圖片大小格式

含視頻搭建教程。 大致功能&#xff1a; 支持本地等多種第三方云儲存 AWS S3、阿里云 OSS、騰訊云 COS、七牛云、又拍云、SFTP、FTP、WebDav、Minio多種數據庫驅動支持&#xff0c;MySQL 5.7、PostgreSQL 9.6、SQLite 3.8.8、SQL Server 2017支持配置使用多種緩存驅動&#xff…

車聯網軟件定義汽車安全攻擊示例

目錄 導言 名詞解釋 TBox QNX介紹 ADB 威脅分析

Flameshot的安裝、配置及使用

概要&#xff1a;本篇主要介紹在Ubuntu22.04環境下&#xff0c;截圖軟件Flameshot的安裝、配置及使用。 一、安裝 推薦命令行安裝 sudo apt install flameshot 二、修改gdm3配置文件 這一步是為了解決截圖時沒有光標的問題&#xff0c;解決方法我是從這里學到的解決flam…

【hugging face】bitsandbytes中8 bit量化的理解

8 位量化使數十億參數規模的模型能夠適應更小的硬件&#xff0c;而不會降低性能。 8 位量化的工作原理如下&#xff1a; 1.從輸入隱藏狀態中按列提取較大值&#xff08;離群值&#xff09;。 2.對 FP16 中的離群值和 int8 中的非離群值執行矩陣乘法。 3.改變非異常值結果以將值…

unity中:搭建在線AR應用

使用Imagine WebAR - Image Tracker插件部署WebGL應用 在使用Imagine WebAR - Image Tracker插件進行WebGL應用開發時&#xff0c;有兩個關鍵知識點需要掌握&#xff1a; 1. 部署到支持HTTPS的服務器 由于WebGL應用需要訪問用戶的攝像頭&#xff0c;因此必須在支持HTTPS的服…

微前端 模塊聯邦技術

目錄 介紹 基本使用 演示用法 初始化配置文件 remote 項目 host 項目 為什么講這個呢&#xff0c;很多人覺得他不是微前端&#xff0c;也有人定義它也是微前端&#xff0c;看怎么理解了&#xff0c;我覺得他是一個去中心化技術&#xff0c;它可以讓多個獨立構建的應用…

【力扣100】9.和為k的子數組

添加鏈接描述 class Solution:def subarraySum(self, nums: List[int], k: int) -> int:# 思路是從第一個元素開始遍歷&#xff0c;加到爆&#xff0c;就把指針向前移一位result0for i in range(len(nums)):# 如果爆了&#xff0c;就向后移一位if i!len(nums)-1:ji1sumnums…

高并發爬蟲用Python語言適合嗎?

不管你用什么語言沒在進行高并發前&#xff0c;有幾點是需要考慮清楚的&#xff0c;&#xff1b;例如&#xff1a;數據集大小&#xff0c;算法、是否有時間和性能方面的制約&#xff0c;是否存在共享狀態&#xff0c;如何調試&#xff08;這里指的是日志、跟蹤策略&#xff09;…

C#云LIS系統源碼 B/S架構,SaaS模式,可擴展性強

基于B/S架構的云LIS檢驗系統源碼&#xff0c;整個系統的運行基于WEB層面&#xff0c;只需要在對應的工作臺安裝一個瀏覽器軟件有外網即可訪問。全套系統采用云部署模式&#xff0c;部署一套可支持多家醫院檢驗科共同使用。 采用.Net Core新的技術框架、DEV報表、前端js封裝、分…

騰訊云CentOS8 jenkins war安裝jenkins步驟文檔

騰訊云CentOS8 jenkins war安裝jenkins步驟文檔 一、安裝jdk 1.1 上傳jdk-11.0.20_linux-x64_bin.tar.gz 1.2 解壓jdk安裝包文件 tar -zxvf jdk*.tar.gz 1.3 在/usr/local 目錄下創建java目錄 cd /usr/local mkdir java 1.4 切到java目錄&#xff0c;把jdk解壓文件改名為jd…

【抽象策略模式】實踐

前言 剛果商城&#xff0c;用戶登錄 Or 注冊 發送郵箱驗證碼場景&#xff0c;使用抽象策略模式實現 什么是抽象策略模式 抽象策略模式是一種行為型設計模式&#xff0c;它允許定義一系列算法&#xff0c;將每個算法封裝起來&#xff0c;并使它們可以互相替換。這使得客戶端代碼…

Java_LinkedList鏈表詳解

目錄 前言 ArrayList的缺陷 鏈表 鏈表的概念及結構 鏈表的種類 1.單向或雙向 2.帶頭或不帶頭 3.循環或不循環 LinkedList的使用 什么是LinkedList LinkedList的使用 LinkedList的構造 LinkedList的其他常用方法介紹 LinkedList的遍歷 ArrayList和LinkedList的…

OpenCL學習筆記(四)手動編譯開發庫(ubuntu+gcc+rk3588)

前言 筆者本次使用的是RK3588的開發板&#xff0c;內部燒寫的是ubuntu20.04&#xff0c;gcc版本是9 本文檔簡單記錄下編譯的過程&#xff0c;有需要的小伙伴可以參考下 一、安裝所需軟件 1.安裝git&#xff0c;教程比較多&#xff0c;不再重復 2.安裝cmake&#xff0c;教程…