网络编程——epoll模型编程

Linux网络编程——epoll原理详解及epoll反应堆模型 设想一个场景:有100万用户同时与一个进程保持着TCP连接,而每一时刻只有几十个或几百个TCP连接是活跃的(接收TCP包),也就是说在每一时刻进程只需要处理这100万连接中的一小部分连接。那么,如何才能高效的处理这种场景呢?进程是否在每次询问操作系统收集有事件发生的TCP连接时,把这100万个连接告诉操作系统,然后由操作系统找出其中有事件发生的几百个连接呢?实际上,在Linux2.4版本以前,那时的select或者poll事件驱动方式是这样做的。 阅读详情

目的

(1) 在理解I/O复用模型速度慢的主要原因的基础上,理解和掌握Linux下对I/O复用模型的改进和扩展——epoll模型的工作原理;
(2) 理解和掌握epoll模型中条件触发和边缘触发两者在运作机制上的区别;
(3) 分别使用epoll模型的条件触发和边缘触发实现服务器编程。

内容

(1) 使用条件触发方式实现回声服务器端(及客户端);
(2) 使用边缘触发方式实现回声服务器端(及客户端);
(3) 实现聊天服务器端,使其可以在连接到服务器端的所有客户端之间交换消息。按照条件触发方式和边缘触发方式分别实现epoll服务器端(及客户端)。

代码及测试结果

(1) 使用条件触发方式实现回声服务器端(及客户端);

//echo_EPLTserv.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE 4
#define EPOLL_SIZE 50
void error_handling(char *buf);

int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;
	socklen_t adr_sz;
	int str_len, i;
	char buf[BUF_SIZE];

	struct epoll_event *ep_events;
	struct epoll_event event;
	int epfd, event_cnt;

	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));
	
	if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");

	epfd=epoll_create(EPOLL_SIZE);
	ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);

	event.events=EPOLLIN;
	event.data.fd=serv_sock;	
	epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

	while(1)
	{
		event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
		if(event_cnt==-1)
		{
			puts("epoll_wait() error");
			break;
		}

		puts("return epoll_wait");
		for(i=0; i<event_cnt; i++)
		{
			if(ep_events[i].data.fd==serv_sock)
			{
				adr_sz=sizeof(clnt_adr);
				clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
				event.events=EPOLLIN;
				event.data.fd=clnt_sock;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
				printf("connected client: %d \n", clnt_sock);
			}
			else
			{
					str_len=read(ep_events[i].data.fd, buf, BUF_SIZE);
					if(str_len==0)    // close request!
					{
						epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
						close(ep_events[i].data.fd);
						printf("closed client: %d \n", ep_events[i].data.fd);
					}
					else
					{
						write(ep_events[i].data.fd, buf, str_len);    // echo!
					}
			}
		}
	}
	close(serv_sock);
	close(epfd);
	return 0;
}

void error_handling(char *buf)
{
	fputs(buf, stderr);
	fputc('\n', stderr);
	exit(1);
}
//echo_client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int sock;
	char message[BUF_SIZE];
	int str_len;
	struct sockaddr_in serv_adr;

	if(argc!=3) {
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_STREAM, 0);   
	if(sock==-1)
		error_handling("socket() error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_adr.sin_port=htons(atoi(argv[2]));
	
	if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("connect() error!");
	else
		puts("Connected...........");
	
	while(1) 
	{
		fputs("Input message(Q to quit): ", stdout);
		fgets(message, BUF_SIZE, stdin);
		
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
			break;

		write(sock, message, strlen(message));
		str_len=read(sock, message, BUF_SIZE-1);
		message[str_len]=0;
		printf("Message from server: %s", message);
	}
	
	close(sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

测试结果:
在这里插入图片描述
在这里插入图片描述

(2) 使用边缘触发方式实现回声服务器端(及客户端);

//echo_EPETserv.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE 4
#define EPOLL_SIZE 50
void setnonblockingmode(int fd);
void error_handling(char *buf);

int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;
	socklen_t adr_sz;
	int str_len, i;
	char buf[BUF_SIZE];

	struct epoll_event *ep_events;
	struct epoll_event event;
	int epfd, event_cnt;

	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));
	
	if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");

	epfd=epoll_create(EPOLL_SIZE);
	ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);

	setnonblockingmode(serv_sock);
	event.events=EPOLLIN;
	event.data.fd=serv_sock;	
	epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

	while(1)
	{
		event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
		if(event_cnt==-1)
		{
			puts("epoll_wait() error");
			break;
		}

		puts("return epoll_wait");
		for(i=0; i<event_cnt; i++)
		{
			if(ep_events[i].data.fd==serv_sock)
			{
				adr_sz=sizeof(clnt_adr);
				clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
				setnonblockingmode(clnt_sock);
				event.events=EPOLLIN|EPOLLET;
				event.data.fd=clnt_sock;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
				printf("connected client: %d \n", clnt_sock);
			}
			else
			{
					while(1)
					{
						str_len=read(ep_events[i].data.fd, buf, BUF_SIZE);
						if(str_len==0)    // close request!
						{
							epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
							close(ep_events[i].data.fd);
							printf("closed client: %d \n", ep_events[i].data.fd);
							break;
						}
						else if(str_len<0)
						{
							if(errno==EAGAIN)
								break;
						}
						else
						{
							write(ep_events[i].data.fd, buf, str_len);    // echo!
						}
				}
			}
		}
	}
	close(serv_sock);
	close(epfd);
	return 0;
}

