一、介绍
1、多进程服务器的缺点:
1、需要大量的运算
2、大量的内存空间
所需的线程数 n(n-1)/2,计算复杂度O(n^2)。
2、select模型:
所需的线程数 n,计算复杂度O(n)。
二、Select模型具体步骤:
步骤
设置文件描述符
select函数监视多个(不超过1024个)文件描述符
fd_set结构体
FD_ZERO(fd set *fdset)∶将fd set变量的所有位初始化为0。
FD_SET(int fd,fd set*fdset)∶在参数dset指向的变量中注册文件描述符fd的信息。
FD_CLR(int fd,fd set*fdset)∶从参数fdset指向的变量中清除文件描述符fd的信息。
FD_ISSET(int fd,fd_set*fdset)∶若参数fdset指向的变量中包含文件描述符fd的信息,则返回"真"。
Select函数
#include<sys/select.h>#include <sys/time.h>
int select(int maxfd, fd_set*readset, fd_set* writeset, fd_set*exceptset, const struct
timeval * timeout);
→成功时返回大于0的值,失败时返回-1。
maxtfd :监视对象文件描述符数量。
readset:用于检查可读性,
writeset:用于检查可写性,
exceptset:用于检查带外数据,
timeout:一个指向timeval结构的指针,用于决定select等待I/O的最长时间。如果为空将一直等待。
Select实现I/O复用服务端
代码:
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <iostream>
#include <pthread.h>
#include <semaphore.h>
#include <sys/select.h>
#include <sys/times.h>
void server101()
{
//套接字
//创建变量
int serv_sock{}, clnt_sock{};
struct sockaddr_in serv_adr {}, clnt_adr{};
socklen_t clnt_sz = sizeof(clnt_adr);
//赋值
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(9527);
//bind
if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
{
//绑定失败
printf("bind error %d %s\n", errno, strerror(errno)); //打印出错误编号和含义
close(serv_sock);
return;
}
//listen
if (listen(serv_sock, 5) == -1)
{
printf("listen error %d %s\n", errno, strerror(errno)); //打印出错误编号和含义
close(serv_sock);
return;
}
//!!!!!!!单线程程序 accept() 卡在此处
//!
fd_set reads{}, copy_reads{}; //设置文件描述符
FD_ZERO(&reads); //清空
FD_SET(serv_sock, &reads); //监控serv_sock上的读
timeval timeout{ 5,5000 }; //
int max_sock = serv_sock;
int fd_num{};
while (true)
{
copy_reads = reads; //!!!!!max_sock+1不能超过1024!!!!!!!
fd_num = select(max_sock + 1, ©_reads, NULL, NULL, &timeout);
if (fd_num == -1)
{
printf("select error %d %s\n", errno, strerror(errno)); //打印出错误编号和含义
close(serv_sock);
return;
}
if (fd_num == 0) continue;
for (int i = 0; i < max_sock + 1; i++) //!!!!CPU一直空转,资源浪费
{
if (FD_ISSET(i, ©_reads))
{
if (i == serv_sock) //服务器上发生的读事件 只可能是有连接进来
{
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_sz);
FD_SET(clnt_sock, &reads);
if (max_sock < clnt_sock)
{
max_sock = clnt_sock;
}
printf("client is connected:%d\n", clnt_sock);
}
else //客户端上发生事件 回声服务器
{
char buffer[256] = "";
ssize_t str_len = read(i, buffer, sizeof(buffer));
if (str_len == 0) //read的返回值是0 客户端已经结束
{
FD_CLR(i, &reads);
close(i);
printf("client is disconnected:%d\n", i);
}
else
{
write(i, buffer, str_len);
}
}
}
}
}
}
void client101()
{
int cln_sock = socket(PF_INET, SOCK_STREAM, 0);
sockaddr_in serv_adr;
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_adr.sin_port = htons(9527);
if (connect(cln_sock, (sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
{
printf("connect error %d %s\n", errno, strerror(errno)); //打印出错误编号和含义
close(cln_sock);
return;
}
char message[256] = "";
while (true)
{
printf("Input message(q/Q to quit):");
fgets(message, sizeof(message), stdin);
if (!strcmp(message, "q\n") || !strcmp(message, "q\n")) break;
write(cln_sock, message, strlen(message));
memset(message, 0, sizeof(message));
read(cln_sock, message, sizeof(message));
printf("server:%s\n", message);
}
close(cln_sock);
}
void lession101(const char* arg)
{
if (strcmp(arg, "s") == 0) //如果用户输入的是s 就启动服务端
{
server101();
}
else
{
client101();
}
}
int main(int argc, char* argv[])
{
lession101(argv[1]);
return 0;
}
三、Epoll模型
Epoll的三大函数:epoll_create,epoll_wait, epoll_ctl
epoll_create
#include<sys/epoll.h>
int epoll_create(int size);
→成功时返回epoll文件描述符,失败时返回-1。
size:epoll实例的大小。
该函数从2.3.2版本的开始加入的,2.6版开始引入内核
Linux最新的内核稳定版本已经到了5.8.14,长期支持版本到了5.4.70
从2.6.8内核开始的Linux,会忽略这个参数,但是必须要大于0
这个是Linux独有的函数
epoll_ctl
#include<sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
→成功时返回0,失败时返回-1。
●epfd 用于注册监视对象的epoll例程的文件描述符。
●op 用于指定监视对象的添加、删除或更改等操作。
EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL
● fd 需要注册的监视对象文件描述符。
● event 监视对象的事件类型。
EPOLLIN∶需要读取数据的情况。
EPOLLOUT∶输出缓冲为空,可以立即发送数据的情况。
EPOLLPRI∶收到OOB数据的情况。
EPOLLRDHUP∶断开连接或半关闭的情况,这在边缘触发方式下非常有用。
EPOLLERR∶发生错误的情况。
EPOLLET∶以边缘触发的方式得到事件通知。
EPOLLONESHOT∶发生一次事件后,相应文件描述符不再收到事件通知。因此需要向
epoll_ctl函数的第二个参数传递
EPOLLCTL_MOD,再次设置事件。
epoll_wait
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event*events,int maxevents,int timeout);
→成功时返回发生事件的文件描述符数,失败时返回-1。
epfd 表示事件发生监视范围的epol例程的文件描述符
events 保存发生事件的文件描述符集合的结构体地址值。
maxevents 第二个参数中可以保存的最大事件数目。
Timeout:以1/1000秒为单位的等待时间,传递-1时,一直等待直到发生事件。
Epoll实现I/O复用服务端
代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <iostream>
#include <pthread.h>
#include <semaphore.h>
#include <sys/select.h>
#include <sys/times.h>
#include <sys/epoll.h>
void server102()
{
//套接字socket
//创建变量
int serv_sock{}, clnt_sock{};
struct sockaddr_in serv_adr {}, clnt_adr{};
socklen_t clnt_sz = sizeof(clnt_adr);
char buf[1024] = ""; //收发缓冲区
//赋值
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(9527);
//连接bind
if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
{
//绑定失败
printf("bind error %d %s\n", errno, strerror(errno)); //打印出错误编号和含义
close(serv_sock);
return;
}
//listen
if (listen(serv_sock, 5) == -1)
{
printf("listen error %d %s\n", errno, strerror(errno)); //打印出错误编号和含义
close(serv_sock);
return;
}
//!!!!!!!epoll
epoll_event event;
int epfd, event_cnt;
epfd = epoll_create(1); //参数大于0即可
if (epfd == -1)
{
printf("epoll_create error %d %s\n", errno, strerror(errno)); //打印出错误编号和含义
close(serv_sock);
return;
}
epoll_event* all_events = new epoll_event[100];
event.events = EPOLLIN;
event.data.fd = serv_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);
while (true)
{
event_cnt = epoll_wait(epfd, all_events, 100, 1000);//最后1000表示等待1s -1则无限等
if (event_cnt==-1)
{
printf("epoll_wait error %d %s\n", errno, strerror(errno));
break;
}
if(event_cnt == 0) continue;
for (int i = 0; i < event_cnt; i++)
{
if (all_events[i].data.fd == serv_sock)
{
clnt_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_sz);
event.events = EPOLLIN;
event.data.fd = clnt_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
printf("client is connected! %d\n", clnt_sock);
}
else
{
ssize_t len = read(all_events[i].data.fd, buf, sizeof(buf));
if (len <= 0)
{
epoll_ctl(epfd, EPOLL_CTL_DEL, all_events[i].data.fd, NULL);
close(all_events[i].data.fd);
printf("client is closed! %d\n", all_events[i].data.fd);
}
else
{
write(all_events[i].data.fd, buf, len);
}
}
}
{
}
}
delete[] all_events;
close(serv_sock);
close(epfd);
}
void client102()
{
int cln_sock = socket(PF_INET, SOCK_STREAM, 0);
sockaddr_in serv_adr;
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_adr.sin_port = htons(9527);
if (connect(cln_sock, (sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
{
printf("connect error %d %s\n", errno, strerror(errno)); //打印出错误编号和含义
close(cln_sock);
return;
}
char message[256] = "";
while (true)
{
printf("Input message(q/Q to quit):");
fgets(message, sizeof(message), stdin);
if (!strcmp(message, "q\n") || !strcmp(message, "q\n")) break;
write(cln_sock, message, strlen(message));
memset(message, 0, sizeof(message));
read(cln_sock, message, sizeof(message));
printf("server:%s\n", message);
}
close(cln_sock);
}
//epoll
void lession102(const char* arg)
{
if (strcmp(arg, "s") == 0) //如果用户输入的是s 就启动服务端
{
server102();
}
else
{
client102();
}
}
int main(int argc, char* argv[])
{
lession102(argv[1]);
return 0;
}