1. 程式人生 > >網路程式設計(4)select函式實現I/O多路複用伺服器

網路程式設計(4)select函式實現I/O多路複用伺服器

    我按理解整了個基於select模式的單程序多路複用併發伺服器,並寫了個簡單的測試程式測了下,雖然離實用還差得遠,但用來練習select夠用了。

至於如何實現的細節,程式碼註釋算比較清楚,就不多弄了。

一。伺服器部份

單程序併發伺服器程式碼:

/*************************************************
Author: xiongchuanliang
Description: I/O複用(非同步阻塞)模式_單程序+select模式伺服器
編譯命令:
Linux:
g++ -g -o tcpserverasynselect2 tcpserverasynselect2.cpp -m64 -I./common
./tcpserverasynselect2
**************************************************/

// 客戶端程式碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "initsock.h"
#include "common.h"

#include <time.h>

//客戶端Socket資訊結構體
typedef struct _client_sock{        
	int    fd;					//客戶端socket描述符
	struct sockaddr_in addr;    //客戶端地址資訊結構體    
	time_t lastseconds;			//可依這個計算空閒時間,空閒太長的連線可以關閉。
} client_sock; 

CInitSock initSock;

//#define IDLE_MAXTIME xxx  //最長空閒時長 DEMO忽略
//#define SELECT_MAXWAITTIME xxxxx
#define NET_TIMEOUT 5000  //傳送超時時限 5s