void setnonblockingmode(int fd)
{
	int flag=fcntl(fd, F_GETFL, 0);
	fcntl(fd, F_SETFL, flag|O_NONBLOCK);
}
void error_handling(char *buf)
{
	fputs(buf, stderr);
	fputc('\n', stderr);
	exit(1);
}


//客户端代码同1

测试结果:
在这里插入图片描述
在这里插入图片描述

(3) 实现聊天服务器端,使其可以在连接到服务器端的所有客户端之间交换消息。按照条件触发方式和边缘触发方式分别实现epoll服务器端(及客户端)。

注意:以下程序能够实现类似群聊功能,也能够实现某个客户端对某个客户端发起聊天

条件触发:

//服务器端:
//EPLTserv.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE 100
#define EPOLL_SIZE 50
#define MAX_CLNT 50
void error_handling(char *buf);
void send_msg(char * msg, int len);

int clnt_cnt=0;//已连接的数量

typedef struct//关于存储客户端套接字的相关信息的结构体
{
	char name[50];//客户端姓名
	int clientsock;//客户端套接字
	int flag;//标志位,选择群聊或者个人聊天的标志位
	int flag1;//标志位,标志某些发给服务器的消息不需要转发
	char Targetname[50];//选择聊天的对象,不适于群聊
	int Targetsock;//选择聊天的客户端套接字
}Client; 

Client clnt_socks[MAX_CLNT];//结构体数组



