poll2
数据包 粘包 一个包 两次read
Read :可能并没有把confd 对应的缓冲区的数据读完,那么connfd仍然是活跃的,
我们应该将读到的数据保存在connfd的应用层的缓冲区(每一次都进行追加)。如何解析协议,我们让应用层的解析协议自己来解析
忙等待 :
假设客户端关注了socket的POLLOUT事件,而此时内核缓冲区有空闲,但是应用层却没数据可写,那么内核将会处于忙等待状态(busy waitting loop),一直发送POLLOUT事件。
解决的方法是:我们要看应用层的缓冲区,如果应用层的缓冲区有数据发,那么我们应该关注POLLOUT事件,要不然就取消POLLOUT事件的关注。
对于客户端在读数据时,我们也应该采用相应的方法,如果应用层的空间空闲时,我们就关注POLLIN事件,要不然就取消POLLIN事件。
请求量大并发时
准备一个空闲的文件描述符(备胎)。遇到这种情况,先关闭这个空闲文件,获得一个文件描述符名额;再accept(2)拿到socket连接的文件描述符;随后立刻close(2),这样就优雅地断开了与客户端的连接;最后重新打开空闲文件,把“坑”填上,以备再次出现这种情况时使用。
code
#include <unistd.h> #include <sys/types.h> #include <fcntl.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <poll.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <vector> #include <iostream> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) typedef std::vector<struct pollfd> PollFdList; int main(void) { signal(SIGPIPE, SIG_IGN); signal(SIGCHLD, SIG_IGN); //备胎fd int idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC); int listenfd; //if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) if ((listenfd = socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP)) < 0) ERR_EXIT("socket"); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); int on = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) ERR_EXIT("setsockopt"); if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("bind"); if (listen(listenfd, SOMAXCONN) < 0) ERR_EXIT("listen"); struct pollfd pfd; pfd.fd = listenfd; pfd.events = POLLIN; PollFdList pollfds; pollfds.push_back(pfd); int nready; struct sockaddr_in peeraddr; socklen_t peerlen; int connfd; while (1) { nready = poll(&*pollfds.begin(), pollfds.size(), -1); if (nready == -1) { if (errno == EINTR) continue; ERR_EXIT("poll"); } if (nready == 0) // nothing happended continue; if (pollfds[0].revents & POLLIN) { peerlen = sizeof(peeraddr); connfd = accept4(listenfd, (struct sockaddr*)&peeraddr, &peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC); /* if (connfd == -1) ERR_EXIT("accept4"); */ // 这里备胎fd起到作用了 if (connfd == -1) { if (errno == EMFILE) { close(idlefd); idlefd = accept(listenfd, NULL, NULL); close(idlefd); idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC); continue; } else ERR_EXIT("accept4"); } pfd.fd = connfd; pfd.events = POLLIN; pfd.revents = 0; pollfds.push_back(pfd); --nready; // 连接成功 std::cout<<"ip="<<inet_ntoa(peeraddr.sin_addr)<< " port="<<ntohs(peeraddr.sin_port)<<std::endl; if (nready == 0) continue; } //std::cout<<pollfds.size()<<std::endl; //std::cout<<nready<<std::endl; for (PollFdList::iterator it=pollfds.begin()+1; it != pollfds.end() && nready >0; ++it) { if (it->revents & POLLIN) { --nready; connfd = it->fd; char buf[1024] = {0}; int ret = read(connfd, buf, 1024); if (ret == -1) ERR_EXIT("read"); if (ret == 0) { std::cout<<"client close"<<std::endl; it = pollfds.erase(it); --it; close(connfd); continue; } std::cout<<buf; //这里要根据业务看需不需要使用POLLOUT事件 write(connfd, buf, strlen(buf)); } } } return 0; }
补充:综合编程 , 其他综合 ,