int main(int argc, char* argv[])
{	
	//fd_set 是通過bit位來存放檔案描述符,可通過sizeof(fd_set) * 8 
	//來得可支援的最大檔案描述符數,但受系統限制,基本達不到
	 fd_set readset;		//select()函式 readset		 
	 int nSelectMaxfd = 0;	//select() maxfdp引數
	 int nSelectRet = 0;	//select() 返回值
	 //int nCheckTimeval = 5;	//輪詢間隔

	 SOCKET sListen,sClient,recvSockfd;
	 client_sock arrClientSock[FD_SETSIZE]; //存放需要select()監控的fd. 
	 int arrClientSockConnAmt = 0;			//實際監控fd數
	 	
	 socklen_t nAddrlen = sizeof(struct sockaddr_in); 		
	 time_t tCurrSysTime;
	 char recvData[MAXDATASIZE]={0};
	 int i = 0 ;

	//建立套接字
	sListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(sListen == INVALID_SOCKET)
	{
		PrintError("socket() failed.\n");
		exit(EXIT_FAILURE);
	}

	//bind() 地址可立即重用
	int nResAddr = 1;
	setsockopt( sListen, SOL_SOCKET, SO_REUSEADDR, (const char*)&nResAddr, sizeof(nResAddr) );

	int nNetTimeout = NET_TIMEOUT;
	//設定傳送超時時限
	setsockopt(sListen,SOL_SOCKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int) );
	//設定接收超時時限
	setsockopt(sListen,SOL_SOCKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));

	//繫結本地IP和埠到套接字
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(SERVPORT); //大於1024且小於65535
	server_addr.sin_addr.s_addr = INADDR_ANY;
	bzero(&(server_addr.sin_zero),8);	

	if(bind(sListen,(struct sockaddr *)&server_addr,sizeof(struct sockaddr)) == SOCKET_ERROR)
    {
		PrintError("bind() failed.");
		exit(EXIT_FAILURE);
    }

    //開始監聽
	// listen(套接字,監聽佇列中允許保持的尚未處理的最大連線數量)	
	// listen僅用在支援連線的套接字上,如SOCK_STREAM型別的套接字
	// 如果連線數超過BACKLOG,客戶端將收到WSAECONNREFUSED錯誤
    if(listen(sListen, BACKLOG) == SOCKET_ERROR) //FD_SETSIZE
    {  
		PrintError("sListen() failed.");
        exit(EXIT_FAILURE);
    }

	//初始化	
	for(int i=0;i<FD_SETSIZE;i++){
		arrClientSock[i].fd = -1;
	}
    nSelectMaxfd = sListen;	 //設定select()函式maxfdp引數	

	//迴圈接收資料
	while(true)
	{	
		struct sockaddr_in remoteAddr;
		tCurrSysTime = time(NULL); //系統當前時間

		//重建fd_set集合
		FD_ZERO(&readset); //每次迴圈須重新初始化,否則select不能檢測描述符變化

		//將陣列中的fd清理並賦給readset
		arrClientSockConnAmt = 0;
		FD_SET(sListen,&readset); //將socket描述符加入檢測集合 
		nSelectMaxfd = sListen;	  //設定select()函式maxfdp引數

		for(i=0;i< FD_SETSIZE;i++)
		{
			if(arrClientSock[i].fd > 0) //從描述符陣列中找到一個還沒用的儲存進去
			{
				//對於空閒時間太長的,可能客戶端已非常規的斷開如斷網,停電之類,將其關閉並從陣列中刪除,DEMO省略				
				/*if( tCurrSysTime - arrClientSock[i].lastseconds  > IDLE_MAXTIME)
				{
					close(arrClientSock[i].fd);
					arrClientSock[i].fd = -1;
					arrClientSock[i].lastseconds  = 0;				
					memset(&arrClientSock[i].addr,0,sizeof(struct sockaddr_in));
				}else{*/

					FD_SET(arrClientSock[i].fd,&readset);
					arrClientSockConnAmt ++;
					//maxfdp
					if( arrClientSock[i].fd > nSelectMaxfd){
						nSelectMaxfd = arrClientSock[i].fd ;
					}

				//}				
			} // end if > 0
		}			
		
		//呼叫select	
		//超時則返回0,否則返回發生事件的檔案描述符的個數
		nSelectRet = select(nSelectMaxfd+1,&readset,NULL,NULL,NULL);	 //設定為阻塞狀態
		//struct sockaddr_in remoteAddr;			
		//struct timeval timeout={nCheckTimeval,0}; //阻塞式select, 超時時間. timeval{一個是秒數,另一個是毫秒數}
		//nSelectRet = select(nSelectMaxfd+1,&readset,NULL,NULL,&timeout); //設定select在超時時間內阻塞
		
		if( FD_ISSET(sListen,&readset) )
		{
			printf("select() 返回值 = %d. \n",nSelectRet );
			printf("accept() 連線客戶端.\n");
			//呼叫accept,連線一個客戶端
			sClient = accept(sListen,(struct sockaddr *)&remoteAddr,(socklen_t *)&nAddrlen);
			if( sClient <= 0) // == INVALID_SOCKET) //-1
			{
				PrintError("accept() failed.");
				continue;
			}

			//描述符陣列已滿
			if( arrClientSockConnAmt + 1 > FD_SETSIZE )
			{
				printf("ERROR: 等待連線的客戶端太多!超出處理能力。\n");
				continue;
			}

			//將連線上的客戶端放入陣列,
			//後續可以再寫個for,檢查已正常close的並把空閒太長的close掉,
			//把arrClientSockConnAmt設為實際值,並注意設定nSelectMaxfd的值
			for(i=0;i< FD_SETSIZE;i++)
			{
				if(arrClientSock[i].fd < 0) //從描述符陣列中找到一個還沒用的儲存進去
				{
					arrClientSock[i].fd = sClient;				
					arrClientSock[i].addr = remoteAddr;	
					arrClientSock[i].lastseconds = time(NULL);
					printf("連線上的客戶端IP = %s. \n",inet_ntoa(arrClientSock[i].addr.sin_addr) );
					arrClientSockConnAmt ++;
					//maxfdp
					if( sClient > nSelectMaxfd){
						nSelectMaxfd = sClient;
					}
					break;
				}
			}		
			
			//如果select()檢測到多個檔案描述符併發時,則繼續while,生成新的socket放入陣列
			nSelectRet -= 1; 
			if(nSelectRet <= 0){	
				continue;	//如果沒有新客戶端連線,則繼續迴圈
			}
		} //end if( FD_ISSET(sListen,&readset) )

		//把select()函式返回的有發生事件的Socket描述符儲存完後,統一在這做響應處理
		for(i = 0;i<arrClientSockConnAmt; i++)
		{
			 //如果客戶端描述符小於0,則沒有連線 
			if( arrClientSock[i].fd < 0){					
				continue;
			}				
			recvSockfd = arrClientSock[i].fd;

			if( FD_ISSET(recvSockfd,&readset) ) //檢查可讀
			{										
				//接收資料
				memset(recvData,0,sizeof(recvData)); //重新清空緩衝區
				printf("recv() fd[%d].\n",i);
				int recvbytes = recv(recvSockfd, recvData, MAXDATASIZE, 0);    		
				if( recvbytes == 0)
				{
					printf("recv() no data!\n");	
					close(recvSockfd);
					FD_CLR(recvSockfd,&readset);
					arrClientSock[i].fd=-1;
					arrClientSockConnAmt --;
					printf("close() \n");					
				}else if( recvbytes < 0){
					PrintError("recv() failed");
					close(recvSockfd);
					FD_CLR(recvSockfd,&readset);
					arrClientSock[i].fd=-1;
					arrClientSockConnAmt --;
					printf("close() \n");	
						;						
					//exit(EXIT_FAILURE); //刷屏
				}else if(recvbytes > 0){            
					recvData[recvbytes]='\0';
					printf("收到資訊:%s\n",recvData);

					//傳送資料到客戶端
					char sendData[500] ={0};
					strcpy(sendData,"Hello client!\n");
					strcat(sendData,recvData);
					send(recvSockfd, sendData, strlen(sendData), 0);

					//更新一下fd最後響應時間
					arrClientSock[i].lastseconds = time(NULL);

					//如果沒有新客戶端連線,則break for
					if( (--nSelectRet) <= 0){
						break;	
					}
				} //end if recv

			} //end if( FD_ISSET(recvSockfd,&readset) )					
		} //end for
			
	} //end while(1)
	//關閉監聽套接字
	close(sListen);

	exit(EXIT_SUCCESS);
}