int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;
	socklen_t adr_sz;
	int str_len,i,j,m,l;
	char buf[BUF_SIZE];

	char temp[BUF_SIZE];//临时数组变量
	char clientname[250];//存贮所有客户端的姓名
	int fp=0;//存贮指针

	struct epoll_event *ep_events;
	struct epoll_event event;
	int epfd, event_cnt;

	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));
	
	if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");

    //epoll模型的创建
	epfd=epoll_create(EPOLL_SIZE);
	ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);

	event.events=EPOLLIN;
	event.data.fd=serv_sock;	
	epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

	while(1)
	{
		event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
		if(event_cnt==-1)
		{
			puts("epoll_wait() error");
			break;
		}

		for(i=0; i<event_cnt; i++)
		{
			if(ep_events[i].data.fd==serv_sock)//如果是服务器套接字
			{
				memset(buf,0,BUF_SIZE);
				adr_sz=sizeof(clnt_adr);
				clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
				event.events=EPOLLIN;
				event.data.fd=clnt_sock;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
				clnt_socks[clnt_cnt].clientsock=clnt_sock;//将套接字描述符加入套接字数组
				read(clnt_sock,buf,BUF_SIZE);//读取客户端发送的名字
				memcpy(clnt_socks[clnt_cnt++].name,buf,strlen(buf));//将名字存贮
				printf("connected client: %d \n", clnt_sock);
			}
			else//如果是客户端套接字
			{
				memset(buf,0,BUF_SIZE);
				for(j=0;j<clnt_cnt;j++)//在客户端数组中寻找该套接字的存储序号
				{
					if(clnt_socks[j].clientsock==ep_events[i].data.fd)
						break;
				}
			    str_len=read(ep_events[i].data.fd, buf, BUF_SIZE);//接收消息
				if(str_len==0)    //关闭该套接字
				{
				    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
					close(ep_events[i].data.fd);
					printf("closed client: %d \n", ep_events[i].data.fd);
					for(m=0; m<clnt_cnt; m++) 
					{
						if(ep_events[i].data.fd==clnt_socks[m].clientsock)
						{
							while(m<clnt_cnt-1)
							{
								clnt_socks[m]=clnt_socks[m+1];
								m++;
							}
							memset(clnt_socks[m].name,0,50);
							break;
						}
					}
					clnt_cnt--;
				}
				else//接收消息
				{
					if(strcmp(buf,"all")==0)//如果是客户端发送的all标志,则接下来该套接字发送的消息为群发方式
					{
						clnt_socks[j].flag=1;
						clnt_socks[j].flag1=0;
					}
					else if(strcmp(buf,"aa")==0)//如果是客户端发送的aa标志,则将已连接的套接字姓名发给该客户端
					{
						memset(clientname,0,250);
						clnt_socks[j].flag=0;
						fp=0;
                        for(m=0;m<clnt_cnt;m++)
                        {
                        	l=strlen(clnt_socks[m].name);
                        	memcpy(clientname+fp,clnt_socks[m].name,l);
                        	fp=fp+l;
                        	memcpy(clientname+fp,"#",1);
                        	fp=fp+1;
                        }
                        clientname[fp]='\0';
                        write(clnt_socks[j].clientsock,clientname,strlen(clientname));
					}
					else
					{
						memset(temp,0,BUF_SIZE);
                        memcpy(temp,buf,2);
						if(strcmp(temp,"cc")==0)//如果是客户端发送的cc标志,则将在客户端数组中查找通信人名,选择单独聊天
						{
							memset(temp,0,BUF_SIZE);
							memcpy(temp,buf+2,strlen(buf)-2);
							for(m=0;m<clnt_cnt;m++)
							{
								if(strcmp(temp,clnt_socks[m].name)==0)//如果找到了,则标志相关标志位
								{
									clnt_socks[j].flag=2;
									clnt_socks[j].flag1=0;
									memset(clnt_socks[j].Targetname,0,50);
									memcpy(clnt_socks[j].Targetname,temp,strlen(temp));
									clnt_socks[j].Targetsock = clnt_socks[m].clientsock;
									printf("%d",clnt_socks[j].Targetsock);
									break;
								}
							}
							if(m==clnt_cnt)//如果没有找到,则回复无该用户
							{
								memcpy(temp,"The client is down!",19);
								clnt_socks[j].flag=0;
								write(clnt_socks[j].clientsock,temp,19);
							}
				    	}
					}
					if(clnt_socks[j].flag1==1&&clnt_socks[j].flag==1)//群发
					{
						memset(temp,0,BUF_SIZE);
						memcpy(temp,buf,strlen(buf));
						memset(buf,0,BUF_SIZE);
						memcpy(buf,"[From group]",12);
						memcpy(buf+12,temp,strlen(temp));
						send_msg(buf,strlen(buf));
					}
					if(clnt_socks[j].flag1==1&&clnt_socks[j].flag==2)//单独发送
					{
						memset(temp,0,BUF_SIZE);
						memcpy(temp,"[From ",6);
						l=strlen(clnt_socks[j].name);
						memcpy(temp+6,clnt_socks[j].name,l);
                        memcpy(temp+6+l,"]",1);
                        memcpy(temp+7+l,buf,strlen(buf));
						write(clnt_socks[j].Targetsock,temp,strlen(temp));
					}
					clnt_socks[j].flag1=1;//标志位
				}
			}
		}
	}
	close(serv_sock);
	close(epfd);
	return 0;
}


void send_msg(char * msg, int len)   // send to all
{
	int i;
	int n;
	for(i=0; i<clnt_cnt; i++)
		write(clnt_socks[i].clientsock, msg, len);
}
void error_handling(char *buf)
{
	fputs(buf, stderr);
	fputc('\n', stderr);
	exit(1);
}
//客户端:
//client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>

#define BUF_SIZE 1024
void error_handling(char *message);
void *sender(void *arg);//发送函数
void *receiver(void *arg);//接收函数
char name[20];//用户名

