1. 程式人生 > >多路I/O轉接之select模型

多路I/O轉接之select模型

struct truct rose sleep 輸出 問題 pre strerror 結構

I/O復用使得程序可以同一時候監聽多個文件描寫敘述符。這對提高程序的性能至關重要。通常,網絡程序同一時候處理或者監聽多個socket文件描寫敘述符的時候可以考慮使用I/O復用模型。

值得強調的是。I/O復用盡管可以同一時候監聽多個文件描寫敘述符。但它本身是堵塞的。當有多個文件描寫敘述符就緒的時候,假設不採取額外的措施,程序就僅僅能按順序依次處理當中的每個文件描寫敘述符,這使得server程序看起來像串行工作的。

假設要實現並發。僅僅可以使用多進程或者多線程的手段。

select 系統調用的用途是:在一段指定的時間內,監聽用戶感興趣的文件描寫敘述符上的可讀,可寫和異常等事件。

技術分享

值得說明的是:

1.select 能監聽的文件描寫敘述符個數受限於FD_SETSIZE。一般默覺得1024,單純改變進程打開的文件符個數並不能改變select監聽的文件描寫敘述符的個數

2.解決1024一下client時候使用select非常合適(比方局域網中)。可是假設連接client過多,select採用是輪詢模型(程序中會有所體現)。這樣會大大減少server的響應效率。

相關系統調用函數介紹

#include <sys/select.h> //所需頭文件

int select(int nfds, fd_set *readfds, fd_set *writefds,
                 fd_set *exceptfds, struct timeval *timeout);
1.nfds 監控的文件描寫敘述符集裏最大文件描寫敘述符加1,由於此參數會告訴內核檢測前多少個文件描寫敘述符的狀態,之所以加1 由於文件描寫敘述符是從0開始編號的
2.readfds 監控有讀數據到達文件描寫敘述符集合,傳入傳出參數 
3.writefds監控有寫數據到達的文件描寫敘述符集合,傳入傳出參數
4.exceptfds 監控異常發生達文件描寫敘述符集合,如帶外數據到達異常,傳入傳出參數
timeout:定時堵塞監控時間,3種情況
1.NULL,永遠等下去,堵塞在這裏
2.設置timeval,等待固定時間
3.設置timeval裏時間均為0。檢查描寫敘述字後馬上返回。輪詢
struct timeval {
     long tv_sec; /* seconds */
     long tv_usec; /* microseconds */
};

void FD_CLR(int fd, fd_set *set);//將fd_set集中相應的文件描寫敘述符清0
int  FD_ISSET(int fd, fd_set *set);//測試文件描寫敘述符集合裏fd是否置1
void FD_SET(int fd, fd_set *set);//設置文件描寫敘述符集合李fd為1
void FD_ZERO(fd_set *set); //把文件描寫敘述符集合裏全部位清0

select 監聽程序:

#include<stdio.h>
#include<string.h>
#include<sys/select.h>
#include <sys/un.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<errno.h>
int create_listen(int port)
{
	int listen_st,on;
	struct sockaddr_in s_addr;
	listen_st =socket(AF_INET,SOCK_STREAM,0);
	if(listen_st==-1)
	
	{
		perror("socket error ");
		return -1;
	}
	if(setsockopt(listen_st,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))==-1)
	{
		perror("setsockopt error");
		return -1;
	}
	s_addr.sin_port=htons(port);
	s_addr.sin_family=AF_INET;
	s_addr.sin_addr.s_addr=htonl(INADDR_ANY);
	
	if(bind(listen_st,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in))==-1)
	{
		perror("bind error");
		return -1;
	}
	if (listen(listen_st, 5) == -1) // 設置文件描寫敘述符具有監聽的功能
    {  
        perror("listen error");
        return -1;  
    }  
    return listen_st;  
}