二。測試 部份

用於測試的程式碼:

/*************************************************
Author: xiongchuanliang
Description: 通過在不同機器或會話視窗執行測試程式,生成多個執行緒連線Socket伺服器來完成測試
編譯命令:
Linux:
g++ -o testthread2 testthread2.cpp -m64 -I./common -lpthread

./testthread2
**************************************************/

// 客戶端程式碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "initsock.h"
#include "common.h"

#include <pthread.h>
#include <string.h>

#include <sys/stat.h>

//指定要連線的伺服器ip
#define SERVIP	"127.0.0.1"  

#define MAX_THREAD 50  //生成執行緒數

CInitSock initSock;

void *TestSocket(void *p); //連線伺服器

int main(int argc, char* argv[])
{
	pthread_t tpid[MAX_THREAD];
	for(int i=0;i< MAX_THREAD - 1;i++)
	{		
		if( pthread_create(&tpid[i],NULL,&TestSocket,&i) != 0 ) 
		 {
			 fprintf(stderr,"Create Thread[%d] Error:%s\n",i,strerror(errno));
			 exit(EXIT_FAILURE);
		 }
		 //pthread_join(tpid[i],NULL);
	}
	
	sleep(10);	
	exit(EXIT_SUCCESS);
}


void *TestSocket(void *p)
{
	int ti = *((int *)p);
	
	pid_t pid;
	pid = getpid();

	pthread_t tid;
	tid = pthread_self();

	time_t ttm = time(NULL);

	 char testMsg[100] = {0};
	 snprintf(testMsg,100,"thread id=%lu pid=%u ttm=%d \n",tid, (unsigned int)pid,ttm); 

	//建立套接字
	SOCKET sclient = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(sclient == INVALID_SOCKET)
	{
		PrintError("invalid() failed");
		exit(EXIT_FAILURE);
	}

	//指定要連線的伺服器地址和埠
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVPORT);
	server_addr.sin_addr.s_addr =inet_addr(SERVIP);		
	memset(&(server_addr.sin_zero),0,8);

	//將套接字連線上伺服器
	if( connect(sclient,(struct sockaddr *)&server_addr,sizeof(struct sockaddr) ) == SOCKET_ERROR)
	{
		PrintError("connect() failed");
		exit(EXIT_FAILURE);
	}
	
	//傳送資料到服務端
	send(sclient,testMsg,strlen(testMsg),0);
	
	//接收返回的資料
	char recvData[MAXDATASIZE] = {0};
	int recvbytes = recv(sclient,recvData,MAXDATASIZE,0);
	if( recvbytes == 0)
	{
		printf("thread id=%lu recv() no data!\n",tid);
	}else if( recvbytes < 0)
	{
		PrintError("recv() failed");
		exit(EXIT_FAILURE);
	}else if( recvbytes > 0)
	{
		recvData[recvbytes]='\0';
		printf("thread id=%lu tm=%d \n服務端返回資訊:%s\n",tid,time(NULL),recvData);
	}

	close(sclient);
	return NULL;
}