int main(int argc, char *argv[])
{
	int sock;
	char message[BUF_SIZE];
	int str_len,len;
	struct sockaddr_in serv_adr;
    pthread_t p1,p2;//线程变量

	if(argc!=4) {
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_STREAM, 0);   
	if(sock==-1)
		error_handling("socket() error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_adr.sin_port=htons(atoi(argv[2]));
	len = strlen(argv[3]);
    memcpy(name,argv[3],len);//将输入的用户名赋给name
    name[len]=0;
	
	//创建两个线程,分别负责发送与接收数据
	if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("connect() error!");
	else
	{
		write(sock,name,len);
		puts("Connected...........");
	}

	if(pthread_create(&p1,NULL,sender,(void*)&sock)!=0)
	{
		puts("send pthread_create() error");
		return -1;
	};
	if(pthread_create(&p2,NULL,receiver,(void*)&sock)!=0)
	{
		puts("receive pthread_create() error");
		return -1;
	};

	pthread_join(p1,NULL);
	pthread_join(p2,NULL);
	return 0;
}


void *sender(void *arg)
{
	int sock=*((int*)arg);
	int len=strlen(name);
	char buffer[BUF_SIZE];
	char temp[BUF_SIZE];//临时变量

	int choice;//选择位

	while(1)//循环
	{
		puts("\n请输入你需要的服务:1[群聊]2[选择客户端发起聊天]3[查看已连接用户]4[退出]");
		scanf("%d",&choice);
		getchar();
		if(choice==1)
		{
			memset(buffer,0,BUF_SIZE);
			memcpy(buffer,"all",3);
			write(sock,buffer,strlen(buffer));//发送all标志信息,表示需要群聊
			while(1)
			{
				printf("请输入需要发送的群聊消息:\n");
				memcpy(buffer,name,len);
				memcpy(buffer+len,":",1);
				fgets(buffer+len+1,BUF_SIZE-len-1,stdin);
				if(!strcmp(buffer+len+1,"q\n") || !strcmp(buffer+len+1,"Q\n"))
				{
					break;
				}
				l = strlen(buffer);
			    write(sock,buffer,l);
		    }
		}
		else if(choice==2)
		{
			memset(temp,0,BUF_SIZE);
			printf("请输入你需要通信的客户端姓名:");
			memcpy(temp,"cc",2);
			fgets(temp+2,BUF_SIZE,stdin);
		    write(sock,temp,strlen(temp)-1);//发送"cc+通信对象的名字信息",表示需要与该用户单独通信
		    memset(buffer,0,BUF_SIZE);
		    memcpy(buffer,temp+2,strlen(temp)-3);
		    memset(temp,0,BUF_SIZE);
		    memcpy(temp,buffer,strlen(buffer));
			while(1)
			{
				printf("请输入发给%s的消息:\n",temp);
				memset(buffer,0,BUF_SIZE);
				memcpy(buffer,name,len);
				memcpy(buffer+len,":",1);
				fgets(buffer+len+1,BUF_SIZE-len-1,stdin);
				if(!strcmp(buffer+len+1,"q\n") || !strcmp(buffer+len+1,"Q\n"))
				{
					break;
				}
				write(sock,buffer,strlen(buffer));
			}
		}
		else if(choice==3)
		{
			memset(buffer,0,BUF_SIZE);
			memcpy(buffer,"aa",2);
			write(sock,buffer,strlen(buffer));//发送aa标志信息,表示需要显示当前在线用户
			sleep(2);
		}
		else
		{
			close(sock);//退出
			exit(0);
		}
	}
	return NULL;	
}
void *receiver(void *arg)
{
	int sock=*((int*)arg);
	int str_len;
	char message[BUF_SIZE];
	while(1)
	{
		str_len=read(sock,message,BUF_SIZE-1);
		if(str_len==-1)
			break;
		message[str_len]=0;
	    fputs(message,stdout);
	}
	return NULL;

}
void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

测试结果:
在这里插入图片描述

边缘触发:

//服务端:
//EPETserv1.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE 100
#define EPOLL_SIZE 50
#define MAX_CLNT 50
void error_handling(char *buf);
void setnonblockingmode(int fd);
void send_msg(char * msg, int len);

int clnt_cnt=0;//已连接的数量

typedef struct//关于存储客户端套接字的相关信息的结构体
{
	char name[50];//客户端姓名
	int clientsock;//客户端套接字
	int flag;//标志位,选择群聊或者个人聊天的标志位
	int flag1;//标志位,标志某些发给服务器的消息不需要转发
	char Targetname[50];//选择聊天的对象,不适于群聊
	int Targetsock;//选择聊天的客户端套接字
}Client; 

Client clnt_socks[MAX_CLNT];//结构体数组



int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;
	socklen_t adr_sz;
	int str_len,i,j,m,l;
	char buf[BUF_SIZE];

	char temp[BUF_SIZE];//临时数组变量
	char clientname[250];//存贮所有客户端的姓名
	int fp=0;//存贮指针

	struct epoll_event *ep_events;
	struct epoll_event event;
	int epfd, event_cnt;

	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));
	
	if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");

    //epoll模型的创建
	epfd=epoll_create(EPOLL_SIZE);
	ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);

    setnonblockingmode(serv_sock);
	event.events=EPOLLIN;
	event.data.fd=serv_sock;	
	epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

	while(1)
	{
		event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
		if(event_cnt==-1)
		{
			puts("epoll_wait() error");
			break;
		}

		for(i=0; i<event_cnt; i++)
		{
			if(ep_events[i].data.fd==serv_sock)//如果是服务器套接字
			{
				memset(buf,0,BUF_SIZE);
				adr_sz=sizeof(clnt_adr);
				clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
				setnonblockingmode(clnt_sock);
				event.events=EPOLLIN|EPOLLET;
				event.data.fd=clnt_sock;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
				clnt_socks[clnt_cnt].clientsock=clnt_sock;//将套接字描述符加入套接字数组
				str_len=read(clnt_sock,buf,BUF_SIZE);//读取客户端发送的名字
				memcpy(clnt_socks[clnt_cnt++].name,buf,strlen(buf));//将名字存贮
				printf("connected client: %d \n", clnt_sock);
			}
			else//如果是客户端套接字
			{
				memset(buf,0,BUF_SIZE);
				for(j=0;j<clnt_cnt;j++)//在客户端数组中寻找该套接字的存储序号
				{
					if(clnt_socks[j].clientsock==ep_events[i].data.fd)
						break;
				}
			    str_len=read(ep_events[i].data.fd, buf, BUF_SIZE);//接收消息
				if(str_len==0)    //关闭该套接字
				{
				    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
					close(ep_events[i].data.fd);
					printf("closed client: %d \n", ep_events[i].data.fd);
					for(m=0; m<clnt_cnt; m++) 
					{
						if(ep_events[i].data.fd==clnt_socks[m].clientsock)
						{
							while(m<clnt_cnt-1)
							{
								clnt_socks[m]=clnt_socks[m+1];
								m++;
							}
							memset(clnt_socks[m].name,0,50);
							break;
						}
					}
					clnt_cnt--;
				}
				else if(str_len<0){
					if(errno==EAGAIN)
						break;
				}
				else//接收消息
				{
					if(strcmp(buf,"all")==0)//如果是客户端发送的all标志,则接下来该套接字发送的消息为群发方式
					{
						clnt_socks[j].flag=1;
						clnt_socks[j].flag1=0;
					}
					else if(strcmp(buf,"aa")==0)//如果是客户端发送的aa标志,则将已连接的套接字姓名发给该客户端
					{
						memset(clientname,0,250);
						clnt_socks[j].flag=0;
						fp=0;
                        for(m=0;m<clnt_cnt;m++)
                        {
                        	l=strlen(clnt_socks[m].name);
                        	memcpy(clientname+fp,clnt_socks[m].name,l);
                        	fp=fp+l;
                        	memcpy(clientname+fp,"#",1);
                        	fp=fp+1;
                        }
                        clientname[fp]='\0';
                        write(clnt_socks[j].clientsock,clientname,strlen(clientname));
					}
					else
					{
						memset(temp,0,BUF_SIZE);
                        memcpy(temp,buf,2);
						if(strcmp(temp,"cc")==0)//如果是客户端发送的cc标志,则将在客户端数组中查找通信人名,选择单独聊天
						{
							memset(temp,0,BUF_SIZE);
							memcpy(temp,buf+2,strlen(buf)-2);
							for(m=0;m<clnt_cnt;m++)
							{
								if(strcmp(temp,clnt_socks[m].name)==0)//如果找到了,则标志相关标志位
								{
									clnt_socks[j].flag=2;
									clnt_socks[j].flag1=0;
									memset(clnt_socks[j].Targetname,0,50);
									memcpy(clnt_socks[j].Targetname,temp,strlen(temp));
									clnt_socks[j].Targetsock = clnt_socks[m].clientsock;
									printf("%d",clnt_socks[j].Targetsock);
									break;
								}
							}
							if(m==clnt_cnt)//如果没有找到,则回复无该用户
							{
								memcpy(temp,"The client is down!",19);
								clnt_socks[j].flag=0;
								write(clnt_socks[j].clientsock,temp,19);
							}
				    	}
					}
					if(clnt_socks[j].flag1==1&&clnt_socks[j].flag==1)//群发
					{
						memset(temp,0,BUF_SIZE);
						memcpy(temp,buf,strlen(buf));
						memset(buf,0,BUF_SIZE);
						memcpy(buf,"[From group]",12);
						memcpy(buf+12,temp,strlen(temp));
						send_msg(buf,strlen(buf));
					}
					if(clnt_socks[j].flag1==1&&clnt_socks[j].flag==2)//单独发送
					{
						memset(temp,0,BUF_SIZE);
						memcpy(temp,"[From ",6);
						l=strlen(clnt_socks[j].name);
						memcpy(temp+6,clnt_socks[j].name,l);
                        memcpy(temp+6+l,"]",1);
                        memcpy(temp+7+l,buf,strlen(buf));
						write(clnt_socks[j].Targetsock,temp,strlen(temp));
					}
					clnt_socks[j].flag1=1;//标志位
				}
			}
		}
	}
	close(serv_sock);
	close(epfd);
	return 0;
}


