国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Linux網(wǎng)絡編程:多路I/O轉(zhuǎn)接服務器(select poll epoll)

這篇具有很好參考價值的文章主要介紹了Linux網(wǎng)絡編程:多路I/O轉(zhuǎn)接服務器(select poll epoll)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

文章目錄:

一:select

1.基礎API?

select函數(shù)

思路分析

select優(yōu)缺點

2.server.c

3.client.c

二:poll

1.基礎API?

poll函數(shù)?

poll優(yōu)缺點

read函數(shù)返回值

突破1024 文件描述符限制

2.server.c

3.client.c

三:epoll

1.基礎API

epoll_create創(chuàng)建? ?epoll_ctl操作? epoll_wait阻塞

epoll實現(xiàn)多路IO轉(zhuǎn)接思路

epoll優(yōu)缺點?

ctags使用

2.server.c

3.client.c

4.事件模型(epoll 事件觸發(fā)模型ET和LT)

4.1 server.c

4.2 client.c

5.epoll 反應堆模型


select、poll以及epoll都是系統(tǒng)內(nèi)核來對網(wǎng)絡通信中的通信套接字(文件描述符)來進行監(jiān)視
能夠在與服務器連接的大量客戶端中識別出與服務器請求了數(shù)據(jù)交換的客戶端,并把它們所對應的套接字通過函數(shù)返回,交給服務器
此時服務器只需要和請求了數(shù)據(jù)交換的客戶端進行通信即可,而其它的套接字則不做任何處理

因此,比起服務器自身每次去輪詢查詢并處理每個套接字的效率要高很多

一:select

1.基礎API?

select函數(shù)

Linux網(wǎng)絡編程:多路I/O轉(zhuǎn)接服務器(select poll epoll),# Linux網(wǎng)絡編程,服務器,網(wǎng)絡,linux

?Linux網(wǎng)絡編程:多路I/O轉(zhuǎn)接服務器(select poll epoll),# Linux網(wǎng)絡編程,服務器,網(wǎng)絡,linux

原理:  借助內(nèi)核, select 來監(jiān)聽, 客戶端連接、數(shù)據(jù)通信事件


//將給定的套接字fd從位圖set中清除出去
    void FD_CLR(int fd,fd_set* set);			
        FD_CLR(4, &rset);                    將一個文件描述符從監(jiān)聽集合中 移除

//檢查給定的套接字fd是否在位圖里面,返回值 在1 不在0
    int FD_ISSET(int fd,fd_set* set);	
        FD_ISSET(4,&rset);                   判斷一個文件描述符是否在監(jiān)聽集合中

//將給定的套接字fd設置到位圖set中		
    void FD_SET(int fd,fd_set* set);            將待監(jiān)聽的文件描述符,添加到監(jiān)聽集合中	
        FD_SET(3, &rset);	
        FD_SET(5, &rset);	
        FD_SET(6, &rset);

//將整個位圖set置零		
    void FD_ZERO(fd_set* set);					
        fd_set rset;                            清空一個文件描述符集
		FD_ZERO(&rset);


//select 是一個系統(tǒng)調(diào)用,用于監(jiān)控多個文件描述符(sockets, files等)的 I/O 活動
//它等待某個文件描述符集變?yōu)榭勺x、可寫或出現(xiàn)異常,然后返回	
    int select(int nfds, 
               fd_set *readfds, 
               fd_set *writefds,
               fd_set *exceptfds, 
               struct timeval *timeout);

		nfds     :監(jiān)聽 所有文件描述符中,最大文件描述符+1
		readfds  :讀   文件描述符監(jiān)聽集合。	傳入、傳出參數(shù)
		writefds :寫   文件描述符監(jiān)聽集合。	傳入、傳出參數(shù)		NULL
		exceptfds:異常 文件描述符監(jiān)聽集合	傳入、傳出參數(shù)		NULL

		timeout: 	
                > 0 : 設置監(jiān)聽超時時長
				NULL:阻塞監(jiān)聽
				0   :非阻塞監(jiān)聽,輪詢

		返回值:
			> 0:所有監(jiān)聽集合(3個)中, 滿足對應事件的總數(shù)
			  0:沒有滿足監(jiān)聽條件的文件描述符
			 -1:errno

思路分析

	int maxfd = 0;
	lfd = socket() ;			    創(chuàng)建套接字
	maxfd = lfd;                   備份
	bind();					        綁定地址結(jié)構(gòu)
	listen();				        設置監(jiān)聽上限

	fd_set rset, allset;			創(chuàng)建r讀監(jiān)聽集合
	FD_ZERO(&allset);				將r讀監(jiān)聽集合清空
	FD_SET(lfd, &allset);			將 lfd 添加至讀集合中
        lfd文件描述符在監(jiān)聽期間沒有滿足讀事件發(fā)生,select返回的時候rset不會在集合中

	while(1) {
		rset = allset;			                                保存監(jiān)聽集合
		ret  = select(lfd+1, &rset, NULL, NULL, NULL);		監(jiān)聽文件描述符集合對應事件
		if(ret > 0) {							                有監(jiān)聽的描述符滿足對應事件
            //處理連接:一次監(jiān)聽		
			if (FD_ISSET(lfd, &rset)) {				            1 在集合中,0不在
				cfd = accept();				                建立連接,返回用于通信的文件描述符
				maxfd = cfd;
				FD_SET(cfd, &allset);				            添加到監(jiān)聽通信描述符集合中
			}
            //處理通信:剩下的
			for (i = lfd+1; i <= 最大文件描述符; i++){
                //嵌套
				    FD_ISSET(i, &rset)				            有read、write事件
				read()
				小 -- 大
				write();
			}	
		}
	}

select優(yōu)缺點

?當你只需要監(jiān)聽幾個指定的套接字時, 需要對整個1024的數(shù)組進行輪詢, 效率降低

缺點:監(jiān)聽上限受文件描述符限制。 最大1024
      檢測滿足條件的fd,自己添加業(yè)務邏輯提高小,提高了編碼難度

      如果監(jiān)聽的文件描述符比較散亂、而且數(shù)量不多,效率會變低


優(yōu)點:	跨平臺win、linux、macOS、Unix、類Unix、mips

2.server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>

#include "wrap.h"

#define SERV_PORT 6666


void FD_CLR(int fd,fd_set* set);			//將給定的套接字fd從位圖set中清除出去
int FD_ISSET(int fd,fd_set* set);			//檢查給定的套接字fd是否在位圖里面,返回0或1
void FD_SET(int fd,fd_set* set);			//將給定的套接字fd設置到位圖set中
void FD_ZERO(fd_set* set);					//將整個位圖set置零


