1. 程式人生 > >Linux網路程式設計——埠複用(多個套接字繫結同一個埠)

Linux網路程式設計——埠複用(多個套接字繫結同一個埠)

實際上,預設的情況下,如果一個網路應用程式的一個套接字 綁定了一個埠( 佔用了 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套接字one
	if(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);		// 埠為8000
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	// 繫結,埠為8000
	err_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套接字two
	if(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;
}

程式編譯執行後結果如下:


那如何讓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套接字one
	if(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);		// 埠為8000
	my_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) );

	// 繫結,埠為8000
	err_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套接字two
	if(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=16
		socklen_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=16
		socklen_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_one
	int sockfd_one;
	sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //建立UDP套接字one
	if(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);		// 埠為8000
	my_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) );

	// 繫結,埠為8000
	err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));
	if(err_log != 0)
	{
		perror("bind sockfd_one");
		close(sockfd_one);		
		exit(-1);
	}
	
	//接收資訊執行緒1
	pthread_t tid_one;
	pthread_create(&tid_one, NULL, recv_one, (void *)sockfd_one);
	
	/////////////////////////sockfd_two
	int sockfd_two;
	sockfd_two = socket(AF_INET, SOCK_DGRAM, 0);  //建立UDP套接字two
	if(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);
	}
	//接收資訊執行緒2
	pthread_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已經在使用中——那隻好等等再重試了,麻煩!

相關推薦

Linux網路程式設計》: 同一個