void send_msg(char * msg, int len)   // send to all
{
	int i;
	int n;
	for(i=0; i<clnt_cnt; i++)
		write(clnt_socks[i].clientsock, msg, len);
}
void setnonblockingmode(int fd)
{
	int flag=fcntl(fd, F_GETFL, 0);
	fcntl(fd, F_SETFL, flag|O_NONBLOCK);
}
void error_handling(char *buf)
{
	fputs(buf, stderr);
	fputc('\n', stderr);
	exit(1);
}

//客户端同上。

测试结果:
在这里插入图片描述
条件触发中,调用epoll_wait函数的次数=连接+释放+传输数据使用read的次数。而边缘触发中,传输只会调用一次epoll_wait函数。

内容3中将所有的客户端套接字描述符放到一个数组是为了存储客户端相关信息。同时实现了能够对指定客户端发送消息这一功能。在客户端显示界面中,虽然来自不同的消息显示有点杂,可以制作相关界面,将不同类的消息进行显示,这就相当于平时的聊天程序,群聊与私聊的功能都实现了

C++ - 网络编程模型 - Linux EPOLL 阅读详情

相关推荐

C#实现多路复用epoll模型实例(附完整源码)

C#实现多路复用epoll模型实例(附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 461

c#: 异步代码是如何解决高并发问题的?async/await、Task、IOCP/epoll