int main(int argc, char *argv[]){
	int i, j, n, maxi;
	
		/*數(shù)組:將需要輪詢的客戶端套接字放入數(shù)組client[FD_SETSIZE],防止遍歷1024個文件描述符  FD_SETSIZE默認為1024*/
		int nready, client[FD_SETSIZE];		
		
		int listenFd, connectFd, maxFd, socketFd;
		
		char buf[BUFSIZ], str[INET_ADDRSTRLEN];					//#define INET_ADDRSTRLEN 16

		struct sockaddr_in serverAddr, clientAddr;
		
		socklen_t clientAddrLen;
		
		fd_set rset, allset;                            		//rset讀事件文件描述符集合,allset用來暫存
	
	
	/*得到監(jiān)聽套接字*/
		listenFd = Socket(AF_INET, SOCK_STREAM, 0);
			/*定義兩個集合,將listenFd放入allset集合當中*/
		fd_set rset, allset;
		FD_ZERO(&allset);										//將整個位圖set置零
		//將給定的套接字fd設置到位圖set中
		FD_SET(listenFd, &allset);								//將connectFd加入集合:構(gòu)造select監(jiān)控文件描述符集
	
	
	/*設置地址端口復用*/
		int opt = 1;
		setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));
	
	
	/*填寫服務器地址結(jié)構(gòu)*/
		bzero(&serverAddr, sizeof(serverAddr));
		
		serverAddr.sin_family = AF_INET;
		serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
		serverAddr.sin_port = htons(SERVER_PORT);
	
	
	/*綁定服務器地址結(jié)構(gòu)*/
		Bind(listenFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
		Listen(listenFd, 128);
	
	
	/*將listenFd設置為數(shù)組中最大的Fd*/
		maxFd = listenFd;										//起初 listenfd 即為最大文件描述符
		maxi = -1;												//將來用作client[]的下標, 初始值指向0個元素之前下標位置
		
	
	/*數(shù)組:初始化自己的數(shù)組為-1*/
		for (i = 0; i < FD_SETSIZE; ++i)
			client[i] = -1;

	while (1){
		/*把allset給rest,讓他去用*/
		rset = allset;											//備份:每次循環(huán)時都從新設置select監(jiān)控信號集
		nready = select(maxFd + 1, &rset, NULL, NULL, NULL);	//使用select監(jiān)聽文件描述符集合對應事件

		if (nready == -1)										//出錯返回
			perr_exit("select error");

		/*listen滿足監(jiān)聽的事件:如果有了新的連接請求,得到connectFd,并將其放入自定義數(shù)組中*/
			if (FD_ISSET(listenFd, &rset)){						//檢查給定的套接字fd是否在位圖里面,返回0或1
				clientAddrLen = sizeof(clientAddr);
				
				//建立鏈接,不會阻塞
				connectFd = Accept(listenFd, (struct sockaddr *)&clientAddr, &clientAddrLen);
				
				printf(
					"Recived from %s at PORT %d\n", 
					inet_ntop(AF_INET, 
					&(clientAddr.sin_addr.s_addr), 
					str, 
					sizeof(str)), 
					ntohs(clientAddr.sin_port));

				for (i = 0; i < FD_SETSIZE; ++i)
					if (client[i] < 0){							//找client[]中沒有使用的位置
						client[i] = connectFd;					//保存accept返回的文件描述符到client[]里	
						break;
					}
					
				/*自定義數(shù)組滿了:達到select能監(jiān)控的文件個數(shù)上限 1024 */
					if(i==FD_SETSIZE){
						fputs("Too many clients\n",stderr);
						exit(1);
					}
				
				/*connectFd加入監(jiān)聽集合:向監(jiān)控文件描述符集合allset添加新的文件描述符connectFd*/
					FD_SET(connectFd, &allset);					//將給定的套接字fd設置到位圖set中

				/*更新最大的Fd*/
					if (maxFd < connectFd)
						maxFd = connectFd;
					
				/*更新循環(huán)上限*/
					if(i>maxi)
						maxi=i;									//保證maxi存的總是client[]最后一個元素下標
					
				/*select返回1,說明只有建立連接請求,沒有數(shù)據(jù)傳送請求,跳出while循環(huán)剩余部分(下面的for循環(huán)輪詢過程)*/
				//如果只有l(wèi)isten事件,只需建立連接即可,無需數(shù)據(jù)傳輸,跳出循環(huán)剩余部分
					if (--nready == 0)
						continue;
			}
		/*檢測哪個clients 有數(shù)據(jù)就緒:select返回不是1,說明有connectFd有數(shù)據(jù)傳輸請求,遍歷自定義數(shù)組*/
		//否則,說明有數(shù)據(jù)傳輸需求
			for (i = 0; i <= maxi; ++i){
				if((socketFd=client[i])<0)
					continue;
					
				/*遍歷檢查*/
				if (FD_ISSET(socketFd, &rset)){					//檢查給定的套接字fd是否在位圖里面,返回0或1
					/*read返回0說明傳輸結(jié)束,關(guān)閉連接:當client關(guān)閉鏈接時,服務器端也關(guān)閉對應鏈接*/
					if ((n=read(socketFd,buf,sizeof(buf)))==0){
						close(socketFd);
						//將給定的套接字fd從位圖set中清除出去
						FD_CLR(socketFd, &allset);				//解除select對此文件描述符的監(jiān)控
						client[i]=-1;
					}else if(n>0){
						for (j = 0; j < n; ++j)
							buf[j] = toupper(buf[j]);
						write(socketFd, buf, n);
						write(STDOUT_FILENO, buf, n);
					}
					
					/*不懂:需要處理的個數(shù)減1?*/
					if(--nready==0)
						break;									//跳出for, 但還在while中
				}
			}
	}
	close(listenFd);
	return 0;
}

3.client.c

