IO复用模型有select、poll、epoll,关于它们的区别:
1.select能处理的fd数量很少,我的电脑是1024个(sizeof(fd_set)*8),而epoll的fd数量没有限制,只与内存大小有关。
2.select当fd就绪时,需要将所有fd拷贝到内核态再将活跃的fd拷贝回用户态,还有遍历fd数组判断哪个是活跃的。而epoll不需要频繁的拷贝,当fd就绪时调用回调函数插入到fd就绪链边中。
3.select是水平触发,epoll支持水平触发和边缘触发,边缘触发在调用epoll_wait时,只有fd从不可读/写到可读/写才通知给用户。而select只要fd的数据没有读完都会不断通知给用户,所以epoll读/写数据时要一次性完成(比如循环读写),否则造成fd死锁。
4.单线程多路复用应该设置nonblock套接字,否则某个客户端出现block就会影响其他fd的处理。
具体一点说就是:
调用select函数需要将用户buffer的fd拷贝到内核buffer进行轮询问,有事件发生又将它们拷贝回用户buffer(两个buffer都在物理内存上面)。既然都在内存上面为啥不公用一块内存呢?epoll就是这么做,mmap将地址空间的虚拟地址映射到一块物理内存,提供给用户和内核使用,减少了拷贝过程,同时那块内存用红黑树来存储fd(加速增删查改)。
服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <list>
#include <fcntl.h>
#include <sys/types.h>
#define IPADDRESS "127.0.0.1"
#define PORT 8888
#define MAXSIZE 1024
#define LISTENQ 5
#define FDSIZE 1024
#define EPOLLEVENTS 100
using namespace std;
//设置非阻塞socket,IO多路复用应该用非阻塞socket
void set_nonblock(int fd);
//添加、删除、修改事件到epoll
void do_event(int epollfd, int fd, int op, int state);
bool Read(int epollfd, int fd, char *buff, int size);
bool Send(int epollfd, int fd, char *buff);
list<int>all_fd;
int main() {
char buff[MAXSIZE];
char WARN[] = "just you online, honey.\n";
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd == -1){
perror("socket error:");
exit(1);
}
else puts("create socket done.");
struct sockaddr_in servaddr;
servaddr.sin_addr.s_addr = inet_addr(IPADDRESS);
servaddr.sin_port = htons(PORT);
servaddr.sin_family = AF_INET;
if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1){
perror("bind error:");
exit(1);
}
else puts("bind done.");
listen(listenfd, LISTENQ);
puts("listen done");
struct epoll_event events[EPOLLEVENTS];
int epollfd = epoll_create(FDSIZE);
do_event(epollfd, listenfd, EPOLL_CTL_ADD, EPOLLIN | EPOLLET);
set_nonblock(listenfd);
while(true){
int num = epoll_wait(epollfd, events, EPOLLEVENTS, -1);//阻塞epoll_wait
for(int i=0; i<num; ++i){
int fd = events[i].data.fd;
if(fd == listenfd){
printf("epolled get a new\n");
struct sockaddr_in cliaddr;
socklen_t cliaddrlen = sizeof(struct sockaddr_in);
int clifd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddrlen);
if(clifd == -1){
perror("accept error");
continue;
}
set_nonblock(clifd);
printf("accept a new client: %s %d, welcome!\n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
do_event(epollfd, clifd, EPOLL_CTL_ADD, EPOLLIN | EPOLLET);
all_fd.push_back(clifd);
}
else{
if(Read(epollfd, fd, buff, MAXSIZE)){
printf("success to read. %s %d\n",buff,all_fd.size());
if(all_fd.size() == 1){
Send(epollfd, fd, WARN);
}
else{
for(auto it=all_fd.begin(); it!=all_fd.end(); ++it){
int tfd = *it;
if(tfd != fd){
if(!Send(epollfd, tfd, buff)){
*it = -1;
}
}
}
all_fd.remove(-1);
}
}
else{
perror("server read message fail.");
continue;
}
}
}
}
close(epollfd);
close(listenfd);
return 0;
}
void set_nonblock(int fd){
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL)|O_NONBLOCK);
}
void do_event(int epollfd, int fd, int op, int state){
struct epoll_event ev;
ev.data.fd = fd;
ev.events = state;
epoll_ctl(epollfd, op, fd, &ev);
}
bool Read(int epollfd, int fd, char *buff, int size){
memset(buff, 0, MAXSIZE);
int num;
do{
num = recv(fd, buff, size, 0);
if(num < 0){
if(errno == EAGAIN){
return true;
}
else{
perror("read error:");
close(fd);
do_event(epollfd, fd, EPOLL_CTL_DEL, EPOLLIN);
return false;
}
}
else{
size -= num;
buff += num;
}
}while(num > 0);
printf("read done %s\n",buff);
return true;
}
bool Send(int epollfd, int fd, char *buff){
int size = strlen(buff);
while(size > 0){
int num = send(fd, buff, size, 0);
if(num < 0){
perror("send error:");
close(fd);
do_event(epollfd, fd, EPOLL_CTL_DEL, EPOLLIN);
return false;
}
else{
size -= num;
buff += num;
}
}
printf("perfect send\n");
return true;
}
客户端(或者直接TELNET连服务器)
客户端参考https://blog.csdn.net/qq_31564375/article/details/51581038的思路,开个子进程,父子进程管道相连,子进程负责写数据,父进程负责读数据和服务器通信。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <list>
#include <fcntl.h>
#include <sys/types.h>
#define IPADDRESS "127.0.0.1"
#define PORT 8888
#define MAXSIZE 1024
#define LISTENQ 5
#define FDSIZE 1024
#define EPOLLEVENTS 100
using namespace std;
//设置非阻塞socket,IO多路复用应该用非阻塞socket
void set_nonblock(int fd);
//添加、删除、修改事件到epoll
void do_event(int epollfd, int fd, int op, int state);
bool Read(int epollfd, int fd, char *buff, int size);
bool Send(int epollfd, int fd, char *buff);
int main(){
struct epoll_event events[3];
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(IPADDRESS);
servaddr.sin_port = htons(PORT);
int serfd = socket(AF_INET, SOCK_STREAM, 0);
if(serfd == -1){
perror("socket create fail.\n");
exit(-1);
}
else puts("socket create done.");
if(connect(serfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
perror("connect fail.\n");
exit(-1);
}
else puts("connect done.");
set_nonblock(serfd);
int pip_fd[2];
if(pipe(pip_fd) < 0){
perror("pipe error.");
exit(-1);
}
else puts("pipe done.");
int epollfd = epoll_create(FDSIZE);
do_event(epollfd, serfd, EPOLL_CTL_ADD, EPOLLIN | EPOLLET);
do_event(epollfd, pip_fd[0], EPOLL_CTL_ADD, EPOLLIN | EPOLLET);
bool cliworking = true;
int pid = fork();
if(pid < 0){
perror("fork error.");
exit(-1);
}
else if(pid == 0){
close(pip_fd[0]);
printf("print exit to leave.\n");
while(cliworking){
char buff[MAXSIZE]={0};
scanf("%s",buff);
if(strncmp("exit", buff, 4) == 0) cliworking = 0;
write(pip_fd[1], buff, strlen(buff));
}
close(serfd);
close(pip_fd[1]);
close(epollfd);
exit(0);
}
else{
close(pip_fd[1]);
while(cliworking){
int num = epoll_wait(epollfd, events, 2, -1);
for(int i=0; i<num; ++i){
int fd = events[i].data.fd;
if(fd == serfd){
char buff[MAXSIZE]={0};
if(Read(epollfd, fd, buff, MAXSIZE)){
printf("%s\n",buff);
}
else{
perror("server unconnect.");
cliworking = 0;
}
}
else{
char buff[MAXSIZE]={0};
read(fd, buff, MAXSIZE);
if(strncmp("exit", buff, 4) == 0){
printf("here\n");
cliworking = 0;
}
else if(!Send(epollfd, serfd, buff)){
perror("send error.");
}
}
}
}
close(pip_fd[0]);
close(serfd);
close(epollfd);
exit(0);
}
return 0;
}
void set_nonblock(int fd){
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL)|O_NONBLOCK);
}
void do_event(int epollfd, int fd, int op, int state){
struct epoll_event ev;
ev.data.fd = fd;
ev.events = state;
epoll_ctl(epollfd, op, fd, &ev);
}
bool Read(int epollfd, int fd, char *buff, int size){
memset(buff, 0, MAXSIZE);
int num;
do{
num = recv(fd, buff, size, 0);
if(num < 0){
if(errno == EAGAIN){
return true;
}
else{
perror("read error:");
close(fd);
do_event(epollfd, fd, EPOLL_CTL_DEL, EPOLLIN);
return false;
}
}
else{
size -= num;
buff += num;
}
}while(num > 0);
return true;
}
bool Send(int epollfd, int fd, char *buff){
int size = strlen(buff);
while(size > 0){
int num = send(fd, buff, size, 0);
if(num < 0){
perror("send error:");
close(fd);
do_event(epollfd, fd, EPOLL_CTL_DEL, EPOLLIN);
return false;
}
else{
size -= num;
buff += num;
}
}
return true;
}