epoll实现的多人聊天室程序

关于epoll的介绍此处不赘述,可以参考这篇博文 linux epoll详解及使用方法概述

这里给出一个epoll实现的聊天室程序,实现群聊功能,分为server端和client端代码:

server.cpp:

//server.cpp
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

//选用list存放sockfd
list clients_list;

//server port
#define SERVER_PORT 8888

//epoll支持的最大并发量
#define EPOLL_SIZE 5000

//message buf size
#define BUF_SIZE 0xFFFF

#define SERVER_WELCOME "Welcome you join  to the chat room! Your chat ID is: Client #%d"

#define SERVER_MESSAGE "ClientID %d say >> %s"

// exit
#define EXIT "EXIT"

#define CAUTION "There is only one int the char room!"

int setnonblocking(int sockfd){
    fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0) | O_NONBLOCK);
    return 0;
}

void addfd(int epollfd, int fd, bool enable_et){
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = EPOLLIN;
    if(enable_et){
        ev.events = EPOLLIN | EPOLLET;
    }
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
    setnonblocking(fd);
}

int sendBroadcastmessage(int clientfd){
    char buf[BUF_SIZE], message[BUF_SIZE];
    //清零
    bzero(buf, BUF_SIZE);
    bzero(message, BUF_SIZE);

    printf("read from client(clientID = %d)\n", clientfd);
    int len = recv(clientfd, buf, BUF_SIZE, 0);
    //len=0 client关闭了连接
    if(len == 0){
        close(clientfd);
        clients_list.remove(clientfd);
        printf("ClientID = %d closed.\n now there are %d client in the char room\n", clientfd, (int)clients_list.size());
    }else{//进行广播
        if(clients_list.size() == 1){
            send(clientfd, CAUTION, strlen(CAUTION), 0);
            return len;
        }
        sprintf(message, SERVER_MESSAGE, clientfd, buf);

        list::iterator iter;
        for(iter = clients_list.begin(); iter != clients_list.end(); iter++){
            if(*iter != clientfd){
                if(send(*iter, message, BUF_SIZE, 0) < 0){
                     perror("error"); 
                     exit(-1);
                }
            }
        }
    }
}

int main(int argc, char* argv[]){
	//服务器IP + port
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = PF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);
    serverAddr.sin_addr.s_addr = htonl (INADDR_ANY);

    //创建监听socket
    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    if(listenfd < 0){
    	perror("listenfd");
    	exit(-1);
    }
    printf("listen socket created");

    //绑定地址
    if( bind(listenfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
        perror("bind error");
        exit(-1);
    }

    //监听
    int ret = listen(listenfd, 5);
    if(ret < 0) { 
        perror("listen error"); exit(-1);
    }

    //在内核中创建事件表
    int epfd = epoll_create(EPOLL_SIZE);
    if(epfd < 0){
        perror("epfd error");
        exit(-1);
    }
    printf("epoll created, epoll size = %d\n", epfd);

    static struct epoll_event events[EPOLL_SIZE];

    //往内核事件表里添加事件
    addfd(epfd, listenfd, true);

    //主循环
    while(1){
        int epoll_events_count = epoll_wait(epfd, events, EPOLL_SIZE, -1);
        if(epoll_events_count < 0){
            perror("epoll failure");
            break;
        }
        printf("epoll event counts = %d\n", epoll_events_count);

        for(int i = 0; i < epoll_events_count; i++){
            int sockfd = events[i].data.fd;
            if(sockfd == listenfd){
                struct sockaddr_in client_address;
                socklen_t client_addrLength = sizeof(struct sockaddr_in);
                int clientfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrLength);
                printf("client connection from: %s : % d(IP : port), clientfd = %d \n", inet_ntoa(client_address.sin_addr),
                ntohs(client_address.sin_port),
                clientfd);

                addfd(epfd, clientfd, true);

                //服务端用list保存用户连接
                clients_list.push_back(clientfd);
                printf("Add new clientfd = %d to epoll\n", clientfd);
                printf("Now there are %d clients int the chat room\n", (int)clients_list.size());

                //服务端发送欢迎消息
                char message[BUF_SIZE];
                bzero(message, BUF_SIZE);
                sprintf(message, SERVER_WELCOME, clientfd);
                int ret = send(clientfd, message, BUF_SIZE, 0);
                if(ret < 0){
                    perror("error");
                    exit(-1);
                }
            }
            else{
                int ret = sendBroadcastmessage(sockfd);
                if(ret < 0){
                    perror("error");
                    exit(-1);
                }
            }
        }
    }

    close(listenfd); //关闭socket
    close(epfd);    //关闭内核
    return 0;
}


client.cpp:

//client.cpp
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

// server port
#define SERVER_PORT 8888

//epoll 支持的最大并发量
#define EPOLL_SIZE 5000