/* client.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    char buf[MAXLINE];
    int sockfd, n;

    if (argc != 2) {
        printf("Enter: ./client server_IP\n");
        exit(1);
    }

    sockfd = Socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);

    Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    printf("------------connect ok----------------\n");

    while (fgets(buf, MAXLINE, stdin) != NULL) {
        Write(sockfd, buf, strlen(buf));
        n = Read(sockfd, buf, MAXLINE);
        if (n == 0) {
            printf("the other side has been closed.\n");
            break;
        }
        else
            Write(STDOUT_FILENO, buf, n);
    }
    Close(sockfd);

    return 0;
}

二:poll

這個函數(shù)是一個半成品,用的很少?

1.基礎API?

poll函數(shù)?

Linux網(wǎng)絡編程:多路I/O轉(zhuǎn)接服務器(select poll epoll),# Linux網(wǎng)絡編程,服務器,網(wǎng)絡,linux

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
		fds:監(jiān)聽的文件描述符,傳入傳出【數(shù)組】
			struct pollfd {			
				  int fd      :待監(jiān)聽的文件描述符				
				  short events:待監(jiān)聽的文件描述符對應的監(jiān)聽事件
						  取值:POLLIN、POLLOUT、POLLERR
				 short revnets:
                            傳入時,給0
                            如果滿足對應事件的話, 返回 非0 --> POLLIN、POLLOUT、POLLERR
			}

		nfds: 監(jiān)聽數(shù)組的,實際有效監(jiān)聽個數(shù)

		timeout:  
             > 0:超時時長。單位:毫秒
			  -1:阻塞等待
			   0:不阻塞

		返回值:返回滿足對應監(jiān)聽事件的文件描述符 總個數(shù)

poll優(yōu)缺點

優(yōu)點:
		自帶數(shù)組結(jié)構(gòu)。 可以將 監(jiān)聽事件集合 和 返回事件集合 分離
		拓展 監(jiān)聽上限。 超出 1024限制


缺點:
		不能跨平臺。 Linux
		無法直接定位滿足監(jiān)聽事件的文件描述符, 編碼難度較大

read函數(shù)返回值

	> 0: 實際讀到的字節(jié)數(shù)

	=0: socket中,表示對端關(guān)閉。close()

	-1:	
        如果 errno == EINTR                    被異常終端                         需要重啟
		如果 errno == EAGIN 或 EWOULDBLOCK     以非阻塞方式讀數(shù)據(jù),但是沒有數(shù)據(jù)    需要,再次讀
		如果 errno == ECONNRESET               說明連接被 重置                    需要 close(),移除監(jiān)聽隊列
		錯誤

突破1024 文件描述符限制

cat /proc/sys/fs/file-max     ——> 當前計算機所能打開的最大文件個數(shù)。 受硬件影響

ulimit -a 	                  ——> 當前用戶下的進程,默認打開文件描述符個數(shù)。  缺省為 1024

修改:
    打開 sudo vi /etc/security/limits.conf, 寫入:
        * soft nofile 65536			    --> 設置默認值, 可以直接借助命令修改。 【注銷用戶,使其生效】
        * hard nofile 100000			--> 命令修改上限

2.server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666
#define OPEN_MAX 1024



int main(int argc,char* argv[]){
	int ret=0;
	/*poll函數(shù)返回值*/
	int nready=0;
	int i,j,maxi;
	int connectFd,listenFd,socketFd;
	ssize_t n;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	socklen_t clientLen;
	
	
	
	/*創(chuàng)建結(jié)構(gòu)體數(shù)組*/	
		struct pollfd client[OPEN_MAX];
	
	/*創(chuàng)建客戶端地址結(jié)構(gòu)和服務器地址結(jié)構(gòu)*/
		struct sockaddr_in clientAddr,serverAddr;
	
	/*得到監(jiān)聽套接字listenFd*/
		listenFd=Socket(AF_INET,SOCK_STREAM,0);
	
	
	
	/*設置地址可復用*/
		int opt=0;
		ret=setsockopt(listenFd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt));
		if(ret==-1)
			perr_exit("setsockopt error");
		
	/*向服務器地址結(jié)構(gòu)填入內(nèi)容*/
		bzero(&serverAddr,sizeof(serverAddr));
		serverAddr.sin_family=AF_INET;
		serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
		serverAddr.sin_port=htons(SERVER_PORT);
	
	/*綁定服務器地址結(jié)構(gòu)到監(jiān)聽套接字,并設置監(jiān)聽上限*/
		Bind(listenFd,(const struct sockaddr*)&serverAddr,sizeof(serverAddr));
		Listen(listenFd,128);
	
	/*初始化第一個pollfd為監(jiān)聽套接字*/
		client[0].fd=listenFd;					//listenfd監(jiān)聽普通讀事件 
		client[0].events=POLLIN;				//事件已經(jīng)準備好被讀取或處理
	
	/*將pollfd數(shù)組的余下內(nèi)容的fd文件描述符屬性置為-1*/
		for(i=1;i<OPEN_MAX;++i)
			client[i].fd=-1;					//用-1初始化client[]里剩下元素
			
		maxi=0;									//client[]數(shù)組有效元素中最大元素下標
	
		while(1){
		/*nready是有多少套接字有POLLIN請求*/
			nready=poll(client,maxi+1,-1);		//阻塞
			if(nready==-1)
				perr_exit("poll error");
				
			
		/*如果listenFd的revents有POLLIN請求,則調(diào)用Accept函數(shù)得到connectFd*/
			if(client[0].revents&POLLIN){		//有客戶端鏈接請求
				clientLen=sizeof(clientAddr);
				connectFd=Accept(listenFd,(struct sockaddr*)&clientAddr,&clientLen);
				
				/*打印客戶端地址結(jié)構(gòu)信息*/
					printf("Received from %s at PORT %d\n",
							inet_ntop(AF_INET,&(clientAddr.sin_addr.s_addr),str,sizeof(str)),
							ntohs(clientAddr.sin_port));
					
				/*將創(chuàng)建出來的connectFd加入到pollfd數(shù)組中*/
					for(i=1;i<OPEN_MAX;++i)
						if(client[i].fd<0){
							//找到client[]中空閑的位置,存放accept返回的connfd 
							client[i].fd=connectFd;			
							break;
						}

					if(i==OPEN_MAX)
						perr_exit("Too many clients,I'm going to die...");
						
				/*當沒有錯誤時,將對應的events設置為POLLIN*/
					client[i].events=POLLIN;	//設置剛剛返回的connfd,監(jiān)控讀事件

					if(i>maxi)						
						maxi=i;					//更新client[]中最大元素下標
					if(--nready<=0)
						continue;				//沒有更多就緒事件時,繼續(xù)回到poll阻塞
			}
		
		
		/*開始從1遍歷pollfd數(shù)組*/
			for(i=1;i<=maxi;++i){				//檢測client[] 
				/*到結(jié)尾了或者有異常*/
					if((socketFd=client[i].fd)<0)
						continue;
						
				/*第i個客戶端有連接請求,進行處理	read*/
				if(client[i].revents&POLLIN){
					if((n=read(socketFd,buf,sizeof(buf)))<0){
						/*出錯時進一步判斷errno*/
							if(errno=ECONNRESET){
								printf("client[%d] aborted connection\n",i);
								close(socketFd);
								client[i].fd=-1;
							}else
								perr_exit("read error");
					}else if(n==0){
						/*read返回0,說明讀到了結(jié)尾,關(guān)閉連接*/
							printf("client[%d] closed connection\n",i);
							close(socketFd);
							client[i].fd=-1;
					}else{
						/*數(shù)據(jù)處理*/
							for(j=0;j<n;++j)
								buf[j]=toupper(buf[j]);
							Writen(STDOUT_FILENO,buf,n);
							Writen(socketFd,buf,n);
					}
					if(--nready==0)
						break;
				}
			}
	}
	return 0;
}

3.client.c

/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, n;

	sockfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	while (fgets(buf, MAXLINE, stdin) != NULL) {
		Write(sockfd, buf, strlen(buf));
		n = Read(sockfd, buf, MAXLINE);
		if (n == 0)
			printf("the other side has been closed.\n");
		else
			Write(STDOUT_FILENO, buf, n);
	}
	Close(sockfd);
	return 0;
}

三:epoll

epoll是Linux下多路復用IO接口select/poll的增強版本,它能顯著提高程序在大量并發(fā)連接中只有少量活躍的情況下的系統(tǒng)CPU利用率:都連接但不發(fā)送數(shù)據(jù)?

1.基礎API

紅黑樹

Linux網(wǎng)絡編程:多路I/O轉(zhuǎn)接服務器(select poll epoll),# Linux網(wǎng)絡編程,服務器,網(wǎng)絡,linux

lfd數(shù)據(jù)連接


cfd數(shù)據(jù)通信

epoll_create創(chuàng)建? ?epoll_ctl操作? epoll_wait阻塞

