目录
一、多路复用IO
1.1.select
1.缺点
1.select监听的文件描述符集合是一个数组,有上限(1024个)
2.select监听的文件描述符集合在应用层,内核层监听事件后需要传递给用户层带来资源开销
3.select需要用户手动查找产生事件的文件描述符
4.select只能工作在水平触发模式(低速模式)而无法工作在边沿触发模式(高速模式)
1.2.poll
1.函数接口
poll()
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能:
监听文件描述符集合,工作方式类似于select
参数:
fds:文件描述符集合首地址
nfds:文件描述符集合的数组的长度
timeout:超时时间,单位毫秒,-1表示永久等待
返回值:
成功返回产生事件文件描述符个数
失败返回-1
超时仍然没有产生的事件返回0
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
2.缺点
1.poll监听的文件描述符集合在应用层,内核层监听事件后需要传递给用户层带来资源开销
2.poll需要用户手动查找产生事件的文件描述符
3.poll只能工作在水平触发模式(低速模式)而无法工作在边沿触发模式(高速模式)
3.实例
使用poll实现TCP的并发模型,多个客户端可以并发访问服务端
client.c
#include "../head.h"
int CreateTcpConnection(const char *pip, int port)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = inet_addr(pip);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
return -1;
}
ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
return -1;
}
return sockfd;
}
int HandleConnection(int sockfd)
{
char tmpbuff[4096] = {0};
static int cnt = 0;
ssize_t nsize = 0;
sprintf(tmpbuff, "hello world --- %d", cnt);
nsize = send(sockfd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
cnt++;
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
else if (0 == nsize)
{
return 0;
}
printf("RECV:%s\n", tmpbuff);
return nsize;
}
int main(void)
{
int sockfd = 0;
int ret = 0;
sockfd = CreateTcpConnection(SER_IP, SER_PORT);
if (-1 == sockfd)
{
printf("连接服务器异常\n");
return -1;
}
while (1)
{
ret = HandleConnection(sockfd);
if (-1 == ret)
{
printf("连接出错!\n");
break;
}
else if (0 == ret)
{
printf("连接关闭\n");
break;
}
sleep(1);
}
close(sockfd);
return 0;
}
serve.c(核心实现)
#include "../head.h"
#define MAX_POLL_FDNUM 10000
int CreateListenSocket(const char *pip, int port)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in seraddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = inet_addr(pip);
ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
return -1;
}
ret = listen(sockfd, 10);
if (-1 == ret)
{
return -1;
}
return sockfd;
}
int HandleConnection(int confd)
{
char tmpbuff[4096] = {0};
ssize_t nsize = 0;
nsize = recv(confd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
else if (0 == nsize)
{
return 0;
}
printf("RECV:%s\n", tmpbuff);
sprintf(tmpbuff, "%s --- echo", tmpbuff);
nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
return nsize;
}
//初始化pollfd数组
int InitPollFd(struct pollfd *pfds, int maxlen)
{
int i = 0;
for (i = 0; i < maxlen; i++)
{
pfds[i].fd = -1;
}
return 0;
}
//添加监听套接字到pollfd数组中
int AddPollDFd(struct pollfd *pfds, int maxlen, int fd, short env)
{
int i = 0;
for (i = 0; i < maxlen; i++ )
{
if (pfds[i].fd == -1)
{
pfds[i].fd = fd;
pfds[i].events = env;
break;
}
}
}
//删除pollfd数组中监听套接字
int DelPollFd(struct pollfd *pfds, int maxlen, int tmpfd)
{
int i = 0;
for (i = 0; i < maxlen; i++)
{
if (pfds[i].fd == tmpfd)
{
pfds[i].fd = -1;
pfds[i].events = 0;
break;
}
}
return 0;
}
int main(void)
{
int sockfd = 0;
int confd = 0;
int ret = 0;
int nready = 0;
struct pollfd fds[MAX_POLL_FDNUM];
int i = 0;
sockfd = CreateListenSocket(SER_IP, SER_PORT);
if (-1 == sockfd)
{
printf("创建监听套接字失败\n");
return -1;
}
InitPollFd(fds, MAX_POLL_FDNUM);
AddPollDFd(fds, MAX_POLL_FDNUM, sockfd, POLLIN);
while (1)
{
//1.检测套接字
nready = poll(fds, MAX_POLL_FDNUM, -1);
if (nready == -1)
{
perror("poll");
return -1;
}
//2.遍历fds数组,检测事件发生的套接字
for (i = 0; i < MAX_POLL_FDNUM; i++)
{
if (fds[i].fd == -1)
{
continue;
}
if (fds[i].events & fds[i].revents && fds[i].fd == sockfd)
{
confd = accept(sockfd, NULL, NULL);
if (confd == -1)
{
printf("连接失败!\n");
DelPollFd(fds, MAX_POLL_FDNUM, sockfd);
close(sockfd);
}
AddPollDFd(fds, MAX_POLL_FDNUM, confd, POLLIN);
}else if (fds[i].events & fds[i].revents && fds[i].fd != sockfd)
{
ret = HandleConnection(fds[i].fd);
if (-1 == ret)
{
printf("接收异常!\n");
DelPollFd(fds, MAX_POLL_FDNUM, fds[i].fd);
close(fds[i].fd);
continue;
}else if (0 == ret)
{
printf("关闭连接!\n");
DelPollFd(fds, MAX_POLL_FDNUM, fds[i].fd);
close(fds[i].fd);
continue;
}
}
}
}
return 0;
}
1.3.epoll
1.函数接口
epoll_creat()
int epoll_create(int size);
功能:
在内核层创建一张epoll监听的事件表
参数:
size:监听的事件表大小
返回值:
成功返回新的文件描述符
失败返回-1
epoll_ctl()
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:
管理内核中epoll事件表
参数:
epfd:文件描述符
op: EPOLL_CTL_ADD 添加客户端
EPOLL_CTL_MOD 修改客户端
EPOLL_CTL_DEL 删除客户端
fd:文件描述符
event:事件结构体
返回值:
成功返回0
失败返回-1
epoll_wait()
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:
监听epfd对应的事件表中是否有事件发生
参数:
epfd:文件描述符
events:存放产生事件的数组空间首地址
maxevents:最多存放数组元素个数
timeout:超时时间,单位毫秒,-1表示永久等待
返回值:
成功返回实际发生事件的个数
失败返回-1
超时仍然没有产生事件返回0
2.优点
1.epoll没有文件描述符上限限制
2.epoll监听的事件表在内核层,内核监听事件不需要操作用户层空间提高效率
3.epoll会获得产生事件的文件描述符,不需要用户查找
4.epoll可以工作在边沿触发模式(高速模式),提高效率
3.实例
使用epoll实现TCP的并发模型,多个客户端可以并发访问服务端
client.c
#include "../head.h"
int CreateTcpConnection(const char *pip, int port)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = inet_addr(pip);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
return -1;
}
ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
return -1;
}
return sockfd;
}
int HandleConnection(int sockfd)
{
char tmpbuff[4096] = {0};
static int cnt = 0;
ssize_t nsize = 0;
sprintf(tmpbuff, "hello world --- %d", cnt);
nsize = send(sockfd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
cnt++;
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
else if (0 == nsize)
{
return 0;
}
printf("RECV:%s\n", tmpbuff);
return nsize;
}
int main(void)
{
int sockfd = 0;
int ret = 0;
sockfd = CreateTcpConnection(SER_IP, SER_PORT);
if (-1 == sockfd)
{
printf("连接服务器异常\n");
return -1;
}
while (1)
{
ret = HandleConnection(sockfd);
if (-1 == ret)
{
printf("连接出错!\n");
break;
}
else if (0 == ret)
{
printf("连接关闭\n");
break;
}
sleep(1);
}
close(sockfd);
return 0;
}
serve.c(核心实现)
#include "../head.h"
#define MAX_POLL_FDNUM 10000
int CreateListenSocket(const char *pip, int port)
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in seraddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(port);
seraddr.sin_addr.s_addr = INADDR_ANY;
ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (-1 == ret)
{
return -1;
}
ret = listen(sockfd, 10);
if (-1 == ret)
{
return -1;
}
return sockfd;
}
int HandleConnection(int confd)
{
char tmpbuff[4096] = {0};
ssize_t nsize = 0;
nsize = recv(confd, tmpbuff, sizeof(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
else if (0 == nsize)
{
return 0;
}
printf("RECV:%s\n", tmpbuff);
sprintf(tmpbuff, "%s --- echo", tmpbuff);
nsize = send(confd, tmpbuff, strlen(tmpbuff), 0);
if (-1 == nsize)
{
return -1;
}
return nsize;
}
int AddFd(int epfd, int fd, uint32_t tmpenvent)
{
struct epoll_event env;
int ret = 0;
env.events = tmpenvent;
env.data.fd = fd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &env);
if (-1 == ret)
{
perror("fail to epoll_ctl");
return -1;
}
return ret;
}
int DelFd(int epfd, int fd)
{
int ret = 0;
ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
if (-1 == ret)
{
perror("fail to epoll_ctl");
return -1;
}
return ret;
}
int main(void)
{
int sockfd = 0;
int confd = 0;
int ret = 0;
int epfd = 0;
int nready = 0;
int i = 0;
struct epoll_event retenv[MAX_POLL_FDNUM];
sockfd = CreateListenSocket(SER_IP, SER_PORT);
if (-1 == sockfd)
{
printf("创建监听套接字失败\n");
return -1;
}
epfd = epoll_create(MAX_POLL_FDNUM);
if (-1 == epfd)
{
perror("fail to epoll_create");
return -1;
}
ret = AddFd(epfd, sockfd, EPOLLIN);
if (-1 == ret)
{
printf("添加套接字失败\n");
return -1;
}
while (1)
{
nready = epoll_wait(epfd, retenv, MAX_POLL_FDNUM, -1);
if (-1 == nready)
{
perror("fail to epoll_wait");
return -1;
}
for (i = 0; i < nready; i++)
{
if (retenv[i].data.fd == sockfd)
{
confd = accept(sockfd, NULL, NULL);
if (-1 == confd)
{
printf("处理连接失败\n");
DelFd(epfd, sockfd);
close(sockfd);
continue;
}
AddFd(epfd, confd, EPOLLIN);
}
else if (retenv[i].data.fd != sockfd)
{
ret = HandleConnection(retenv[i].data.fd);
if (-1 == ret)
{
printf("接收异常!\n");
DelFd(epfd, retenv[i].data.fd);
close(retenv[i].data.fd);
continue;
}
else if (0 == ret)
{
printf("关闭连接!\n");
DelFd(epfd, retenv[i].data.fd);
close(retenv[i].data.fd);
continue;
}
}
}
}
close(epfd);
return 0;
}
1.4.多线程实现
注意:需要将线程属性分离,自动回收线程空间,防止使用回收函数接口发生阻塞
1.5.多进程实现
二、总结
2024年8月21日,学习的第37天。今天学完了所有的多路复用IO,其中呢效率最高的是epoll。 也可以使用前面学习的线程和进程来实现,不过这个的客户端个数很局限,但也可以实现。不妨我们下去使用UDP结合多进程来实现,多人的在线聊天室。
加油!