測試效果圖:


MAIL: [email protected]

BLOG: http://blog.csdn.net/xcl168

相關推薦

網路程式設計(4)select函式實現I/O伺服器

    我按理解整了個基於select模式的單程序多路複用併發伺服器,並寫了個簡單的測試程式測了下,雖然離實用還差得遠,但用來練習select夠用了。 至於如何實現的細節,程式碼註釋算比較清楚,就不多弄了。 一。伺服器部份 單程序併發伺服器程式碼: /*********

I/O伺服器程式設計

一、實驗目的 理解I/O多路複用技術的原理。 學會編寫基本的單執行緒併發伺服器程式和客戶程式。 二、實驗平臺 ubuntu-8.04作業系統 三、實驗內容 採用I/O多路複用技術實現單執行緒併發伺服器,完成使用一個執行緒處理併發客戶請求的功能。 四、實驗原理 除了可以採用多

poll實現I/O

函式原型: 函式說明:該函式允許程序指示核心等待多個事件中的任何一個發生,並只在有一個或多個事件發生的時候才。 喚醒它 引數說明: fds:是一個struct pollfd 結構體型別的陣列,用於存放需要檢測其狀態的socket描述符。 每當呼叫這個函式之後,系

Linux網路程式設計---I/Oselect

1.I/O多路複用(IO multiplexing) 我們之前講了I/O多路複用和其他I/O的區別,在這裡,我們再具體討論下I/O多路複用是怎麼工作? I/O 多路複用技術就是為了解決程序或執行緒阻塞到某個 I/O 系統呼叫而出現的技術,使程序不阻塞於某個特定的 I/O 系統呼叫。

嵌入式Linux網路程式設計I/Oselect()示例,select()客戶端,select()伺服器,單鏈表

文章目錄 1,IO複用select()示例 1.1 select()---net.h 1.2 select()---client.c 1.3 select()---sever.c 1.4 select()---linklist.h

嵌入式Linux網路程式設計I/O,阻塞I/O模式,非阻塞I/O模式fcntl()/ioctl(),I/O select()/pselect()/poll(),訊號驅動I/O

文章目錄 1,I/O模型 2,阻塞I/O 模式 2.1,讀阻塞(以read函式為例) 2.2,寫阻塞 3,非阻塞模式I/O 3.1,非阻塞模式的實現(fcntl()函式、ioctl() 函式)

嵌入式Linux網路程式設計I/O,epoll()示例,epoll()客戶端,epoll()伺服器,單鏈表

文章目錄 1,I/O多路複用 epoll()示例 1.1,epoll()---net.h 1.2,epoll()---client.c 1.3,epoll()---sever.c 1.4,epoll()---linklist.h

嵌入式Linux網路程式設計I/O,poll()示例,poll()客戶端,poll()伺服器,單鏈表

文章目錄 1,IO複用poll()示例 1.1,poll()---net.h 1.2,poll()---client.c 1.3,poll()---sever.c 1.4,poll()---linklist.h 1.5,p

UNIX網路程式設計-I/O

目錄 Unix下可用的5種I/O模型 阻塞式I/O模型 非阻塞式I/O模型 I/O複用模型 訊號驅動式I/O模型 非同步I/O模型 各種I/O模型的比較 參考   Unix下可用的5種I/O模型 阻塞式I/O 非阻塞式I/O

淺談網路I/O模型 select & poll & epoll

我們首先需要知道select,poll,epoll都是IO多路複用的機制。I/O多路複用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒後自己負責進行讀寫,

Socket網路程式設計_之I/O

1. IO多路複用: 每一次網路通訊都是一個Socket的I/O流,對於伺服器而言,有兩種方法 1.傳統的多程序併發模型(每進來一個新的I/O流會分配一個新的程序管理。) 2.方法二就是I/O的多路複用

Linux網路程式設計---I/O之epoll

/* TCP伺服器 用法:./server port */ #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string