在《繫結( bind )埠需要注意的問題》提到:一個網路應用程式只能繫結一個埠( 一個套接字只能繫結一個埠 )。 請檢視《Linux網路程式設計》: 繫結( bind )埠需要注意的問題 實際上,預設的情況下,如果一個網路應用程式的一個套接字 綁定了一個埠( 佔用了 80

Linux網路程式設計——同一個

實際上,預設的情況下,如果一個網路應用程式的一個套接字 綁定了一個埠( 佔用了 8000 ),這時候,別的套接字就無法使用這個埠( 8000 ), 驗證例子如下: #include <stdio.h> #include <stdlib.h> #

accept()返回的哪個 新舊的聯絡

摘要:對於伺服器程式設計中最重要的一步等待並接受客戶的連線,那麼這一步在程式設計中如何完成,accept函式就是完成這一步的。它從核心中取出已經建立的客戶連線,然後把這個已經建立的連線返回給使用者程式,此時使用者程式就可以與自己的客戶進行點到點的通訊了。 accept函

query事件整理事件可以同時到一個元素中

方法 描述 向匹配元素附加一個或更多事件處理器 觸發、或將函式繫結到指定元素的 blur 事件 觸發、或將函式繫結到指定元素的 change 事件 觸發、或將函式繫結到指定元素的 click 事件 觸發、或將函式繫結到指定元素的 double cli

TCP/IP網路程式設計 學習筆記_8 --優雅地斷開連線

基於TCP的半關閉 TCP中的斷開連線過程比建立連線過程更重要,因為建立連線過程一般不會出現什麼大的變數,但斷開過程就有可能發生預想不到的情況,因此要準確的掌控。 單方面斷開連線帶來的問題 Linux的close函式和Windows的closesocke

Linux網路程式設計——tcp併發伺服器I/O之select

與多執行緒、多程序相比,I/O複用最大的優勢是系統開銷小,系統不需要建立新的程序或者執行緒,也不必維護這些執行緒和程序。 程式碼示例: #include <stdio.h> #include <unistd.h> #include <

java網路程式設計(二)Socket連線以及使用執行緒完成客戶端的連線

在前面的示例中,客戶端中建立了一次連線,只發送一次資料就關閉了,這就相當於撥打電話時,電話打通了只對話一次就關閉了,其實更加常用的應該是撥通一次電話以後多次對話,這就是複用客戶端連線。 那 麼如何實現建立一次連線,進行多次資料交換呢?其實很簡單,建立連線以後,

linux網路程式設計練習---聊天室TCP/IP實現

為了更深刻的鍛鍊認識TCP/IP協議,加強自己對Linux系統的網路程式設計部分的編寫程式碼能力,編寫基於控制檯的聊天視窗,用本機既當伺服器又當客戶端,先開啟一個shell,執行伺服器程式,然後再開啟一個shell視窗,執行客戶端程式,顯示連線成功,開始聊天吧。不知道為什麼

Linux網路程式設計——tcp併發伺服器poll實現

#include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/select.h> #include <sys/time.

JAVA SOCKET網路程式設計,服務端接收客戶端連線的實現

這是基於前面一篇文章的一個簡單擴充套件,當然這不是最好的實現 服務端如果要想接收多個客戶端連線,必須死迴圈去接收新的客戶端的連線請求,一個完整的客戶端服務端socket連線通過一個執行緒來維護 package com.tree.demo.socket; import

網路程式設計:服務端處理客戶端----執行緒實現、建立執行緒特有資料.

重點集中在用多執行緒實現,建立執行緒特有資料,不會發生資料寫入衝突。實現的功能很簡單,客戶端連線成功後,輸入一個整數,服務端返回它的二進位制形式。客戶端輸入0,則主動退出。三個檔案: duoxianc.c ,主檔案binarykey.c,執行緒執行函式及特有資料建立clien

父元件傳給元件父元件傳給一個子元件props——元件通訊

註冊介面:手機、個人詳情、密碼共用一個介面的頂部時:子元件應該是重複最多的介面。 子元件(register_title):在script中,加上 props: ['step'],{{step}}(可選是否顯示) 父元件1: 1、匯入子元件: import RegisterTitl

[SPOJ] (1812) Longest Common Substring II ---- SAM串的最長公共子串

題目傳送門 做法: 類似求兩個串的最長公共子串。 我們對第一個串建立自動機,然後把剩餘的n-1個串放進自動機上匹配。 每個串都儲存它們在每個狀態上的匹配的最大長度ml, 然後對於每個狀態,維護一個數組

字尾自動機穿的最長公共子串spoj1812

SPOJ Problem Set (classical) 1812. Longest Common Substring II Problem code: LCS2 A string is finite sequence of characters over a non-

ASP.NET MVC Identity 兩符串問題解決一例

fail conn init led user asp identity 字符串 initial 按照ASP.NET MVC Identity建立了一個用戶權限管理模塊,由於還要加自己已有的數據庫,所以建立了一個實體模型,建立了之後,發現登錄不了: 一直顯示“Login i

網路是怎樣連線的-第二章-建立

2.1 建立套接字 2.1.1 協議棧的內部結構 協議棧的內部如圖 2.1 所示,分為幾個部分,分別承擔不同的功能。 這張圖中的上下關係是有一定規則的,上面的部分會向下面的部分委派工作,下面的部分接受委派的工作並實際執行。 上下關係只是一個總體的規則,其中也有一部分上下關係不明確,或者上下關係相反的情

34.非阻塞丶異常處理丶掃描

非阻塞及套接字異常處理:   套接字異常捕獲: 套接字建立失敗,8000  socket.error  客戶端連線錯誤:

程序筆記2:程序之間的通訊UNIX域socket

socket的地址資料結構根據不同的系統以及網路環境有不同形式。為了使不同格式地址能夠被傳入套接字函式,必須強制將地址結構轉換為: struct sockaddr{ sa_family_t sa_family; /* address family*/ char

選項分析

一、IP_ADD_MEMBERSHIP、IPV6_JOIN_GROUP和MCAST_JOIN_GROUP 在一個指定的本地介面上面新增一個不限源的多播組。我們將會有以下三個結構來表示新增和離開多播組 struct ip_mreq{ struct in_addr imr_

spring mvc bean,或一個bean物件的資料

一、前臺傳遞不同類不同物件 1、屬性名不同,可直接封裝進controller方法的物件引數(經驗證) 2、屬性名有重複,可在重複的類中設定一個值型別,後臺再去將值型別值賦值給例項變數(經驗證) 二、同一類多個物件集合 方法1、Json方式 方法2、新建一個類,該