环境: window 10 centos 8.2 .netframework .netcore 一、异步和多线程出现的目的以及要解决的问题 1. 密集型I/O 2. 密集型运算 异步和多线程的关系: 二、ICOP和epoll select/poll/epoll iocp的去留 三、.net中的异步实现方式 1. 基于事件实现的异步编程(EAP) 2. 异步编程模型(APM) 3. 基于任务实现的异步编程(TAP) 四、async/await语法糖之状态机 五、Configuration(true)

火焰 7209

能在linux(epoll)运行基于C# .net standard2.0 写的socket框架,可使用于dotnet core程序集

能在linux(epoll)运行基于C# .net standard2.0 写的socket框架,可使用dotnet core程序集 安装NuGet: https://www.nuget.org/packages/socket.core/ Package Manager: Install-Package socket.core .Net CLI :dotnet add package socket.core Paket CLI:paket add socket.core 一:TCP模块介绍 服务端所在socket.core.Server命名空间下,分别为三种模式 push/pull/pack 客户端所在socket.core.Client命名空间下,分别为三种模式 push/pull/pack 主要流程与对应的方法和事件介绍. 注:connectId(int)代表着一个连接对象,data(byte[]),success(bool)

epoll函数的简单使用

server.c #include <stdio.h> #include <sys/epoll.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <unistd....

qq1187239259的博客 281

功能完整的epoll tcp服务器和C#客户端,包括接收超时处理、粘包拆包,对收到的数据进行CRC验证然后处理,可作为通用epoll tcp服务器。