//message buffer size
#define BUF_SIZE 0xFFFF

// exit
#define EXIT "EXIT"

//设置sockfd,pipefd非阻塞
int setnonblocking(int sockfd){
	fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0) | O_NONBLOCK);
	return 0;
}

int addfd(int epollfd, int fd, bool enable_et){
	struct epoll_event ev;
	ev.data.fd = fd;
	ev.events = EPOLLIN; //输入出发epoll-event
	if(enable_et){
		ev.events = EPOLLIN | EPOLLET;
	}
	epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
	setnonblocking(fd);
}

int main(int argc, char* argv[]){
	//用户连接的服务器IP、端口
	struct sockaddr_in serverAddr;
	serverAddr.sin_family = PF_INET;
	serverAddr.sin_port = htons(SERVER_PORT);
	const char* servInetAddr = "127.0.0.1";
	inet_pton(AF_INET, servInetAddr, &serverAddr.sin_addr);

	//创建socket
	int sock = socket(PF_INET, SOCK_STREAM, 0);
	if(sock < 0){
		perror("sock error");
		exit(-1);
	}

	// 连接服务端
	if(connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0){
		perror("connect error");
		exit(-1);
	}

	//创建管道,其中fd[0]用于父进程读,fd[1]用于子进程写
	int pipe_fd[2];
	if(pipe(pipe_fd) < 0){
		perror("pipe error");
		exit(-1);
	}

	// 1 创建epoll
	int epfd = epoll_create(EPOLL_SIZE);
	if(epfd < 0) { perror("epfd error"); exit(-1); }
	static struct epoll_event events[2];
	//将sock和管道读端都加到内核事件表中
	addfd(epfd, sock, true);
	addfd(epfd, pipe_fd[0], true);

	// 表示客户端是否正常工作
    bool isClientwork = true;

    // 聊天信息缓冲区
    char message[BUF_SIZE];


   	//Fork
   	int pid = fork();
   	if(pid < 0) { perror("fork error"); exit(-1); }
   	else if(pid == 0){  //子进程
   		//子进程负责写入管道,因此先关闭读端
   		close(pipe_fd[0]);
   		printf("Please input 'exit' to exit the chat room\n");

   		while(isClientwork){
   			bzero(&message, BUF_SIZE);
   			fgets(message, BUF_SIZE, stdin);

   			//客户端输出exit,退出
   			if(strncasecmp(message, EXIT, strlen(EXIT)) == 0){
   				isClientwork = 0;
   			}else{
   				//子进程将信息写入管道
   				if(write(pipe_fd[1], message, strlen(message) - 1) < 0){
   					{ perror("fork error"); exit(-1); }
   				}
   			} 
   		}
   	}else{		//pid > 0 父进程
   		//父进程负责读管道数据,因此先关闭写端
   		close(pipe_fd[1]);

  		while(isClientwork){
  			int epoll_events_count = epoll_wait(epfd, events, 2, -1);
   		//处理就绪事件
   		for(int i = 0; i < epoll_events_count; i++){
   			bzero(&message, BUF_SIZE);
   			//服务端发来消息
   			if(events[i].data.fd == sock){
   				//接受服务端消息
   				int ret = recv(sock, message, BUF_SIZE, 0);

   				//ret = 0  服务端关闭
   				if(ret == 0){
   					printf("Server closed connection: %d\n", sock);
                    close(sock);
                    isClientwork = 0;
   				}else{
   					printf("%s\n", message);
   				}
   			}else{
   				//子进程写入事件发生,父进程处理并发送数据
   				int ret = read(events[i].data.fd, message, BUF_SIZE);
   				if(ret = 0){
   					isClientwork = 0;
   				}else{
   					send(sock, message, BUF_SIZE, 0);
   				}
   			}
   		}
  		}
   		
   	}

   	if(pid){
   		close(pipe_fd[1]);
   		close(sock);
   	}else{
   		close(pipe_fd[0]);
   	}

   	return 0;
}


分别编译两个cpp文件得到执行文件:

epoll实现的多人聊天室程序_第1张图片


开启服务端程序:



在另一个terminal里开启一个client:

epoll实现的多人聊天室程序_第2张图片


此时server处:

epoll实现的多人聊天室程序_第3张图片


开启第二个client:

epoll实现的多人聊天室程序_第4张图片


此时server端打印:

epoll实现的多人聊天室程序_第5张图片

此时聊天室内有两个用户,类似,可以添加多个


在其中一个client(client 6)中发送一条消息:

epoll实现的多人聊天室程序_第6张图片


此时,server端打印:



另一个client打印:



如果有多个client,其中一个client发送的消息另外所有的client都能接收到


关闭client 5,此时server打印:



你可能感兴趣的:(c/c++,网络编程/多线程)