epoll实现高并发聊天室

http://blog.csdn.net/qq_31564375/article/details/51581038

项目介绍

本项目是实现一个简单的聊天室,聊天室分为服务端和客户端。本项目将很多复杂的功能都去掉了,线程池、多线程编程、超时重传、确认收包等等都不会涉及。总共300多行代码,让大家真正了解C/S模型,以及epoll的使用。为了方便查看,代码已经改的很小白,绝对比nginx源码好理解(当然大家有兴趣的话,还是要拜读下nginx源码,绝对大有收获)。希望本项目能为大家以后的工作或者学习提供一点帮助! 介绍如下:

1. 服务端

a. 支持多个用户接入,实现聊天室的基本功能
b. 使用epoll机制实现并发,增加效率

2. 客户端

a. 支持用户输入聊天消息
b. 显示其他用户输入的信息
c. 使用fork创建两个进程
子进程有两个功能:
  1. 等待用户输入聊天信息
  2. 将聊天信息写到管道(pipe),并发送给父进程
父进程有两个功能
  1. 使用epoll机制接受服务端发来的信息,并显示给用户,使用户看到其他用户的聊天信息
  2. 将子进程发给的聊天信息从管道(pipe)中读取, 并发送给服务端

3. 代码说明

一共有3个文件, 即: server.cpp, client.cpp, utility.h

a. server.cpp是服务端程序
b. client.cpp是客户端程序

c. utility.h是一个头文件,包含服务端程序和客户端程序都会用到的一些头文件、变量声明、函数、宏等。


1.1 TCP服务端通信的常规步骤

(1)使用socket()创建TCP套接字(socket)
(2)将创建的套接字绑定到一个本地地址和端口上(Bind)
(3)将套接字设为监听模式,准备接收客户端请求(listen)
(4)等待客户请求到来: 当请求到来后,接受连接请求,返回一个对应于此次连接的新的套接字(accept)
(5)用accept返回的套接字和客户端进行通信(使用write()/send()或send()/recv() )
(6)返回,等待另一个客户请求
(7)关闭套接字
[cpp] view plain copy
  1. //server.cpp代码(通信模块):  
  2.     //服务端地址 ip地址 + 端口号  
  3.     struct sockaddr_in serverAddr;  
  4.     serverAddr.sin_family = PF_INET;  
  5.     serverAddr.sin_port = htons(SERVER_PORT);  
  6.     serverAddr.sin_addr.s_addr = inet_addr(SERVER_HOST);  
  7.   
  8.     //服务端创建监听socket  
  9.     int listener = socket(PF_INET, SOCK_STREAM, 0);  
  10.     if(listener < 0) { perror("listener"); exit(-1);}  
  11.     printf("listen socket created \n");  
  12.   
  13.     //将服务端地址与监听socket绑定  
  14.     if( bind(listener, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {  
  15.         perror("bind error");  
  16.         exit(-1);  
  17.     }  
  18.     //开始监听  
  19.     int ret = listen(listener, 5);  
  20.     if(ret < 0) { perror("listen error"); exit(-1);}  
  21.     printf("Start to listen: %s\n", SERVER_HOST);  

2. 基本技术介绍

2.1 阻塞与非阻塞socket

通常的,对一个文件描述符指定的文件或设备, 有两种工作方式: 阻塞与非阻塞方式。

(1). 阻塞方式是指: 当试图对该文件描述符进行读写时,如果当时没有数据可读,或者暂时不可写,程序就进入等待状态,直到有东西可读或者可写为止。

(2). 非阻塞方式是指: 如果没有数据可读,或者不可写,读写函数马上返回,而不会等待。

(3). 举个例子来说,比如说小明去找一个女神聊天,女神却不在。 如果小明舍不得走,只能在女神大门口死等着,当然小明可以休息。当女 神来了,她会把你唤醒(囧,因为挡着她门了),这就是阻塞方式。如果小明发现女神不在,立即离开,以后每隔十分钟回来看一下(采用轮询方式),不在的话仍然立即离开,这就是非阻塞方式。

(4). 阻塞方式和非阻塞方式唯一的区别: 是否立即返回。本项目采用更高效的做法,所以应该将socket设置为非阻塞方式。这样能充分利用服务器资源,效率得到了很大提高。

[cpp] view plain copy
  1. //utility.h代码(设置非阻塞函数模块):  
  2. //将文件描述符设置为非阻塞方式(利用fcntl函数)  
  3. int setnonblocking(int sockfd)  
  4. {  
  5.     fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)| O_NONBLOCK);  
  6.     return 0;  
  7. }  