int epoll_create(int size);						                                    創(chuàng)建一棵監(jiān)聽紅黑樹
		size:創(chuàng)建的紅黑樹的監(jiān)聽節(jié)點數(shù)量(僅供內(nèi)核參考)
		返回值:
            成功:指向新創(chuàng)建的紅黑樹的根節(jié)點的 fd
			失?。?-1 errno


int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);	                 操作控制監(jiān)聽紅黑樹
		epfd:epoll_create 函數(shù)的返回值 epfd

		op  :對該監(jiān)聽紅黑數(shù)所做的操作
			EPOLL_CTL_ADD 添加fd到 監(jiān)聽紅黑樹
			EPOLL_CTL_MOD 修改fd在 監(jiān)聽紅黑樹上的監(jiān)聽事件
			EPOLL_CTL_DEL 將一個fd 從監(jiān)聽紅黑樹上摘下(取消監(jiān)聽)

		fd:待監(jiān)聽的fd			

		event:本質(zhì)struct epoll_event 結(jié)構(gòu)體 地址
			成員 events:EPOLLIN / EPOLLOUT / EPOLLERR				
                EPOLLIN :	表示對應的文件描述符可以讀(包括對端SOCKET正常關(guān)閉)
		        EPOLLOUT:	表示對應的文件描述符可以寫
		        EPOLLPRI:	表示對應的文件描述符有緊急的數(shù)據(jù)可讀(這里應該表示有帶外數(shù)據(jù)到來)
		        EPOLLERR:	表示對應的文件描述符發(fā)生錯誤
		        EPOLLHUP:	表示對應的文件描述符被掛斷;
		        EPOLLET: 	將EPOLL設為邊緣觸發(fā)(Edge Triggered)模式,這是相對于水平觸發(fā)(Level Triggered)而言的
		        EPOLLONESHOT:只監(jiān)聽一次事件,當監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里
                
			成員 typedef union epoll_data: 聯(lián)合體(共用體)
				int fd;	      對應監(jiān)聽事件的 fd
				void *ptr; 
				uint32_t u32;
				uint64_t u64;		

		返回值:成功 0; 失敗: -1 errno


int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 	   阻塞監(jiān)聽
		epfd:epoll_create 函數(shù)的返回值 epfd
		events:傳出參數(shù),【數(shù)組】, 滿足監(jiān)聽條件的 哪些 fd 結(jié)構(gòu)體
		maxevents:數(shù)組 元素的總個數(shù) 1024(不是字節(jié)數(shù))				
			       struct epoll_event evnets[1024]
		timeout:
			-1: 阻塞————通過等待某些特定條件出現(xiàn)來實現(xiàn)的,而在等待的過程中,程序的其他部分都會被暫停執(zhí)行
			 0:不阻塞
			>0: 超時時間 (毫秒)

		read返回值:
			> 0: 滿足監(jiān)聽的 總個數(shù),可以用作循環(huán)上限
			  0:沒有fd滿足監(jiān)聽事件
			 -1:失敗,errno

epoll實現(xiàn)多路IO轉(zhuǎn)接思路

lfd = socket();			                    監(jiān)聽連接事件lfd
bind();
listen();


int epfd = epoll_create(1024);				    epfd, 監(jiān)聽紅黑樹的樹根

    struct epoll_event tep, ep[1024];			tep, 用來設置單個fd屬性, ep是epoll_wait() 傳出的滿足監(jiān)聽事件的數(shù)組
    tep.events = EPOLLIN;					    初始化  lfd的監(jiān)聽屬性_文件描述符可以讀
    tep.data.fd = lfd
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tep);		將 lfd 添加到監(jiān)聽紅黑樹上


while (1) {
	ret = epoll_wait(epfd, ep,1024, -1);		                           阻塞監(jiān)聽

	for (i = 0; i < ret; i++) {		
        //lfd數(shù)據(jù)連接
		if (ep[i].data.fd == lfd) {				                           lfd 滿足讀事件,有新的客戶端發(fā)起連接請求
			cfd = Accept();

			tep.events = EPOLLIN;				                           初始化  cfd的監(jiān)聽屬性_文件描述符可以讀
			tep.data.fd = cfd;

			epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tep);                    將 cfd 添加到監(jiān)聽紅黑樹上
		}
         //cfd數(shù)據(jù)通信
         else {						                                       cfd 們 滿足讀事件, 有客戶端寫數(shù)據(jù)來
			n = read(ep[i].data.fd, buf, sizeof(buf));
			if ( n == 0) {
				close(ep[i].data.fd);
				epoll_ctl(epfd, EPOLL_CTL_DEL, ep[i].data.fd , NULL);	   將關(guān)閉的cfd,從監(jiān)聽樹上摘下
			} else if (n > 0) {
				小--大
				write(ep[i].data.fd, buf, n);
			}
		}
	}
}

epoll優(yōu)缺點?

優(yōu)點:
		高效。突破1024文件描述符

缺點:
		不能跨平臺。 Linux

ctags使用

是vim下方便代碼閱讀的工具1

    `ctags ./* -R`在項目目錄下生成ctags文件;
    
    `Ctrl+]`跳轉(zhuǎn)到函數(shù)定義的位置;

    `Ctrl+t`返回此前的跳轉(zhuǎn)位置;

    `Ctrl+o`屏幕左邊列出文件列表, 再按關(guān)閉;

    `F4`屏幕右邊列出函數(shù)列表, 再按關(guān)閉;



(還是VSCode比較香)

2.server.c

#include "033-035_wrap.h"

#define SERVER_PORT 9527
#define MAXLINE     80
#define OPEN_MAX    1024

