1. 程式人生 > >select實現多個客戶機與伺服器之間的通訊

select實現多個客戶機與伺服器之間的通訊

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define MAXFD 10 //陣列的大小用巨集定義出來

void fds_init(int fds[])//  初始化存放描述符的陣列,因為正確的描述符都是正數,
{                       //所以將陣列中的所有元素都初始化為“-1”
	int i = 0;
	for( ; i < MAXFD; i++)
	{
		fds[i] = -1;
	}
}

void fds_add(int fds[], fd)//將新描述符新增到描述符陣列中
{
	int i = 0;
	for(; i < MAXFD; i++)//   遍歷陣列中,只要發現某一個元素是初始值,就代表沒有描述符存放
	{                    //在該位置,所以就可以將新的描述符存進去
		if( fds[i] == -1)
		{
			fds[i] = fd;//   注意,一旦找到存放位置,在存放後就要返回,如果不返回,i就會遍歷
			return ;    //整個陣列,將新描述符存進整個陣列
		}
	}
}

void fds_del(int fds[], int fd)//將陣列中與fd相等的描述符刪除
{
	int i = 0;
	for( ; i < MAXFD; i++)
	{
		if(fds[i] == fd)
		{
			fds[i] = -1;//注意,一旦找到目標描述符,刪掉後就要返回。原因同存放描述符函式相同
			return ;
		}
	}
}

int main()
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0); //建立套接字
	assert(sockfd != NULL);

	struct sockaddr_in saddr, caddr;
	memset(&saddr, 0, sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //設定協議族埠號和ip地址

	int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)); //命名套接字
	assert(res != -1);

	listen(sockfd, 5);//監聽套接字

	int fds[MAXFD];//定義一個數組用來存放描述符
	fds_init(fds);//初始化陣列
	fds_add(fds, sockfd);//將sockfd先放進陣列中

	while(1)
	{
		fd_set fdset; //給select建立一個“集合”
		FD_ZERO(&fdset);//把集合裡面清零

		int maxfd = -1; //設定一個記錄最大描述符的變數,可以告訴select函式監視的描述符範圍,減少時間浪費
		int i = 0;
		for( ; i < MAXFD; i++)//開始遍歷整個存放描述符的陣列
		{ 
			if(fds[i] == -1)//    如果陣列中的元素的初始值被更改,見代表存放了描述符進去,如果初始值沒有
			{               //更改,就代表沒有存放描述符進去,
				break;
			}
			FD_SET(fds[i], &fdset);//按照描述符的大小在集合中偏移後做標記
			if(fds[i] > maxfd)
			{
				maxfd = fds[i]; //集合中每進入一個描述符就將最大的用maxfd標記起來
			}
		}
		struct timeval tv = {5, 0}; //給設定select函式設定監視集合的時間
		int n = select(maxfd + 1, &fdset, NULL, NULL, &tv); //時間、大小、描述符存放好後開始讓select監視
		if( n == -1) //如果在規定的時間內select返回值為“-1”就代表select函式發生錯誤,返回去繼續監視集合
		{
			printf("select error!\n");
			continue;
		}
		if( n == 0)// 如果在規定時間內select監視的集合中沒有描述符上面有資料傳來,就打超時時一次,繼續監視
		{          
			printf("time out!\n");
			continue;
		}
		else//如果在監視的時間內發現有描述符傳來資料
		{
			int i = 0;
			for( ; i < MAXFD; i++)//遍歷整個陣列,找存放的描述符
			{
				if(fds[i] == -1)
				{
					continue;
				}
				if(FD_ISSET(fds[i] , &fdset))//每找到一個描述符就測試fds[i]是否可讀,即是否網路上有資料 
				{
					if(fds[i] == sockfd)//   如果fds[i]有資料傳來 ,就得判斷是新連線的客戶機還是已經建立
					{                   //連線的客戶機發送給伺服器的資料
						int len = sizeof(caddr);
						int c = accept(sockfd, (struct sockaddr*)caddr, &len); //   如果是新連線的客戶機,
						if( c < 0)                                             //就將客戶機的埠號(檔案
						{                                                      //描述符)存在陣列中
							continue;
						}
						printf("accept = %d\n",c); //將新客戶機描述符打印出
						fds_add(fds, c); //將新檔案描述符存進陣列
					}
					else
					{
						char buff[128] = {0};   //如果是已經建立連線的客戶機發來的資料,就將資料接收在buff中
						int num = recv(fds[i], buff, 127, 0);
						if( num <= 0) //   如果recv接受資料後返回錯誤值“-1”就代表接收失敗,如果返回“0”
						{             //就代表客戶端斷開了與伺服器的連線,此時伺服器端也斷開個描述符所對應的客戶端
							close(fds[i]);
							fds_del(fds, fds[i]); //將這個描述符從陣列中拿出去丟掉
							printf("one client over!\n");
						}
						else
						{
							printf("recv(%d) = %s\n",fds[i],buff);//如果recv返回的值大於0,就代表客戶端發來了資料,輸出伺服器傳送來資料
							send(fds[i], "ok", 2, 0);//給客戶端傳送資料接收成功確認
						}
					}
				}
			}
		}
	}
	exit(0);
}