Java網路程式設計與NIO詳解2:JAVA NIO 一步步構建I/O的請求模型

微信公眾號【黃小斜】作者是螞蟻金服 JAVA 工程師,專注於 JAVA 後端技術棧:SpringBoot、SSM全家桶、MySQL、分散式、中介軟體、微服務,同時也懂點投資理財,堅持學習和寫作,相信終身學習的力量!關注公眾號後回覆”架構師“即可領取 Java基礎、進階、專案和架構師等免費學習資料,更有資料

I/Oselect、poll、epoll

很早之前有寫過篇IO多路複用的文章:https://www.cnblogs.com/klcf0220/archive/2013/05/14/3077003.html 參考連結:https://segmentfault.com/a/1190000003063859 select,poll,epoll都是IO多路

java併發程式設計之IO基礎入門之I/O技術

在I/O程式設計過程中,當需要同時處理多個客戶端接入請求時,可以利用多執行緒或者I/O多路複用技術進行處理。I/O多路複用技術通過把多個I/O的阻塞複用到同一個select的阻塞上,從而使得系統在單執行緒的情況下可以同時處理多個客戶端請求。與傳統的多執行緒/多程序模型比,

網路I/O模型到Netty,先深入瞭解下I/O

微信搜尋【阿丸筆記】,關注Java/MySQL/中介軟體各系列原創實戰筆記,乾貨滿滿。   本文是Netty系列第3篇 上一篇文章我們瞭解了Unix標準的5種網路I/O模型,知道了它們的核心區別與各自的優缺點。尤其是I/O多路複用模型,在高併發場景下,有著非常好的優勢。而Netty也採用了I

I/O

I/O型別:     接下來我們將介紹幾種常見的I/O模型及其區別         阻塞I/O:blocking I/O(如果沒有資訊,則阻塞)       

【Linux】I/O

五種IO模型     阻塞IO(等待魚上鉤)         在核心將資料準備好之前,系統呼叫會一直等待,所有的套接字,預設是阻塞模式。         等待,拷貝資料到buf中,(等待的時間長)     非阻塞IO(定期檢視是否有魚上鉤)         如果核心還未將資料

I/O技術(multiplexing)

作者:知乎使用者 連結:https://www.zhihu.com/question/28594409/answer/52835876 來源:知乎 著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。 下面舉一個例子,模擬一個tcp伺服器處理30個客戶soc

IO基礎入門之I/O技術

在I/O程式設計過程中,當需要同時處理多個客戶端接入請求時,可以利用多執行緒或者I/O多路複用技術進行處理。I/O多路複用技術通過把多個I/O的阻塞複用到同一個select的阻塞上,從而使得系統在單執行緒的情況下可以同時處理多個客戶端請求。與傳統的多執行緒/多程序模型比,I