int main(int argc,char* argv[]){
    int i=0,n=0,num=0;
    int clientAddrLen=0;
    int listenFd=0,connectFd=0,socketFd=0;
    ssize_t nready,efd,res;
    char buf[MAXLINE],str[INET_ADDRSTRLEN];

    struct sockaddr_in serverAddr,clientAddr;
	
    /*創(chuàng)建一個臨時節(jié)點temp和一個數(shù)組ep*/
		struct epoll_event temp;
		struct epoll_event ep[OPEN_MAX];




    /*創(chuàng)建監(jiān)聽套接字*/
		listenFd=Socket(AF_INET,SOCK_STREAM,0);
	
    /*設置地址可復用*/
		int opt=1;
		setsockopt(listenFd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt));

    /*初始化服務器地址結(jié)構(gòu)*/
		bzero(&serverAddr,sizeof(serverAddr));
		serverAddr.sin_family=AF_INET;
		serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
		serverAddr.sin_port=htons(SERVER_PORT);

    /*綁定服務器地址結(jié)構(gòu)*/
		Bind(listenFd,(const struct sockaddr*)&serverAddr,sizeof(serverAddr));
		
    /*設置監(jiān)聽上限*/
		Listen(listenFd,128);

    /*創(chuàng)建監(jiān)聽紅黑樹樹根*/
		efd=epoll_create(OPEN_MAX);
		if(efd==-1)
			perr_exit("epoll_create error");

    /*將listenFd加入監(jiān)聽紅黑樹中*/
		temp.events=EPOLLIN;
		temp.data.fd=listenFd;
		res=epoll_ctl(efd,EPOLL_CTL_ADD,listenFd,&temp);
		if(res==-1)
			perr_exit("epoll_ctl error");

    while(1){
        /*阻塞監(jiān)聽寫事件*/
			nready=epoll_wait(efd,ep,OPEN_MAX,-1);
			if(nready==-1)
				perr_exit("epoll_wait error");

        /*輪詢整個數(shù)組(紅黑樹)*/
			for(i=0;i<nready;++i){
				if(!(ep[i].events&EPOLLIN))
					continue;

            /*如果是建立連接請求*/
				// lfd 滿足讀事件,有新的客戶端發(fā)起連接請求
				if(ep[i].data.fd==listenFd){
					clientAddrLen=sizeof(clientAddr);
					connectFd=Accept(listenFd,(struct sockaddr*)&clientAddr,&clientAddrLen);
					
					printf("Received from %s at PORT %d\n",
							inet_ntop(AF_INET,
							&clientAddr.sin_addr.s_addr,
							str,
							sizeof(str)),
							ntohs(clientAddr.sin_port));
					printf("connectFd=%d,client[%d]\n",connectFd,++num);

					/*將新創(chuàng)建的連接套接字加入紅黑樹*/
						//初始化  cfd的監(jiān)聽屬性_文件描述符可以讀
							temp.events=EPOLLIN;
							temp.data.fd=connectFd;
							
							res=epoll_ctl(efd,EPOLL_CTL_ADD,connectFd,&temp);
						
						if(res==-1)
							perr_exit("epoll_ctl errror");
				}else{
					/*不是建立連接請求,是數(shù)據(jù)處理請求*/
						socketFd=ep[i].data.fd;
						//cfd 們 滿足讀事件, 有客戶端寫數(shù)據(jù)來
						n=read(socketFd,buf,sizeof(buf));
				
						/*讀到0說明客戶端關(guān)閉*/
							//已經(jīng)讀到結(jié)尾
							if(n==0){
								res=epoll_ctl(efd,EPOLL_CTL_DEL,socketFd,NULL);
								if(res==-1)
									perr_exit("epoll_ctl error");
								close(socketFd);
								printf("client[%d] closed connection\n",socketFd);
							//報錯
							}else if(n<0){	
								/*n<0報錯*/
									perr_exit("read n<0 error");
									
									// 將關(guān)閉的cfd,從監(jiān)聽樹上摘下
										res=epoll_ctl(efd,EPOLL_CTL_DEL,socketFd,NULL);
										close(socketFd);
							//   > 0實際讀到的字節(jié)數(shù)
							}else{
								/*數(shù)據(jù)處理*/
									for(i=0;i<n;++i)
										buf[i]=toupper(buf[i]);
									write(STDOUT_FILENO,buf,n);
									Writen(socketFd,buf,n);
							}
				}
        }
    }

    close(listenFd);
    close(efd);
    return 0;
}

3.client.c

/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, n;

	sockfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	while (fgets(buf, MAXLINE, stdin) != NULL) {
		Write(sockfd, buf, strlen(buf));
		n = Read(sockfd, buf, MAXLINE);
		if (n == 0)
			printf("the other side has been closed.\n");
		else
			Write(STDOUT_FILENO, buf, n);
	}

	Close(sockfd);
	return 0;
}

4.事件模型(epoll 事件觸發(fā)模型ET和LT)

ET工作模式:邊沿觸發(fā)————只有數(shù)據(jù)到來才觸發(fā),不管緩存區(qū)中是否還有數(shù)據(jù),緩沖區(qū)剩余未讀盡的數(shù)據(jù)不會導致
    作用:當文件描述符從未就緒變?yōu)榫途w時,內(nèi)核會通過epoll告訴你一次喊你就緒,直到你做操作導致那個文件描述符不再為就緒狀態(tài)
		     緩沖區(qū)未讀盡的數(shù)據(jù)不會導致epoll_wait返回, 新的數(shù)據(jù)寫入才會觸發(fā)(等文件描述符不再為就緒狀態(tài))		
			    struct epoll_event event
			    event.events = EPOLLIN | EPOLLET

LT工作模式:水平觸發(fā)————只要有數(shù)據(jù)都會觸發(fā)(默認采用模式)
    作用:內(nèi)核告訴你一個文件描述符是否就緒,然后可以對這個就緒的fd進行io操作,如果你不做任何操作,內(nèi)核還會繼續(xù)通知你
	        緩沖區(qū)未讀盡的數(shù)據(jù)會導致epoll_wait返回(繼續(xù)通知你)
	
結(jié)論:epoll 的 ET模式, 高效模式,但是只支持 非阻塞模式
             --- 忙輪詢:用于在計算機系統(tǒng)中處理硬件中斷
                     忙輪詢是一種不進入內(nèi)核的方式,它在用戶空間中輪詢檢測硬件狀態(tài)
                     及時響應硬件的中斷請求,避免CPU在中斷服務程序中處理完所有的中斷請求后,又再次觸發(fā)中斷

		struct epoll_event event;
		event.events = EPOLLIN | EPOLLET;

		epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);	

		int flg = fcntl(cfd, F_GETFL);	 非阻塞
		flg |= O_NONBLOCK;
		fcntl(cfd, F_SETFL, flg);

代碼實現(xiàn)?

#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>

#define MAXLINE 10

int main(int argc, char *argv[])
{
    int efd, i;
    int pfd[2];
    pid_t pid;
    char buf[MAXLINE], ch = 'a';

    pipe(pfd);
    pid = fork();

    if (pid == 0) {             //子 寫
        close(pfd[0]);
        while (1) {
            //aaaa\n
            for (i = 0; i < MAXLINE/2; i++)
                buf[i] = ch;
            buf[i-1] = '\n';
            ch++;
            //bbbb\n
            for (; i < MAXLINE; i++)
                buf[i] = ch;
            buf[i-1] = '\n';
            ch++;
            //aaaa\nbbbb\n
            write(pfd[1], buf, sizeof(buf));
            sleep(5);
        }
        close(pfd[1]);

    } else if (pid > 0) {       //父 讀
        struct epoll_event event;
        struct epoll_event resevent[10];          //epoll_wait就緒返回event
        int res, len;

        close(pfd[1]);
        efd = epoll_create(10);

        event.events = EPOLLIN | EPOLLET;         // ET 邊沿觸發(fā)
       // event.events = EPOLLIN;                 // LT 水平觸發(fā) (默認)
        event.data.fd = pfd[0];
        epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);

        while (1) {
            res = epoll_wait(efd, resevent, 10, -1);
            printf("res %d\n", res);
            if (resevent[0].data.fd == pfd[0]) {
                len = read(pfd[0], buf, MAXLINE/2);
                write(STDOUT_FILENO, buf, len);
            }
        }

        close(pfd[0]);
        close(efd);

    } else {
        perror("fork");
        exit(-1);
    }

    return 0;
}

4.1 server.c

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>