基本实现原理: 1)粘包和拆包采用帧头里面加长度信息来做; 2)超时机制:采用一个单独的线程来判断,每次收到数据就更新接收时间。在单独的线程里面判断如果当前时间减去上次接收的时间超过20秒则认为超时,关闭该连接; 3)用到了epoll_event结构体中的 epoll_data.ptr 指针:把客户端的信息保存在 epoll_data.ptr 指针当中,当出现epoll事件的时候从该指针提取客户端信息,然后进行操作。 完整的c代码,可以直接使用gcc编译,编译方法:gcc CreatCRC.c main.c -lpthread 配套有一个C#客户端(vs2008工程,全部源代码),可以向服务器发送数据和接收服务器返回的数据。

epoll demo(LT和ET)

自己学习并改写的epoll代码,在Linux测试没问题,分别实现了epoll的LT模式和ET模式,初学者可以学一学

网络编程中epoll模型

epoll的两种模式ET和LT模式水平触发: 高电平代表1​ 只要缓冲区中有数据, 就一直通知边缘触发: 电平有变化就代表1​ 缓冲区中有数据只会通知一次, 之后再有数据才会通知.(若是读数据的时候没有读完, 则剩余的数据不会再通知, 直到有新的数据到来)

m0_65948738的博客 1487

功能完整的epoll tcp服务器和C#客户端,包括接收超时处理、粘包拆包,对收到的数据进行CRC验证然后处理

功能完整的epoll tcp服务器和C#客户端,包括接收超时处理、粘包拆包,对收到的数据进行CRC验证然后处理

【Linux网络编程】谈谈网络编程中的select、poll、epoll、Reactor、Proactor模型(上)

本文主要介绍BIO、NIO、IO多路复用(第一版:select、poll)。

theaipower的博客 2万+

【论文阅读】LIME:Low-light Image Enhancement via Illumination Map Estimation(笔记最全篇)

文章目录AbstractIntroductionContributionMethodSpeed-up Method(1):ALM子问题 T子问题 G子问题 Z和μ\muμSpeed-up Method(2):权重变量实验conclusion 今天要整理的一篇论文是使用传统的方法解决低照度的问题,Guo等人于2016年发表在IEEE上,论文《LIME:Low-light Image Enhance...

florrie 2万+

planning模块(6)-参考线的平滑(二次规划)

上一篇已经介绍了采样点生成锚点数据的过程,并且将所有采样点生成的锚点数据设置到了平滑器中.接下来继续介绍使用二次规划平滑参考线的算法过程.从锚点数据中提取坐标存入raw_point2d,提取横向边界存入anchorpoints_lateralbound,将起点和终点的横向边界设置为0,防止车在起点和终点位置偏离车道中心线.

让复杂的问题变得更容易理解 788

网络编程select,poll,epoll模型

本文资源全部来自于B站。

qq_61667084的博客 279

10年上位机开发踩过的20个坑:C#通信/线程/UI适配避坑指南

通信层:所有通信操作异步化、异常全覆盖,加断线重连+心跳检测,多设备隔离资源;线程层:UI更新必做跨线程判断,共享数据加锁,线程池限流,异常不静默;UI层:适配高分屏/DPI,用布局容器替代固定坐标,多屏场景绑定主屏幕。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 904

【Linux网络编程】谈谈网络编程中的select、poll、epoll、Reactor、Proactor模型(下)

这个模型也叫做基于线程架构的模型。

theaipower的博客 5770

使用Qt在FreeRTOS上构建MCU应用程序

如果我们想要在FreeRTOS上运行该应用程序,我们需要选择适用于我们处理器架构的交叉编译工具链,并将目标设备设置为我们的开发板。在该代码中,我们使用xTaskCreate()函数创建了一个名为“qt_task”的任务,并将其启动。首先,我们需要安装Qt for MCU开发环境。Qt for MCU是基于Qt 5开发的针对MCU的特殊版本,它可以生成最小体积的代码,从而适用于资源有限的嵌入式系统。如果我们想要在FreeRTOS上运行这个程序,我们需要将其包装在一个任务中,并在任务创建后启动Qt的事件循环。

ByteWhizX的博客 1815

RT-Thread GD32F4xx HWTIMER驱动

GD32F4xx RT-Thread 硬件定时器驱动调试使用记录

madao1234的博客 1482

