utility.h头文件ios
客户端.cpp编程
客户端要处理的事件有2种,1.来自服务器的数据或者断开链接 .2.处理用户输入的数据。因此为了高效使用并发编程,父进程处理服务器相关的操做,子进程处理用户输入服务器
代码中isClientwork状态用来标记该聊天室是否工做,当使用了fork后,父子进程共享数据,可是数据会写时更新,即不管父子进程修改这个变量都不会影响对方,由于修改这个值后会为子进程分配新的数据空间并发
EPOLLIN:表示对应的文件描述符能够读(包括对端SOCKET正常关闭);socket
EPOLLHUP:挂起。好比管道的写端被关闭后,读端描述符上接收到POLLHUOP事件测试
再来看一个问题,代码中父进程结束有2种方式,一是服务器关闭链接,此时socke即connect有事件发生,对端关闭。二是用户输入EXIT,子进程结束,写管道关闭,由于读管道文件描述符被挂在epoll注册的事件表中,它的对端关闭了,因此有事件发生。二者this
读取数据都为0,断定为对端正常关闭。spa
若是输入了EXIT,子进程关闭,而后父进程关闭。code
若是服务器关闭,那么父进程关闭,可是子进程并无退出,它变成了孤儿进程,它的父进程为init(1号进程),当你向终端写数据时,由于父进程关闭了读管道,因此写入失败,exit(-1),子进程退出orm
#include "utility.h" 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 = inet_addr(SERVER_IP); // 建立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); } // 建立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]); // 主循环(epoll_wait) 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); // ret = 0 if(ret == 0) isClientwork = 0; else{ // 将信息发送给服务端 send(sock, message, BUF_SIZE, 0); } } }//for }//while } if(pid){ //关闭父进程和sock close(pipe_fd[0]); close(sock); }else{ //关闭子进程 close(pipe_fd[1]); } return 0; }
服务器.cpp
服务器只须要向内核注册两个感兴趣的事件。1.有新客户链接 2.某个客户有数据发送.
有新客户到来,就把该用户添加到list中
有客户发送数据就把这数据转发到其余客户端
#include "utility.h" 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 = inet_addr(SERVER_IP); //建立监听socket int listener = socket(PF_INET, SOCK_STREAM, 0); if(listener < 0) { perror("listener"); exit(-1);} printf("listen socket created \n"); //绑定地址 if( bind(listener, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) { perror("bind error"); exit(-1); } //监听 int ret = listen(listener, 5); if(ret < 0) { perror("listen error"); exit(-1);} printf("Start to listen: %s\n", SERVER_IP); //在内核中建立事件表 int epfd = epoll_create(EPOLL_SIZE); if(epfd < 0) { perror("epfd error"); exit(-1);} printf("epoll created, epollfd = %d\n", epfd); static struct epoll_event events[EPOLL_SIZE]; //往内核事件表里添加事件 addfd(epfd, listener, true); //主循环 while(1) { //epoll_events_count表示就绪事件的数目 int epoll_events_count = epoll_wait(epfd, events, EPOLL_SIZE, -1); if(epoll_events_count < 0) { perror("epoll failure"); break; } printf("epoll_events_count = %d\n", epoll_events_count); //处理这epoll_events_count个就绪事件 for(int i = 0; i < epoll_events_count; ++i) { int sockfd = events[i].data.fd; //新用户链接 if(sockfd == listener) { struct sockaddr_in client_address; socklen_t client_addrLength = sizeof(struct sockaddr_in); int clientfd = accept( listener, ( 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()); // 服务端发送欢迎信息 printf("welcome message\n"); 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("send error"); exit(-1); } } //处理用户发来的消息,并广播,使其余用户收到信息 else { int ret = sendBroadcastmessage(sockfd); if(ret < 0) { perror("error");exit(-1); } } } } close(listener); //关闭socket close(epfd); //关闭内核 return 0; }