int run_server(int port)
{
	int i,maxi,maxfd,listen_st,conn_st,sockaddr_len;
	int nready,client[FD_SETSIZE];
	char buf[1024];
	struct sockaddr_in c_addr;
	fd_set rset,allset;
	listen_st=create_listen(port);
	if(listen_st==-1)
	{
		return -1;
	}
	for(i=0;i<FD_SETSIZE;i++)
	{
		client[i]=-1;
	}
	FD_ZERO(&allset);
	FD_SET(listen_st, &allset);
	maxfd = listen_st;
	maxi=-1;
	while(1)
	{
		rset=allset;
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);//select 開始監聽
		if(nready<0)
		{
			perror("select error");
			break;
		}
		if(FD_ISSET(listen_st,&rset))//檢測listen_st 在fd_set有沒有被事件到達
		{
			sockaddr_len=sizeof(c_addr);
			conn_st=accept(listen_st,(struct sockaddr *)&c_addr,&sockaddr_len);
			printf("received form %s at port:%d \n",inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));
			for(i=0;i<FD_SETSIZE;i++)
			{
				if(client[i]<0)
				{
					client[i]=conn_st;
					break;
				}
			}
			if(i==FD_SETSIZE)
			{
				printf("too many client \n");
				close(conn_st);
			}else
			{
				FD_SET(conn_st,&allset);
				if(i>maxi) //記錄最大下標
				{
					maxi=i;
				}
				if(conn_st>maxfd)//記錄最大文件描寫敘述符用於select用
				{
					maxfd=conn_st;
				}
			}
			if(--nready==0) continue;
		}
		
		for(i=0;i<=maxi;i++)
		{
			if((conn_st=client[i])<0)
			{
				continue;
			}
			
			if(FD_ISSET(conn_st,&rset))
			{
				memset(buf,0,sizeof(buf));
				if(read(conn_st,buf,sizeof(buf))==0)
				{
					printf("close client \n");
					close(conn_st);
					FD_CLR(conn_st,&allset);
					client[i]=-1;
				}
				else
				{
					printf("recv from client:%s \n",buf);
					write(conn_st,buf,strlen(buf));
				}
				if (--nready == 0) break;  //就緒個數減一
			}
			
		}
		sleep(1);
	}
	close(listen_st);
	return 0;
}


int main(int argc,char *argv[])
{
	if(argc<2)
	{
		printf("usage:%s port \n",argv[0]);
		return 0;
	}
	int port=atoi(argv[1]);
	if(port==0)
	{
		printf("port error \n");
		return 0;
	}
	printf("start server \n");
	run_server(port);
	return 0;
}

client程序

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#define BUFFSIZE 1024
#define ERRORCODE -1

static void *thread_send(void *arg)
{
	char buf[BUFFSIZE];
	int sd = *(int *) arg;
	while (1)
	{
		memset(buf, 0, sizeof(buf));
		read(STDIN_FILENO, buf, sizeof(buf));
		if (send(sd, buf, strlen(buf), 0) == -1)
		{
			printf("send error:%s \n", strerror(errno));
			break;
		}
	}
	return NULL;
}
static void* thread_recv(void *arg)
{
	char buf[BUFFSIZE];
	int sd = *(int *) arg;
	while (1)
	{
		memset(buf, 0, sizeof(buf));
		int rv = recv(sd, buf, sizeof(buf), 0);
		if (rv <= 0)
		{
			if(rv == 0) //server socket關閉情況
			{
				printf("server have already full !\n");
				exit(0);//退出整個客服端
			}
			printf("recv error:%s \n", strerror(errno));
			break;
		}
		printf("%s", buf);//輸出接收到的內容
	}
	return NULL;
}
int run_client(char *ip_str, int port)
{
	int client_sd;
	int con_rv;
	pthread_t thrd1, thrd2;
	struct sockaddr_in client_sockaddr; //定義IP地址結構

	client_sd = socket(AF_INET, SOCK_STREAM, 0);
	if (client_sd == -1)
	{
		printf("socket create error:%s \n", strerror(errno));
		return ERRORCODE;
	}

	memset(&client_sockaddr, 0, sizeof(client_sockaddr));
	client_sockaddr.sin_port = htons(port); //指定一個端口號並將hosts字節型傳化成Inet型字節型(大端或或者小端問題)
	client_sockaddr.sin_family = AF_INET;	//設置結構類型為TCP/IP
	client_sockaddr.sin_addr.s_addr = inet_addr(ip_str);
	//將字符串的ip地址轉換成int型,客服端要連接的ip地址
	con_rv = connect(client_sd, (struct sockaddr*) &client_sockaddr,
			sizeof(client_sockaddr));
	//struct sockaddr 是非常早曾經定義的 struct sockaddr_in 是後定義的,眼下用的比較多
	//調用connect連接到指定的ip地址和端口號,建立連接後通過socket描寫敘述符通信
	if (con_rv == -1)
	{
		printf("connect error:%s \n", strerror(errno));
		return ERRORCODE;
	}
	if (pthread_create(&thrd1, NULL, thread_send, &client_sd) != 0)
	{
		printf("thread error:%s \n", strerror(errno));
		return ERRORCODE;

	}
	if (pthread_create(&thrd2, NULL, thread_recv, &client_sd) != 0)
	{
		printf("thread error:%s \n", strerror(errno));
		return ERRORCODE;
	}
	pthread_join(thrd2, NULL);
	pthread_join(thrd1, NULL);
	close(client_sd);
	return 0;
}
int main(int argc, char *argv[])
{
	if (argc < 3)
	{
		printf("Usage:ip port,example:127.0.0.1 8080 \n");
		return ERRORCODE;
	}
	int port = atoi(argv[2]);
	char *ip_str = argv[1];
	run_client(ip_str,port);
	return 0;
}




多路I/O轉接之select模型