朴素、Select、Poll和Epoll网络编程模型实现和分析——模型比较

经过之前四篇博文的介绍,可以大致清楚各种模型的编程步骤。现在我们来回顾下各种模型(转载请指明出于breaksoftware的csdn博客)模型编程步骤对比 《朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型》中介绍的是最基本的网络编程模型,我们使用单线程去实现,它的步骤很简单 如同这幅流程图,它简单到非常单薄。这个模型暴露出来...

方亮的专栏 3455

微信删除的聊天记录怎么恢复(2024年最新攻略合集)

随着微信对我们生活中方方面面的渗透,微信已然从一款简单的社交软件过渡成为现时代的一种生活方式,而微信中的聊天记录就是我们和朋友之间历程的最好证明。对此,今天我们来聊聊微信删除的聊天记录怎么恢复的问题。

m0_70960708的博客 945

linux中网络编程I/O模型---epoll

linux中网络编程I/O模型—epoll(异步I/O) epoll(异步I/O)模型使用,使用epoll模型需要用到一下3个函数: //创建一个epoll的句柄,size用来告诉系统内核要监听的文件描述符的数量 //注:使用完epoll_create创建的句柄后需要close int epoll_create(int size); //epoll_ctl注册需要监听的事件类型。参数列表: ep...

laying_1230 205

10分钟搞定小红书爆款笔记爬取!Selenium模拟登录+数据解析全流程,新手也能落地

小红书爬虫实战的核心,是**“模拟真人操作+解析结构化数据”**——Selenium解决了动态页面和登录的问题,lxml解决了数据提取的问题,pandas解决了数据存储的问题。新手学爬虫不用一开始就啃复杂的反爬机制,先从这类“可见即可爬”的场景入手,掌握“模拟操作→数据解析→数据保存”的全流程,再逐步叠加异常处理、批量爬取、反爬应对等功能。需要注意的是:爬取数据仅用于个人学习和研究,不得用于商业用途,且需遵守小红书的用户协议,不要过度爬取导致服务器压力。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1729

【MATLAB代码】TDOA+TOA融合算法,高精度定位。通过多边定位解算4个锚点、平面目标的位置,附代码

二维平面下的TOA、TDOA及混合定位算法,通过4个锚节点对目标点进行定位。

985工科博士毕业,专攻定位、导航和滤波等算法研究。从业10年,主要使用MATLAB 211

epoll模型网络编程知识要领

创建监听socket调用bind函数绑定ip地址、port端口号调用listen函数监听调用epoll_create函数创建epollfd调用epoll_ctrl函数将listenfd绑定到epollfd上,监测listenfd的读事件在一个无限循环中,调用epoll_wait函数等待事件发生。

GHL284271090的博客 287

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

epoll编程实例客户端_Python epoll网络编程二:客户端模型

Python epoll网络编程二:客户端模型第一篇简单介绍了一个使用epoll的服务器模型,本节介绍使用epoll的客户端模型,客户端连接多个服务器。以下是发送hello请求的客户端,可以作为探测程序,探测服务器的工作状态,如果返回greeting,表示服务器正常工作。代码如下:文件:epoll_python.zip大小:2KB下载:def usage():print 'python epoll...

weixin_33765530的博客 589

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll、Epoll模型处理长连接性能比较

在《朴素、Select、Poll和Epoll网络编程模型实现和分析——模型比较》一文中,我们分析了各种模型在处理短连接时的能力。本文我们将讨论处理长连接时各个模型的性能。(转载请指明出于breaksoftware的csdn博客) 我们可以想象下场景,如果我们使用朴素模型来处理长连接,则需要引入多线程来处理:主线程接收接入的Socket,子线程处理一个个Socket...

方亮的专栏 5731

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

网络编程之IO、 poll、 epoll模型

网络编程的目的是为了解决机器间的通信问题。 1.常规API实现的基础IO int listenfd, connfd, n; struct sockaddr_in servaddr; char buff[MAXLNE]; if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { printf("create socket failed error: %s(errno:%d)\n", strerro

qq_39612543的博客 2861

网络编程-epoll模型/udp通信

epoll 模型udp模型:单对单通信udp 组播udp广播。

weixin_63164319的博客 237
上一篇: 网络编程——I/O复用模型编程
下一篇: 网络编程——多线程编程
DiliLearngent
博客等级 码龄7年 977粉丝 49原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值