2.2 epoll

前面介绍了阻塞和非阻塞方式,现在该介绍下epoll机制了。epoll真的是一个特别重要的概念,实验的师兄们去bat任何一家面试后台开发,或者系统开发等相关职位都会问epoll机制。当服务端的在线人数越来越多,会导致系统资源吃紧,I/O效率越来越慢,这时候就应该考虑epoll了。epoll是Linux内核为处理大批句柄而作改进的poll,是Linux特有的I/O函数。其特点如下:

a.
epoll是Linux下多路复用IO接口select/poll的增强版本。其实现和使用方式与select/poll有很多不同,epoll通过一组函数来完成有关任务,而不是一个函数。
b.
epoll之所以高效,是因为epoll将用户关心的文件描述符放到内核里的一个事件表中,而不是像select/poll每次调用都需要重复传入文件描述符集或事件集。比如当一个事件发生(比如说读事件),epoll无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入就绪队列的描述符集合就行了。
c.
epoll有两种工作方式,LT(level triggered):水平触发和ET(edge-triggered):边沿触发。LT是select/poll使用的触发方式,比较低效;而ET是epoll的高速工作方式(本项目使用epoll的ET方式)。
d.
通俗理解就是,比如说有一堆女孩,有的很漂亮,有的很凤姐。现在你想找漂亮的女孩聊天,LT就是你需要把这一堆女孩全都看一遍,才可以找到其中的漂亮的(就绪事件);而ET是你的小弟(内核)将N个漂亮的女孩编号告诉你,你直接去看就好,所以epoll很高效。另外,还记得小明找女神聊天的例子吗?采用非阻塞方式,小明还需要每隔十分钟回来看一下(select);如果小明有小弟(内核)帮他守在大门口,女神回来了,小弟会主动打电话,告诉小明女神回来了,快来处理吧!这就是epoll。
epoll 共3个函数,
1、int epoll_create(int size)创建一个epoll句柄,参数size用来告诉内核监听的数目,size为epoll所支持的最大句柄数
2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)函数功能: epoll事件注册函数参数epfd为epoll的句柄,即epoll_create返回值参数op表示动作,用3个宏来表示:  EPOLL_CTL_ADD(注册新的fd到epfd), EPOLL_CTL_MOD(修改已经注册的fd的监听事件),EPOLL_CTL_DEL(从epfd删除一个fd);其中参数fd为需要监听的标示符;参数event告诉内核需要监听的事件,event的结构如下:struct epoll_event {__uint32_t events; //Epoll eventsepoll_data_t data; //User data variable};其中介绍events是宏的集合,本项目主要使用EPOLLIN(表示对应的文件描述符可以读,即读事件发生),其他宏类型,可以google之!
3、 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
等待事件的产生,函数返回需要处理的事件数目(该数目是就绪事件的数目,就是前面所说漂亮女孩的个数N)

因此服务端使用epoll的时候,步骤如下:

  1. 调用epoll_create函数在Linux内核中创建一个事件表;
  2. 然后将文件描述符(监听套接字listener)添加到所创建的事件表中;
  3. 在主循环中,调用epoll_wait等待返回就绪的文件描述符集合;
  4. 分别处理就绪的事件集合,本项目中一共有两类事件:新用户连接事件和用户发来消息事件(epoll还有很多其他事件,本项目为简洁明了,不介绍)。

下面介绍下如何将一个socket添加到内核事件表中,如下:

[cpp] view plain copy
  1. //utility.h(添加socket模块):  
  2. //将文件描述符fd添加到epollfd标示的内核事件表中, 并注册EPOLLIN和EPOOLET事件,EPOLLIN是数据可读事件;EPOOLET表明是ET工作方式。最后将文件描述符设置非阻塞方式  
  3. /** 
  4.   * @param epollfd: epoll句柄 
  5.   * @param fd: 文件描述符 
  6.   * @param enable_et : enable_et = true,  
  7.      采用epoll的ET工 作方式;否则采用LT工作方式 
  8. **/  
  9. void addfd( int epollfd, int fd, bool enable_et )  
  10. {  
  11.     struct epoll_event ev;  
  12.     ev.data.fd = fd;  
  13.     ev.events = EPOLLIN;  
  14.     if( enable_et )  
  15.         ev.events = EPOLLIN | EPOLLET;  
  16.     epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);  
  17.     setnonblocking(fd);  
  18.     printf("fd added to epoll!\n\n");  
  19. }  