#define MAXLINE 10
#define SERV_PORT 9000

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    int efd;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 20);

    struct epoll_event event;
    struct epoll_event resevent[10];
    int res, len;

    efd = epoll_create(10);
    event.events = EPOLLIN | EPOLLET;     /* ET 邊沿觸發(fā) */
    //event.events = EPOLLIN;                 /* 默認 LT 水平觸發(fā) */

    printf("Accepting connections ...\n");

    cliaddr_len = sizeof(cliaddr);
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    printf("received from %s at PORT %d\n",
            inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
            ntohs(cliaddr.sin_port));

    event.data.fd = connfd;
    epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);

    while (1) {
        res = epoll_wait(efd, resevent, 10, -1);

        printf("res %d\n", res);
        if (resevent[0].data.fd == connfd) {
            len = read(connfd, buf, MAXLINE/2);         //readn(500)   
            write(STDOUT_FILENO, buf, len);
        }
    }

    return 0;
}

4.2 client.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define MAXLINE 10
#define SERV_PORT 9000

int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    char buf[MAXLINE];
    int sockfd, i;
    char ch = 'a';

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);

    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    while (1) {
        //aaaa\n
        for (i = 0; i < MAXLINE/2; i++)
            buf[i] = ch;
        buf[i-1] = '\n';
        ch++;
        //bbbb\n
        for (; i < MAXLINE; i++)
            buf[i] = ch;
        buf[i-1] = '\n';
        ch++;
        //aaaa\nbbbb\n
        write(sockfd, buf, sizeof(buf));
        sleep(5);
    }
    close(sockfd);

    return 0;
}

5.epoll 反應堆模型

作用:提高網(wǎng)絡IO處理的效率



epoll ET模式 + 非阻塞、輪詢 + void *ptr
    void *ptr:指向結(jié)構(gòu)體,該結(jié)構(gòu)體包含socket、地址、端口等信息


原來:epoll實現(xiàn)多路IO轉(zhuǎn)接思路
		socket、bind、listen -- epoll_create 創(chuàng)建監(jiān)聽 紅黑樹 --  返回 epfd -- epoll_ctl() 向樹上添加一個監(jiān)聽fd -- while(1)--

		-- epoll_wait 監(jiān)聽 -- 對應監(jiān)聽fd有事件產(chǎn)生 -- 返回 監(jiān)聽滿足數(shù)組。 -- 判斷返回數(shù)組元素 -- lfd滿足 -- Accept -- cfd 滿足 

		-- read() --- 小->大 -- write回去


反應堆:不但要監(jiān)聽 cfd 的讀事件、還要監(jiān)聽cfd的寫事件

		socket、bind、listen -- epoll_create 創(chuàng)建監(jiān)聽 紅黑樹 --  返回 epfd -- epoll_ctl() 向樹上添加一個監(jiān)聽fd -- while(1)--

		-- epoll_wait 監(jiān)聽 -- 對應監(jiān)聽fd有事件產(chǎn)生 -- 返回 監(jiān)聽滿足數(shù)組。 -- 判斷返回數(shù)組元素 -- lfd滿足 -- Accept -- cfd 滿足 

		-- read() --- 小->大 
		
		-- cfd從監(jiān)聽紅黑樹上摘下 -- EPOLLOUT -- 回調(diào)函數(shù) -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到紅黑上監(jiān)聽“寫”事件-- 等待 epoll_wait 返回 -- 說明 cfd 可寫 -- write回去 
		
		-- cfd從監(jiān)聽紅黑樹上摘下 -- EPOLLIN -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到紅黑上監(jiān)聽“讀”事件 -- epoll_wait 監(jiān)聽

		


eventset函數(shù):設置回調(diào)函數(shù)
				lfd --> acceptconn()
				cfd --> recvdata();
				cfd --> senddata();
				
eventadd函數(shù):將一個fd, 添加到 監(jiān)聽紅黑樹
              設置監(jiān)聽讀事件,還是監(jiān)聽寫事件


網(wǎng)絡編程中: read --- recv()            write --- send();

epoll基于非阻塞I/O事件驅(qū)動文章來源地址http://www.zghlxwxcb.cn/news/detail-668987.html

/*
 *epoll基于非阻塞I/O事件驅(qū)動
 */
#include <stdio.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#define MAX_EVENTS  1024                                    						//監(jiān)聽上限數(shù)
#define BUFLEN 4096
#define SERV_PORT   8080															//默認端口號

void recvdata(int fd, int events, void *arg);
void senddata(int fd, int events, void *arg);

/* 描述就緒文件描述符相關(guān)信息 */
struct myevent_s {
    int fd;                                                 						//要監(jiān)聽的文件描述符
    int events;                                             						//對應的監(jiān)聽事件
    void *arg;                                              						//泛型參數(shù)
    void (*call_back)(int fd, int events, void *arg);       						//回調(diào)函數(shù)
    int status;                                             						//是否在監(jiān)聽:1->在紅黑樹上(監(jiān)聽), 0->不在(不監(jiān)聽)
    char buf[BUFLEN];
    int len;
    long last_active;                                       						//記錄每次加入紅黑樹 g_efd 的時間值
};

