一个服务器,有多个客户端访问。
可以每个请求都开启一个线程处理。
缺点:
多线程的弊端,CPU需要不停的切换上下文,过程繁琐,消耗资源多
单线程IO多路复用
在linux中每一个网络连接都可以看成是一个文件描述符,一切皆文件。
1.select
fdset是一个bitmap,每一位标志了对应fd是否有数据变化。select监听fdset,当有数据变化时,返回fdset。
四个常用宏:
- FD_ZERO(&set);//将套接字集合清空
- FD_SET(s, &set);//将给定的套接字添加到集合
- FD_CLR(int ,fd_set*);//将一个给定的文件描述符从集合中删除
- FD_ISSET(int ,fd_set*);//检测fd在fdset集合中的状态是否变化,当检测到变化时,返回真,否则返回假
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1, stdineof;
fd_set rset;//关键数据结构
char buf[MAXLINE];
int n;
stdineof = 0;
FD_ZERO(&rset);//清零
for ( ; ; ) {
if (stdineof == 0)
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);//关联socket
maxfdp1 = max(fileno(fp), sockfd) + 1;//最大的文件描述符 要加1
Select(maxfdp1, &rset, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
if (stdineof == 1)
return; /* normal termination */
else
err_quit("str_cli: server terminated prematurely");
}
Write(fileno(stdout), buf, n);
}
if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
stdineof = 1;
Shutdown(sockfd, SHUT_WR); /* send FIN */
FD_CLR(fileno(fp), &rset);
continue;
}
Writen(sockfd, buf, n);
}
}
}
缺点:
- 上限最多监听1024个文件描述符
- 每次轮询都要重置fdset
- 每次轮询都要从内核态拷贝数据到用户态
- O(n) 遍历复杂度
FD_SETSIZE windows上限64,linux 1024 可调整,但有上限
当有数据过来时
- 会将fd相对的rset置位
- select 返回
2.poll
特点:
引入了数据结构
struct pollfd{
int fd;
short events;//POLLIN读 POLLOUT写
shoer revents;
}
/* include fig01 */
#include "unp.h"
#include <limits.h> /* for OPEN_MAX */
int
main(int argc, char **argv)
{
int i, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXLINE];
socklen_t clilen;
struct pollfd client[OPEN_MAX];
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
client[0].fd = listenfd;
client[0].events = POLLRDNORM;
for (i = 1; i < OPEN_MAX; i++)
client[i].fd = -1; /* -1 indicates available entry */
maxi = 0; /* max index into client[] array */
/* end fig01 */
/* include fig02 */
for ( ; ; ) {
nready = Poll(client, maxi+1, INFTIM);
if (client[0].revents & POLLRDNORM) { /* new client connection */
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
#ifdef NOTDEF
printf("new client: %s\n", Sock_ntop((SA *) &cliaddr, clilen));
#endif
for (i = 1; i < OPEN_MAX; i++)
if (client[i].fd < 0) {
client[i].fd = connfd; /* save descriptor */
break;
}
if (i == OPEN_MAX)
err_quit("too many clients");
client[i].events = POLLRDNORM;
if (i > maxi)
maxi = i; /* max index in client[] array */
if (--nready <= 0)
continue; /* no more readable descriptors */
}
for (i = 1; i <= maxi; i++) { /* check all clients for data */
if ( (sockfd = client[i].fd) < 0)
continue;
if (client[i].revents & (POLLRDNORM | POLLERR)) {
if ( (n = read(sockfd, buf, MAXLINE)) < 0) {
if (errno == ECONNRESET) {
/* connection reset by client */
#ifdef NOTDEF
printf("client[%d] aborted connection\n", i);
#endif
Close(sockfd);
client[i].fd = -1;
} else
err_sys("read error");
} else if (n == 0) {
/* connection closed by client */
#ifdef NOTDEF
printf("client[%d] closed connection\n", i);
#endif
Close(sockfd);
client[i].fd = -1;
} else
Writen(sockfd, buf, n);
if (--nready <= 0)
break; /* no more readable descriptors */
}
}
}
}
/* end fig02 */
当有数据到来时,会返回数据的数量
解决了select的缺点1,2
3.epoll
epoll_create
epoll_ctl 添加事件
epoll_wait
结构体
struct epoll_event
#include "intf.h"
#include "socket.h"
#include <algorithm>
#define MAXEVENTS 64
struct MSG
{
string strBuff;
SOCKET s;
};
// 采用epoll异步机制实现服务器
int main()
{
// 创建socket
string ip = "192.168.176.131";
int port = 1122;
TcpSocket tcp(AF_INET, SOCK_STREAM);
tcp.Bind(ip, port);
tcp.Listen(MAXEVENTS);
// 设置socket为O_NONBLOCK
int flags = fcntl(tcp.m_Sock, F_GETFL, 0);
if(!(flags & O_NONBLOCK))
{
flags |= O_NONBLOCK;
}
int exflags = fcntl(tcp.m_Sock, F_SETFL, 0);
if(exflags == -1)
{
cout << "fcntl F_SETFL o_NONBLOCK faild" << endl;
return -1;
}
// 创建epoll,该参数在新版本的linux中已经没有作用了
int epollftd = epoll_create(MAXEVENTS);
if(epollftd == -1)
{
cout << "epoll create faild" << endl;
return -1;
}
// 将服务区socket描述符添加到epoll
epoll_event event;
event.events = EPOLLIN | EPOLLET;
event.data.fd = tcp.m_Sock;
int ret = epoll_ctl(epollftd, EPOLL_CTL_ADD, tcp.m_Sock, &event);
if(ret == -1)
{
cout << "epoll_ctrl EPOLL_CTL_ADD faild" << endl;
return -1;
}
epoll_event *pEvents = (epoll_event *)calloc(MAXEVENTS, sizeof(epoll_event));
if(!pEvents)
{
cout << "calloc epoll events faild" << endl;
return -1;
}
// 事件处理
while(1)
{
int nEventNum = epoll_wait(epollftd, pEvents, MAXEVENTS, 500);
for(int i = 0; i < nEventNum; i ++)
{
// 新连接到来
if(pEvents[i].data.fd == tcp.m_Sock)
{
cout << "准备接收客户端的连接" << endl;
SOCKET client = tcp.Accept();
cout << "有客户端连接" << endl;
// 添加到队列中
epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = client;
epoll_ctl(epollftd, EPOLL_CTL_ADD, client, &ev);
}
else if(pEvents[i].events & EPOLLIN)
{
string strBuff = tcp.Recv(pEvents[i].data.fd);
cout << "接收到客户端数据:" << strBuff << endl;
if(strBuff == "EOF")
{
cout << "客户端:" << pEvents[i].data.fd << "请求断开连接" << endl;
}
string strSend = "";
transform(strBuff.begin(), strBuff.end(), back_inserter(strSend), ::toupper);
MSG msg;
msg.strBuff = strSend;
msg.s = pEvents[i].data.fd;
epoll_event ev;
ev.data.fd = pEvents[i].data.fd;
ev.events = EPOLLOUT | EPOLLET;
ev.data.ptr = (void *)&msg;
epoll_ctl(epollftd, EPOLL_CTL_MOD, pEvents[i].data.fd, &ev);
}
else if(pEvents[i].events & EPOLLOUT)
{
MSG *pMsg = (MSG*)pEvents[i].data.ptr;
cout << "向客户端输出信息:" << pMsg->strBuff << endl;
int ret = tcp.Send(pMsg->s, pMsg->strBuff);
event.data.fd = pMsg->s;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epollftd, EPOLL_CTL_MOD, pMsg->s, &event);
}
else
{
cout << "其它的处理" << endl;
}
}
}
return 0;
}
epollserver.cpp
不在是从内核态到用户态的拷贝,而是两态共享这块区域
当有数据到来时,重新排列位置,有数据的放前面
应用:
- redis
- nginx
- Java nio