3. 服务端实现

上面我们介绍了基本的模型和技术,现在该去实现服务端了。首先介绍下utility.h中一些变量和函数。

3.1 utility.h

[cpp] view plain copy
  1. /* 限于篇幅,这里先介绍下utility.h的主要构成。其中的头文件和一些函数实现没有显示,完整源码位于3.2节 */  
  2.     //服务端存储所有在线用户socket, 便于广播信息  
  3.     list<int> clients_list;  
  4.     // 服务器ip地址,为测试使用本地机地址,可以更改为其他服务端地址  
  5.     #define SERVER_IP "127.0.0.1"  
  6.     // 服务器端口号  
  7.     #define SERVER_PORT 8888  
  8.     //int epoll_create(int size)中的size,为epoll支持的最大句柄数  
  9.     #define EPOLL_SIZE 5000  
  10.   
  11.     // 缓冲区大小65535  
  12.     #define BUF_SIZE 0xFFFF  
  13.     //一些宏  
  14.     #define SERVER_WELCOME "Welcome you join to the chat room! Your chat ID is: Client #%d"  
  15.     #define SERVER_MESSAGE "ClientID %d say >> %s"  
  16.     #define EXIT "EXIT"  
  17.     #define CAUTION "There is only one int the char room!"  
  18.     /* 一些函数 */  
  19.     //设置非阻塞  
  20.     int setnonblocking(int sockfd);  
  21.     //将文件描述符fd添加到epollfd标示的内核事件表  
  22.     void addfd( int epollfd, int fd, bool enable_et );  
  23.     //服务端发送广播信息,使所有用户都能收到消息  
  24.     int sendBroadcastmessage(int clientfd);  
  25. 3.1 utility.h完整源码  
  26.   
  27. #ifndef UTILITY_H_INCLUDED  
  28. #define UTILITY_H_INCLUDED  
  29.   
  30. #include <iostream>  
  31. #include <list>  
  32. #include <sys/types.h>  
  33. #include <sys/socket.h>  
  34. #include <netinet/in.h>  
  35. #include <arpa/inet.h>  
  36. #include <sys/epoll.h>  
  37. #include <fcntl.h>  
  38. #include <errno.h>  
  39. #include <unistd.h>  
  40. #include <stdio.h>  
  41. #include <stdlib.h>  
  42. #include <string.h>  
  43.   
  44. using namespace std;  
  45.   
  46. // clients_list save all the clients's socket  
  47. list<int> clients_list;  
  48.   
  49. /**********************   macro defintion **************************/  
  50. // server ip  
  51. #define SERVER_IP "127.0.0.1"  
  52.   
  53. // server port  
  54. #define SERVER_PORT 8888  
  55.   
  56. //epoll size  
  57. #define EPOLL_SIZE 5000  
  58.   
  59. //message buffer size  
  60. #define BUF_SIZE 0xFFFF  
  61.   
  62. #define SERVER_WELCOME "Welcome you join  to the chat room! Your chat ID is: Client #%d"  
  63.   
  64. #define SERVER_MESSAGE "ClientID %d say >> %s"  
  65.   
  66. // exit  
  67. #define EXIT "EXIT"  
  68.   
  69. #define CAUTION "There is only one int the char room!"  
  70.   
  71. /**********************   some function **************************/  
  72. /** 
  73.   * @param sockfd: socket descriptor 
  74.   * @return 0 
  75. **/  
  76. int setnonblocking(int sockfd)  
  77. {  
  78.     fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)| O_NONBLOCK);  
  79.     return 0;  
  80. }  
  81.   
  82. /** 
  83.   * @param epollfd: epoll handle 
  84.   * @param fd: socket descriptor 
  85.   * @param enable_et : enable_et = true, epoll use ET; otherwise LT 
  86. **/  
  87. void addfd( int epollfd, int fd, bool enable_et )  
  88. {  
  89.     struct epoll_event ev;  
  90.     ev.data.fd = fd;  
  91.     ev.events = EPOLLIN;  
  92.     if( enable_et )  
  93.         ev.events = EPOLLIN | EPOLLET;  
  94.     epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);  
  95.     setnonblocking(fd);  
  96.     printf("fd added to epoll!\n\n");  
  97. }  
  98.   
  99. /** 
  100.   * @param clientfd: socket descriptor 
  101.   * @return : len 
  102. **/  
  103. int sendBroadcastmessage(int clientfd)  
  104. {  
  105.     // buf[BUF_SIZE] receive new chat message  
  106.     // message[BUF_SIZE] save format message  
  107.     char buf[BUF_SIZE], message[BUF_SIZE];  
  108.     bzero(buf, BUF_SIZE);  
  109.     bzero(message, BUF_SIZE);  
  110.   
  111.     // receive message  
  112.     printf("read from client(clientID = %d)\n", clientfd);  
  113.     int len = recv(clientfd, buf, BUF_SIZE, 0);  
  114.   
  115.     if(len == 0)  // len = 0 means the client closed connection  
  116.     {  
  117.         close(clientfd);  
  118.         clients_list.remove(clientfd); //server remove the client  
  119.         printf("ClientID = %d closed.\n now there are %d client in the char room\n", clientfd, (int)clients_list.size());  
  120.   
  121.     }  
  122.     else  //broadcast message   
  123.     {  
  124.         if(clients_list.size() == 1) { // this means There is only one int the char room  
  125.             send(clientfd, CAUTION, strlen(CAUTION), 0);  
  126.             return len;  
  127.         }  
  128.         // format message to broadcast  
  129.         sprintf(message, SERVER_MESSAGE, clientfd, buf);  
  130.   
  131.         list<int>::iterator it;  
  132.         for(it = clients_list.begin(); it != clients_list.end(); ++it) {  
  133.            if(*it != clientfd){  
  134.                 if( send(*it, message, BUF_SIZE, 0) < 0 ) { perror("error"); exit(-1);}  
  135.            }  
  136.         }  
  137.     }  
  138.     return len;  
  139. }  
  140. #endif // UTILITY_H_INCLUDED  