int g_efd;                                                  						//全局變量, 保存epoll_create返回的文件描述符
struct myevent_s g_events[MAX_EVENTS+1];                    						//自定義結(jié)構(gòu)體類型數(shù)組. +1-->listen fd


	/*將結(jié)構(gòu)體 myevent_s 成員變量 初始化賦值*/
	void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg)
	{
		ev->fd = fd;
		ev->call_back = call_back;													//設置回調(diào)函數(shù)
		ev->events = 0;
		ev->arg = arg;
		ev->status = 0;
		memset(ev->buf, 0, sizeof(ev->buf));
		ev->len = 0;
		ev->last_active = time(NULL);                       						//調(diào)用eventset函數(shù)的時間

		return;
	}

	/* 向 epoll監(jiān)聽的紅黑樹 添加一個 文件描述符 */
	//eventadd函數(shù): 將一個fd添加到監(jiān)聽紅黑樹, 設置監(jiān)聽讀事件還是寫事件
	//eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);
	void eventadd(int efd, int events, struct myevent_s *ev)
	{
		struct epoll_event epv = {0, {0}};
		int op;
		epv.data.ptr = ev;
		epv.events = ev->events = events;      									    //EPOLLIN 或 EPOLLOUT

		if (ev->status == 0) {                                          			//已經(jīng)在紅黑樹 g_efd 里
			op = EPOLL_CTL_ADD;                 									//將其加入紅黑樹 g_efd, 并將status置1
			ev->status = 1;
		}

		if (epoll_ctl(efd, op, ev->fd, &epv) < 0)                       			//實際添加/修改
			printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);
		else
			printf("event add OK [fd=%d], op=%d, events[%0X]\n", ev->fd, op, events);

		return ;
	}

	/* 從epoll 監(jiān)聽的 紅黑樹中刪除一個 文件描述符*/
	void eventdel(int efd, struct myevent_s *ev)
	{
		struct epoll_event epv = {0, {0}};

		if (ev->status != 1)                                        				//不在紅黑樹上
			return ;

		//epv.data.ptr = ev;
		epv.data.ptr = NULL;
		ev->status = 0;                                             				//修改狀態(tài)
		epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv);                				//從紅黑樹 efd 上將 ev->fd 摘除

		return ;
	}

			/*  當有文件描述符就緒, epoll返回, 調(diào)用該函數(shù) 與客戶端建立鏈接 */
			void acceptconn(int lfd, int events, void *arg)
			{
				struct sockaddr_in cin;
				socklen_t len = sizeof(cin);
				int cfd, i;

				if ((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1) {
					if (errno != EAGAIN && errno != EINTR) {
						/* 暫時不做出錯處理 */
					}
					printf("%s: accept, %s\n", __func__, strerror(errno));
					return ;
				}

				do {
					for (i = 0; i < MAX_EVENTS; i++)                               			//從全局數(shù)組g_events中找一個空閑元素
						if (g_events[i].status == 0)                                		//類似于select中找值為-1的元素
							break;                                                  		//跳出 for

					if (i == MAX_EVENTS) {
						printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS);
						break;                                                      		//跳出do while(0) 不執(zhí)行后續(xù)代碼
					}

					int flag = 0;
					if ((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) {             		//將cfd也設置為非阻塞
						printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
						break;
					}

					/* 給cfd設置一個 myevent_s 結(jié)構(gòu)體, 回調(diào)函數(shù) 設置為 recvdata */
					eventset(&g_events[i], cfd, recvdata, &g_events[i]);   
					eventadd(g_efd, EPOLLIN, &g_events[i]);                         		//將cfd添加到紅黑樹g_efd中,監(jiān)聽讀事件

				} while(0);

				printf("new connect [%s:%d][time:%ld], pos[%d]\n", 
						inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i);
				return ;
			}

			//epoll反應堆-wait被觸發(fā)后read和write回調(diào)及監(jiān)聽	
				void recvdata(int fd, int events, void *arg)
				{
					struct myevent_s *ev = (struct myevent_s *)arg;
					int len;

					len = recv(fd, ev->buf, sizeof(ev->buf), 0);            				//讀文件描述符, 數(shù)據(jù)存入myevent_s成員buf中

					eventdel(g_efd, ev);        //將該節(jié)點從紅黑樹上摘除

					if (len > 0) {

						ev->len = len;
						ev->buf[len] = '\0';                                				//手動添加字符串結(jié)束標記
						printf("C[%d]:%s\n", fd, ev->buf);

						eventset(ev, fd, senddata, ev);                     				//設置該 fd 對應的回調(diào)函數(shù)為 senddata
						eventadd(g_efd, EPOLLOUT, ev);                      				//將fd加入紅黑樹g_efd中,監(jiān)聽其寫事件

					} else if (len == 0) {
						close(ev->fd);
						/* ev-g_events 地址相減得到偏移元素位置 */
						printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);
					} else {
						close(ev->fd);
						printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
					}

					return;
				}

				void senddata(int fd, int events, void *arg)
				{
					struct myevent_s *ev = (struct myevent_s *)arg;
					int len;

					len = send(fd, ev->buf, ev->len, 0);                    				//直接將數(shù)據(jù) 回寫給客戶端。未作處理

					eventdel(g_efd, ev);                                					//從紅黑樹g_efd中移除

					if (len > 0) {

						printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);
						eventset(ev, fd, recvdata, ev);                     				//將該fd的 回調(diào)函數(shù)改為 recvdata
						eventadd(g_efd, EPOLLIN, ev);                       				//從新添加到紅黑樹上, 設為監(jiān)聽讀事件

					} else {
						close(ev->fd);                                      				//關(guān)閉鏈接
						printf("send[fd=%d] error %s\n", fd, strerror(errno));
					}

					return ;
				}


	/*創(chuàng)建 socket, 初始化lfd */
	void initlistensocket(int efd, short port)
	{
		struct sockaddr_in sin;

		//將socket設為lfd非阻塞
		int lfd = socket(AF_INET, SOCK_STREAM, 0);
		fcntl(lfd, F_SETFL, O_NONBLOCK);                                            

		//設置地址結(jié)構(gòu)
		memset(&sin, 0, sizeof(sin));                                               //bzero(&sin, sizeof(sin))
		sin.sin_family = AF_INET;
		sin.sin_addr.s_addr = INADDR_ANY;
		sin.sin_port = htons(port);

		bind(lfd, (struct sockaddr *)&sin, sizeof(sin));

		listen(lfd, 20);

		/* void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg);  */
		 /*把g_events數(shù)組的最后一個元素設置為lfd,回調(diào)函數(shù)設置為acceptconn*/
			eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);

		/* void eventadd(int efd, int events, struct myevent_s *ev) */
		/*掛上樹*/
			eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);

		return ;
	}




int main(int argc, char *argv[])
{
	/*選擇默認端口號或指定端口號*/
    unsigned short port = SERV_PORT;

    if (argc == 2)
		//使用用戶指定端口.如未指定,用默認端口
        port = atoi(argv[1]);                           								

	//創(chuàng)建紅黑樹,返回給全局 g_efd
    g_efd = epoll_create(MAX_EVENTS+1);                 								 
    if (g_efd <= 0)
        printf("create efd in %s err %s\n", __func__, strerror(errno));

	//初始化監(jiān)聽socket
    initlistensocket(g_efd, port);                      								

	//創(chuàng)建一個系統(tǒng)的epoll_event的數(shù)組,與my_events的規(guī)模相同
		struct epoll_event events[MAX_EVENTS+1];           								//保存已經(jīng)滿足就緒事件的文件描述符數(shù)組 
		printf("server running:port[%d]\n", port);

    int checkpos = 0, i;
    while (1) {
        /* 超時驗證,每次測試100個鏈接,不測試listenfd 當客戶端60秒內(nèi)沒有和服務器通信,則關(guān)閉此客戶端鏈接 */	
			long now = time(NULL);                          							//當前時間
			for (i = 0; i < 100; i++, checkpos++) {         							//一次循環(huán)檢測100個。 使用checkpos控制檢測對象
				if (checkpos == MAX_EVENTS)
					checkpos = 0;
				if (g_events[checkpos].status != 1)        								//不在紅黑樹 g_efd 上
					continue;

				long duration = now - g_events[checkpos].last_active;       			//時間間隔,客戶端不活躍的世間

				if (duration >= 60) {
					close(g_events[checkpos].fd);                           			//關(guān)閉與該客戶端鏈接
					printf("[fd=%d] timeout\n", g_events[checkpos].fd);
					eventdel(g_efd, &g_events[checkpos]);                   			//將該客戶端 從紅黑樹 g_efd移除
				}
			}

        /*監(jiān)聽紅黑樹g_efd, 將滿足的事件的文件描述符加至events數(shù)組中, 1秒沒有事件滿足, 返回 0*/
			int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);
			if (nfd < 0) {
				printf("epoll_wait error, exit\n");
				break;
			}

			for (i = 0; i < nfd; i++) {
				/*使用自定義結(jié)構(gòu)體myevent_s類型指針, 接收 聯(lián)合體data的void *ptr成員*/
				struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;  

				//cfd從監(jiān)聽紅黑樹上摘下  
				if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) {          	//讀就緒事件
					ev->call_back(ev->fd, events[i].events, ev->arg);
					//lfd  EPOLLIN  
				}
				if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) {         //寫就緒事件
					ev->call_back(ev->fd, events[i].events, ev->arg);
				}
			}
    }

    /* 退出前釋放所有資源 */
    return 0;
}

到了這里,關(guān)于Linux網(wǎng)絡編程:多路I/O轉(zhuǎn)接服務器(select poll epoll)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務器費用

