网络编程——epoll模型编程

目的

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值