目录
一、概念
二、语法
1.select
1.1 select函数的语法
1.2 文件描述符集合操作
1.3 select函数的优缺点
2.epoll
2.1 epoll语法
2.2 epoll的工作模式
2.3 epoll的优缺点
三、select服务端代码
四、epoll服务端代码
五、客户端代码
IO多路复用是一种同步的I/O模型,它允许一个进程同时监视多个文件描述符,一旦某个文件描述符就绪(可以进行I/O操作),就能够通知程序进行相应的读写操作。
IO多路复用有三种实现方式:select、poll、epoll。
select是最早的I/O多路复用技术之一,它使用fd_set数据结构来存储和跟踪文件描述符。
select是基于线性方式处理待检测集合的,因此每次都要遍历集合;对于返回的集合,还需要判断文件描述符是否就绪。
select ( int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout );
epoll是一种高效且可扩展的I/O多路复用技术。epoll是基于红黑树来处理待检测集合的,使用回调机制,处理效率高;其返回的集合中的文件描述符都是就绪的,无需再次进行检测。
epoll主要通过以下三个函数来实现其功能:epoll_create、epoll_ctl、epoll_wait。
epoll_create (int size);
创建一个epoll对象,返回一个文件描述符,用于后续的操作,参数size大于0即可。
epoll_ctl ( int epfd, int op, int fd,
struct epoll_event *event );
向epoll对象中添加、修改或删除文件描述符。
struct epoll_event
{
uint32_t events;
epoll_data_t data;
};typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
};
epoll_event结构体用于描述事件,成员events表示事件类型:
epoll_data为联合体,通常使用int类型的fd,用于存储发生对应事件的文件描述符。
epoll_wait ( int epfd, struct epoll_event *events,
int maxevents, int timeout );
等待epoll对象中的事件发生,返回发生事件的文件描述符集合。
水平触发是epoll的默认工作模式。在这种模式下,当文件描述符上的事件就绪时,epoll_wait函数会返回,并且如果该事件没有被处理,epoll_wait函数会在下一次调用时再次返回,直到该事件被处理。例如,当一个套接字上有数据可读时,epoll_wait会返回,并且如果数据没有被读取,epoll_wait会在下一次调用时再次返回,直到数据被读取。
边沿触发模式下,epoll_wait函数只会在文件描述符的状态发生变化时返回。例如,当一个套接字上有数据可读时,epoll_wait会返回,但是如果数据没有被读取,epoll_wait不会在下一次调用时再次返回,直到有新的数据到达。因此在边沿触发模式下,程序需要在一次epoll_wait调用返回后,立即处理所有就绪的事件。
epoll在边沿模式下,必须将套接字设置为非阻塞模式,此时需要循环读取读缓冲区的数据,读取完后recv函数会返回-1,此时需要特殊处理。
#include
#include
#include
#include
#include
#include
using namespace std;
int main(int argc, char* argv[])
{
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1) {
cerr << "error" << endl;
return -1;
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(8080);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(lfd, (struct sockaddr*)&local, sizeof(local)) == -1) {
cerr << "error" << endl;
return -1;
}
if (listen(lfd, 128) == -1) {
cerr << "error" << endl;
return -1;
}
fd_set readset;
FD_ZERO(&readset);
FD_SET(lfd, &readset);
int maxfd = lfd;
while (true) {
fd_set tmp = readset;
int ret = select(maxfd + 1, &tmp, NULL, NULL, NULL);
if (FD_ISSET(lfd, &tmp)) {
int cfd = accept(lfd, NULL, NULL);
FD_SET(cfd, &readset);
maxfd = max(maxfd, cfd);
cout << "connect: " << cfd << endl;
}
for (int i = 0; i <= maxfd; i++) {
if (i != lfd && FD_ISSET(i, &tmp)) {
char buffer[1024] = { 0 };
int len = recv(i, buffer, sizeof buffer, 0);
if (len == -1) {
cerr << "error" << endl;
return -1;
}
else if (len == 0) {
cout << "client disconnect: " << i << endl;
FD_CLR(i, &readset);
close(i);
break;
}
cout << buffer << endl;
for (int i = 0; i < len; i++) {
buffer[i] = toupper(buffer[i]);
}
len = send(i, buffer, strlen(buffer) + 1, 0);
if (len == -1) {
cerr << "error" << endl;
return -1;
}
}
}
}
close(lfd);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main(int argc, char* argv[])
{
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1) {
cerr << "error" << endl;
return -1;
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(8080);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(lfd, (struct sockaddr*)&local, sizeof(local)) == -1) {
cerr << "error" << endl;
return -1;
}
if (listen(lfd, 128) == -1) {
cerr << "error" << endl;
return -1;
}
int epfd = epoll_create(1);
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &event);
struct epoll_event events[1024];
while (true) {
int fds = epoll_wait(epfd, events, 1024, -1);
for (int i = 0; i < fds; i++) {
int fd = events[i].data.fd;
if (fd == lfd) {
int cfd = accept(lfd, NULL, NULL);
struct epoll_event client_event;
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
client_event.events = EPOLLIN | EPOLLET;
client_event.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &client_event);
cout << "connect: " << cfd << endl;
}
else {
while(true) {
char buffer[128] = { 0 };
int len = recv(fd, buffer, sizeof buffer, 0);
if (len == 0) {
cout << "client disconnect: " << fd << endl;
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
break;
}
else if (len > 0) {
cout << buffer << endl;
for (int i = 0; i < len; i++) {
buffer[i] = toupper(buffer[i]);
}
len = send(fd, buffer, strlen(buffer) + 1, 0);
if (len == -1) {
cerr << "error" << endl;
return -1;
}
}
else if (len == -1) {
if (errno = EAGAIN) {
cout << "Data read complete." << endl;
break;
}
else {
cerr << "error" << endl;
return -1;
}
}
}
}
}
}
close(lfd);
return 0;
}
#include
#include
#include
#include
#include
#include
using namespace std;
int main(void)
{
int client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket == -1) {
cerr << "error" << endl;
return -1;
}
struct sockaddr_in target;
target.sin_family = AF_INET;
target.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &target.sin_addr.s_addr);
if (connect(client_socket, (struct sockaddr*)&target, sizeof target) == -1) {
cerr << "error" << endl;
close(client_socket);
return -1;
}
while (true) {
char buffer1[1024] = { 0 };
cout << "enter: ";
cin >> buffer1;
send(client_socket, buffer1, strlen(buffer1), 0);
char buffer2[1024] = { 0 };
int ret = recv(client_socket, buffer2, sizeof buffer2, 0);
if (ret <= 0) {
cout << "server disconnect." << endl;
}
cout << buffer2 << endl;
}
close(client_socket);
return 0;
}
参考内容:
爱编程的大丙