3.3 服务端完整源码

在上面的基础上。服务端的代码就很容易写出了

[cpp] view plain copy
  1. #include "utility.h"  
  2.   
  3. int main(int argc, char *argv[])  
  4. {  
  5.     //服务器IP + port  
  6.     struct sockaddr_in serverAddr;  
  7.     serverAddr.sin_family = PF_INET;  
  8.     serverAddr.sin_port = htons(SERVER_PORT);  
  9.     serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);  
  10.     //创建监听socket  
  11.     int listener = socket(PF_INET, SOCK_STREAM, 0);  
  12.     if(listener < 0) { perror("listener"); exit(-1);}  
  13.     printf("listen socket created \n");  
  14.     //绑定地址  
  15.     if( bind(listener, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {  
  16.         perror("bind error");  
  17.         exit(-1);  
  18.     }  
  19.     //监听  
  20.     int ret = listen(listener, 5);  
  21.     if(ret < 0) { perror("listen error"); exit(-1);}  
  22.     printf("Start to listen: %s\n", SERVER_IP);  
  23.     //在内核中创建事件表  
  24.     int epfd = epoll_create(EPOLL_SIZE);  
  25.     if(epfd < 0) { perror("epfd error"); exit(-1);}  
  26.     printf("epoll created, epollfd = %d\n", epfd);  
  27.     static struct epoll_event events[EPOLL_SIZE];  
  28.     //往内核事件表里添加事件  
  29.     addfd(epfd, listener, true);  
  30.     //主循环  
  31.     while(1)  
  32.     {  
  33.         //epoll_events_count表示就绪事件的数目  
  34.         int epoll_events_count = epoll_wait(epfd, events, EPOLL_SIZE, -1);  
  35.         if(epoll_events_count < 0) {  
  36.             perror("epoll failure");  
  37.             break;  
  38.         }  
  39.   
  40.         printf("epoll_events_count = %d\n", epoll_events_count);  
  41.         //处理这epoll_events_count个就绪事件  
  42.         for(int i = 0; i < epoll_events_count; ++i)  
  43.         {  
  44.             int sockfd = events[i].data.fd;  
  45.             //新用户连接  
  46.             if(sockfd == listener)  
  47.             {  
  48.                 struct sockaddr_in client_address;  
  49.                 socklen_t client_addrLength = sizeof(struct sockaddr_in);  
  50.                 int clientfd = accept( listener, ( struct sockaddr* )&client_address, &client_addrLength );  
  51.   
  52.                 printf("client connection from: %s : % d(IP : port), clientfd = %d \n",  
  53.                 inet_ntoa(client_address.sin_addr),  
  54.                 ntohs(client_address.sin_port),  
  55.                 clientfd);  
  56.   
  57.                 addfd(epfd, clientfd, true);  
  58.   
  59.                 // 服务端用list保存用户连接  
  60.                 clients_list.push_back(clientfd);  
  61.                 printf("Add new clientfd = %d to epoll\n", clientfd);  
  62.                 printf("Now there are %d clients int the chat room\n", (int)clients_list.size());  
  63.   
  64.                 // 服务端发送欢迎信息    
  65.                 printf("welcome message\n");                  
  66.                 char message[BUF_SIZE];  
  67.                 bzero(message, BUF_SIZE);  
  68.                 sprintf(message, SERVER_WELCOME, clientfd);  
  69.                 int ret = send(clientfd, message, BUF_SIZE, 0);  
  70.                 if(ret < 0) { perror("send error"); exit(-1); }  
  71.             }  
  72.             //处理用户发来的消息,并广播,使其他用户收到信息  
  73.             else   
  74.             {     
  75.                 int ret = sendBroadcastmessage(sockfd);  
  76.                 if(ret < 0) { perror("error");exit(-1); }  
  77.             }  
  78.         }  
  79.     }  
  80.     close(listener); //关闭socket  
  81.     close(epfd);    //关闭内核  
  82.     return 0;  
  83. }  
g++ server.cpp utility.h -o server
./server

4. 客户端实现

4.1 子进程和父进程的通信

前面已经介绍了子进程和父进程的功能,他们之间用管道进行通信。如下图所示,我们可以更直观的了解子进程和父进程各自的功能。 

通过调用int pipe(int fd[2])函数创建管道, 其中fd[0]用于父进程读, fd[1]用于子进程写。
[cpp] view plain copy
  1. //client.cpp代码(管道模块)  
  2.    // 创建管道.  
  3.     int pipe_fd[2];  
  4.     if(pipe(pipe_fd) < 0) { perror("pipe error"); exit(-1); }  

通过int pid = fork()函数,创建子进程,当pid < 0 错误;当pid = 0, 说明是子进程;当pid > 0说明是父进程。根据pid的值,我们可以父子进程,从而实现对应的功能!

4.2 客户端完整源码

根据上述介绍,我们可以写出客户端的源码。如下:

[cpp] view plain copy
  1. #include "utility.h"  
  2.   
  3. int main(int argc, char *argv[])  
  4. {  
  5.     //用户连接的服务器 IP + port  
  6.     struct sockaddr_in serverAddr;  
  7.     serverAddr.sin_family = PF_INET;  
  8.     serverAddr.sin_port = htons(SERVER_PORT);  
  9.     serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);  
  10.   
  11.     // 创建socket  
  12.     int sock = socket(PF_INET, SOCK_STREAM, 0);  
  13.     if(sock < 0) { perror("sock error"); exit(-1); }  
  14.     // 连接服务端  
  15.     if(connect(sock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {  
  16.         perror("connect error");  
  17.         exit(-1);  
  18.     }  
  19.   
  20.     // 创建管道,其中fd[0]用于父进程读,fd[1]用于子进程写  
  21.     int pipe_fd[2];  
  22.     if(pipe(pipe_fd) < 0) { perror("pipe error"); exit(-1); }  
  23.   
  24.     // 创建epoll  
  25.     int epfd = epoll_create(EPOLL_SIZE);  
  26.     if(epfd < 0) { perror("epfd error"); exit(-1); }  
  27.     static struct epoll_event events[2];   
  28.     //将sock和管道读端描述符都添加到内核事件表中  
  29.     addfd(epfd, sock, true);  
  30.     addfd(epfd, pipe_fd[0], true);  
  31.     // 表示客户端是否正常工作  
  32.     bool isClientwork = true;  
  33.   
  34.     // 聊天信息缓冲区  
  35.     char message[BUF_SIZE];  
  36.   
  37.     // Fork  
  38.     int pid = fork();  
  39.     if(pid < 0) { perror("fork error"); exit(-1); }  
  40.     else if(pid == 0)      // 子进程  
  41.     {  
  42.         //子进程负责写入管道,因此先关闭读端  
  43.         close(pipe_fd[0]);   
  44.         printf("Please input 'exit' to exit the chat room\n");  
  45.   
  46.         while(isClientwork){  
  47.             bzero(&message, BUF_SIZE);  
  48.             fgets(message, BUF_SIZE, stdin);  
  49.   
  50.             // 客户输出exit,退出  
  51.             if(strncasecmp(message, EXIT, strlen(EXIT)) == 0){  
  52.                 isClientwork = 0;  
  53.             }  
  54.             // 子进程将信息写入管道  
  55.             else {  
  56.                 if( write(pipe_fd[1], message, strlen(message) - 1 ) < 0 )  
  57.                  { perror("fork error"); exit(-1); }  
  58.             }  
  59.         }  
  60.     }  
  61.     else  //pid > 0 父进程  
  62.     {  
  63.         //父进程负责读管道数据,因此先关闭写端  
  64.         close(pipe_fd[1]);   
  65.   
  66.         // 主循环(epoll_wait)  
  67.         while(isClientwork) {  
  68.             int epoll_events_count = epoll_wait( epfd, events, 2, -1 );  
  69.             //处理就绪事件  
  70.             for(int i = 0; i < epoll_events_count ; ++i)  
  71.             {  
  72.                 bzero(&message, BUF_SIZE);  
  73.   
  74.                 //服务端发来消息  
  75.                 if(events[i].data.fd == sock)  
  76.                 {  
  77.                     //接受服务端消息  
  78.                     int ret = recv(sock, message, BUF_SIZE, 0);  
  79.   
  80.                     // ret= 0 服务端关闭  
  81.                     if(ret == 0) {  
  82.                         printf("Server closed connection: %d\n", sock);  
  83.                         close(sock);  
  84.                         isClientwork = 0;  
  85.                     }  
  86.                     else printf("%s\n", message);  
  87.   
  88.                 }  
  89.                 //子进程写入事件发生,父进程处理并发送服务端  
  90.                 else {   
  91.                     //父进程从管道中读取数据  
  92.                     int ret = read(events[i].data.fd, message, BUF_SIZE);  
  93.   
  94.                     // ret = 0  
  95.                     if(ret == 0) isClientwork = 0;  
  96.                     else{   // 将信息发送给服务端  
  97.                       send(sock, message, BUF_SIZE, 0);  
  98.                     }  
  99.                 }  
  100.             }//for  
  101.         }//while  
  102.     }  
  103.   
  104.     if(pid){  
  105.        //关闭父进程和sock  
  106.         close(pipe_fd[0]);  
  107.         close(sock);  
  108.     }else{  
  109.         //关闭子进程  
  110.         close(pipe_fd[1]);  
  111.     }  
  112.     return 0;  
  113. }  
同理建立一个client.cpp文件,并将上述完整源码拷贝进去,然后启动一个新的XFce终端,执行如下命令:
cd Desktop
g++ client.cpp utility.h -o client
./client
如图所示,通过查看两个终端界面,可以看到有一个用户登陆服务端了。 同理,再点击一下桌面上的XFce,开启一个终端,运行同样的命令(这里不用运行g++进行编译了,因为前面已经生成了可执行文件client):
cd Desktop
./client
转载自:https://www.shiyanlou.com/courses/315
很好的学习了epoll。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383769.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BZOJ2809-左偏树合并

Description 在一个忍者的帮派里&#xff0c;一些忍者们被选中派遣给顾客&#xff0c;然后依据自己的工作获取报偿。在这个帮派里&#xff0c;有一名忍者被称之为 Master。除了 Master以外&#xff0c;每名忍者都有且仅有一个上级。为保密&#xff0c;同时增强忍者们的领导力&a…

处理大并发之一 对epoll的理解,epoll客户端服务端代码

http://blog.csdn.net/zhuxiaoping54532/article/details/56494313处理大并发之一对epoll的理解&#xff0c;epoll客户端服务端代码序言&#xff1a;该博客是一系列的博客&#xff0c;首先从最基础的epoll说起&#xff0c;然后研究libevent源码及使用方法&#xff0c;最后研究n…

epoll详解

http://blog.csdn.net/majianfei1023/article/details/45772269欢迎转载&#xff0c;转载请注明原文地址&#xff1a;http://blog.csdn.net/majianfei1023/article/details/45772269一.基本概念&#xff1a;1.epoll是什么&#xff1a;epoll是Linux内核为处理大批量文件描述符而…

数据分割-并查集+set

小w来到百度之星的赛场上&#xff0c;准备开始实现一个程序自动分析系统。 这个程序接受一些形如xixj 或 xi≠xj 的相等/不等约束条件作为输入&#xff0c;判定是否可以通过给每个 w 赋适当的值&#xff0c;来满足这些条件。 输入包含多组数据。 然而粗心的小w不幸地把每组数据…

linux c++线程池的实现

http://blog.csdn.net/zhoubl668/article/details/8927090?t1473221020107 线程池的原理大家都知道&#xff0c;直接上代码了^_^ Thread.h [cpp] view plaincopy #ifndef __THREAD_H #define __THREAD_H #include <vector> #include <string> #inc…

树启发式合并入门

所谓启发式合并&#xff0c;就是一种符合直觉的合并方法&#xff1a;将小的子树合并在大的子树上。 这些问题一般是相似的问题背景&#xff1a;都是树上的计数问题&#xff0c;都不能直接从上往下进行暴力&#xff0c;都需要从下往上计数时对子树信息进行运算从而得到父亲节点的…

链栈基本操作

http://blog.csdn.net/jwentao01/article/details/46765517###;栈基本概念&#xff1a; 栈&#xff08;stack&#xff09;是限定在表尾进行插入和删除操作的线性表&#xff08;或单链表&#xff09;。 //只能在一段进行插入和删除&#xff0c;因此不存在&#xff0c;在中间进行…

Linux网络编程---I/O复用模型之select

https://blog.csdn.net/men_wen/article/details/53456435Linux网络编程—I/O复用模型之select 1. IO复用模型 IO复用能够预先告知内核&#xff0c;一旦发现进程指定的一个或者多个IO条件就绪&#xff0c;它就通知进程。IO复用阻塞在select或poll系统调用上&#xff0c;而不是阻…

UVa12633-Super Rooks on Chessboard-容斥+FFT

题目大意就是给你一个R*C的棋盘&#xff0c;上面有超级兵&#xff0c;这种超级兵会攻击 同一行、同一列、同一主对角线的所有元素&#xff0c;现在给你N个超级兵的坐标&#xff0c;需要你求出有多少方块是不能被攻击到的(R,C,N<50000) 遇到这种计数问题就要联想到容斥&#…

Linux网络编程---I/O复用模型之poll

https://blog.csdn.net/men_wen/article/details/53456474Linux网络编程—I/O复用模型之poll 1.函数poll poll系统调用和select类似&#xff0c;也是在指定时间内轮询一定数量的文件描述符&#xff0c;以测试其中是否有就绪者。 #include <poll.h>int poll(struct pollfd…

FFT模板

整理了一下&#xff0c;自己写了一下模板 const double PIacos(-1.0); struct complex {double r,i;complex(double _r0,double _i0):r(_r),i(_i){}complex operator (const complex &b) {return complex(rb.r,ib.i);}complex operator -(const complex &b) {return c…

Linux网络编程---I/O复用模型之epoll

https://blog.csdn.net/men_wen/article/details/53456474 Linux网络编程—I/O复用模型之epoll 1. epoll模型简介 epoll是Linux多路服用IO接口select/poll的加强版&#xff0c;e对应的英文单词就是enhancement&#xff0c;中文翻译为增强&#xff0c;加强&#xff0c;提高&…

POJ 1741tree-点分治入门

学习了一下点分治&#xff0c;如果理解有误还请不吝赐教。 为了快速求得树上任意两点之间距离满足某种关系的点对数&#xff0c;我们需要用到这种算法。 点分治是树上的一种分治算法&#xff0c;依靠树和子树之间的关系进行分治从而降低复杂度。 和其他树上的算法有一些区别…

基于单链表的生产者消费者问题

『生产者与消费者问题分析』「原理」生产者生产产品&#xff0c;消费者消费产品。产品如果被消费者消费完了&#xff0c;同时生产者又没有生产出产品&#xff0c;消费者 就必须等待。同样的&#xff0c;如果生产者生产了产品&#xff0c;而消费者没有去消费&#x…

C++智能指针(一)智能指针的简单介绍

https://blog.csdn.net/nou_camp/article/details/70176949C智能指针 在正式了解智能指针前先看一下下面的一段代码 #include<iostream> using namespace std; class A { public:A():_ptr(NULL), _a(0){}~A(){} public:int* _ptr;int _a; };void test() {A a;int *p1 ne…

聪聪可可-点分治

聪聪和可可是兄弟俩&#xff0c;他们俩经常为了一些琐事打起来&#xff0c;例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑&#xff08;可是他们家只有一台电脑&#xff09;……遇到这种问题&#xff0c;一般情况下石头剪刀布就好了&#xff0c;可是他们已经玩儿…

C++智能指针(二)模拟实现三种智能指针

https://blog.csdn.net/nou_camp/article/details/70186721在上一篇博客中提到了Auto_ptr(C智能指针&#xff08;一&#xff09;)&#xff0c;下面进行模拟实现Auto_ptr 采用类模板实现 #include<iostream> using namespace std; template<class T> class Autoptr …

Prime Distance On Tree-树分治+FFT

题目描述 Problem description. You are given a tree. If we select 2 distinct nodes uniformly at random, what’s the probability that the distance between these 2 nodes is a prime number? Input The first line contains a number N: the number of nodes in this…

C++智能指针(三)总结

https://blog.csdn.net/nou_camp/article/details/70195795 在上一篇博客中&#xff08;C智能指针&#xff08;二&#xff09;&#xff09;模拟实现了三种智能指针。 其中最好的就是shared_ptr,但是这并不代表它就是最完美的&#xff0c;它也有问题&#xff0c;这个问题就是循环…

POJ2114-Boatherds-树分治

题目描述 Boatherds Inc. is a sailing company operating in the country of Trabantustan and offering boat trips on Trabantian rivers. All the rivers originate somewhere in the mountains and on their way down to the lowlands they gradually join and finally th…