相關(guān)文章

  • Linux高性能服務器編程 學習筆記 第五章 Linux網(wǎng)絡編程基礎API

    Linux高性能服務器編程 學習筆記 第五章 Linux網(wǎng)絡編程基礎API

    我們將從以下3方面討論Linux網(wǎng)絡API: 1.socket地址API。socket最開始的含義是一個IP地址和端口對(ip,port),它唯一表示了使用TCP通信的一端,本書稱其為socket地址。 2.socket基礎API。socket的主要API都定義在sys/socket.h頭文件中,包括創(chuàng)建socket、命名socket、監(jiān)聽socket、接受連接、發(fā)

    2024年02月07日
    瀏覽(41)
  • Linux學習之網(wǎng)絡編程3(高并發(fā)服務器)

    Linux學習之網(wǎng)絡編程3(高并發(fā)服務器)

    Linux網(wǎng)絡編程我是看視頻學的,Linux網(wǎng)絡編程,看完這個視頻大概網(wǎng)絡編程的基礎差不多就掌握了。這個系列是我看這個Linux網(wǎng)絡編程視頻寫的筆記總結(jié)。 問題: 根據(jù)上一個筆記,我們可以寫出一個簡單的服務端和客戶端通信,但是我們發(fā)現(xiàn)一個問題——服務器只能連接一個

    2024年02月01日
    瀏覽(28)
  • Linux網(wǎng)絡編程:多進程 多線程_并發(fā)服務器

    文章目錄: 一:wrap常用函數(shù)封裝 wrap.h? wrap.c server.c封裝實現(xiàn) client.c封裝實現(xiàn) 二:多進程process并發(fā)服務器 server.c服務器 實現(xiàn)思路 代碼邏輯? client.c客戶端 三:多線程thread并發(fā)服務器 server.c服務器 實現(xiàn)思路 代碼邏輯? client.c客戶端 ???? ??read 函數(shù)的返回值 wrap.h? wrap

    2024年02月12日
    瀏覽(31)
  • Linux網(wǎng)絡編程:Socket套接字編程(Server服務器 Client客戶端)

    Linux網(wǎng)絡編程:Socket套接字編程(Server服務器 Client客戶端)

    文章目錄: 一:定義和流程分析 1.定義 2.流程分析? 3.網(wǎng)絡字節(jié)序 二:相關(guān)函數(shù)? IP地址轉(zhuǎn)換函數(shù)inet_pton inet_ntop(本地字節(jié)序 網(wǎng)絡字節(jié)序) socket函數(shù)(創(chuàng)建一個套接字) bind函數(shù)(給socket綁定一個服務器地址結(jié)構(gòu)(IP+port)) listen函數(shù)(設置最大連接數(shù)或者說能同時進行三次握手的最

    2024年02月12日
    瀏覽(35)
  • Linux下網(wǎng)絡編程(3)——socket編程實戰(zhàn),如何構(gòu)建一個服務器和客戶端連接

    Linux下網(wǎng)絡編程(3)——socket編程實戰(zhàn),如何構(gòu)建一個服務器和客戶端連接

    ????????經(jīng)過前幾篇的介紹,本文我們將進行編程實戰(zhàn),實現(xiàn)一個簡單地服務器和客戶端應用程序。 編寫服務器程序 ???????? 編寫服務器應用程序的流程如下: ????????①、調(diào)用 socket()函數(shù)打開套接字,得到套接字描述符; ????????②、調(diào)用 bind()函數(shù)將套接字

    2024年02月03日
    瀏覽(30)
  • Linux網(wǎng)絡編程:線程池并發(fā)服務器 _UDP客戶端和服務器_本地和網(wǎng)絡套接字

    Linux網(wǎng)絡編程:線程池并發(fā)服務器 _UDP客戶端和服務器_本地和網(wǎng)絡套接字

    文章目錄: 一:線程池模塊分析 threadpool.c 二:UDP通信 1.TCP通信和UDP通信各自的優(yōu)缺點 2.UDP實現(xiàn)的C/S模型 server.c client.c 三:套接字? 1.本地套接字 2.本地套 和 網(wǎng)絡套對比 server.c client.c threadpool.c ? server.c client.c server.c client.c

    2024年02月11日
    瀏覽(91)
  • Linux網(wǎng)絡編程:Socket服務器和客戶端實現(xiàn)雙方通信

    Linux網(wǎng)絡編程:Socket服務器和客戶端實現(xiàn)雙方通信

    目錄 一,什么是網(wǎng)絡編程 二,為什么使用端口號 三,TCP協(xié)議與UDP協(xié)議 ①TCP(傳輸控制協(xié)議) ②UDP(用戶數(shù)據(jù)報協(xié)議,User Data Protocol) ③總結(jié)歸納 四,Socket服務器和客戶端的開發(fā)流程 五,服務器和客戶端相關(guān)API說明 ①socket()函數(shù) ②bind()函數(shù) ③listen()函數(shù) ④accept()函數(shù) ⑤客戶端

    2024年02月11日
    瀏覽(33)
  • 【Linux網(wǎng)絡編程】高并發(fā)服務器框架 線程池介紹+線程池封裝

    【Linux網(wǎng)絡編程】高并發(fā)服務器框架 線程池介紹+線程池封裝

    前言 一、線程池介紹 ??線程池基本概念 ??線程池組成部分 ??線程池工作原理? 二、線程池代碼封裝 ??main.cpp ??ThreadPool.h ??ThreadPool.cpp ??ChildTask.h? ??ChildTask.cpp ??BaseTask.h ??BaseTask.cpp 三、測試效果 四、總結(jié) ??創(chuàng)建線程池的好處 本文主要學習 Linux內(nèi)核編程 ,結(jié)合

    2024年01月16日
    瀏覽(33)
  • Linux網(wǎng)絡編程:socket、客戶端服務器端使用socket通信(TCP)

    Linux網(wǎng)絡編程:socket、客戶端服務器端使用socket通信(TCP)

    socket(套接字),用于網(wǎng)絡中不同主機間進程的通信。 socket是一個偽文件,包含讀緩沖區(qū)、寫緩沖區(qū)。 socket必須成對出現(xiàn)。 socket可以建立主機進程間的通信,但需要協(xié)議(IPV4、IPV6等)、port端口、IP地址。 ??????? ?(1)創(chuàng)建流式socket套接字。 ? ? ? ? ? ? ? ? a)此s

    2024年02月11日
    瀏覽(33)
  • 服務器(I/O)之多路轉(zhuǎn)接

    服務器(I/O)之多路轉(zhuǎn)接

    1、阻塞等待:在內(nèi)核將數(shù)據(jù)準備好之前,系統(tǒng)調(diào)用會一直等待。所有的套接字,默認都是阻塞方式。 2、非阻塞等待:如果內(nèi)核沒有將數(shù)據(jù)準備好,系統(tǒng)調(diào)用仍然會返回,并且會返回EWUOLDBLOCK或者EAGAIN錯誤碼。 3、信號驅(qū)動:內(nèi)核將數(shù)據(jù)準備好的時候,使用SIGIO信號通知應用程

    2024年02月09日
    瀏覽(12)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包