select对所有连接进行遍历,取出有事件产生的连接描述符,判断每个描述符是否在读或写的描述符集合中。
在调用select函数之前需要将描述符集合进行备份,因为select函数内部会改变描述符集合的内容。
fd_set通过位图来标识连接描述符,bitmap的大小由FD_SETSIZE宏来设置,默认1024个。
#include <serversocket.h>
#include <clientsocket.h>
#include <sys/select.h>
#include <iostream>
using namespace std;
int main(){
ServerSocket ssocket;
if(ssocket.fd() < 0){
ssocket.close();
exit(1);
}
char host[] = "127.0.0.1";
int port = 9000;
if(!ssocket.bind(host, port)){
ssocket.close();
exit(1);
}
if(!ssocket.listen(128)){
ssocket.close();
exit(1);
}
fd_set readfds;
fd_set writefds;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_SET(ssocket.fd(), &readfds);
int maxfd = ssocket.fd();
while(true){
struct timeval timeout;
timeout.tv_sec = 10;
timeout.tv_usec = 0;
fd_set tmpfds = readfds; //复制描述符集合
fd_set tmpfds1 = writefds;
int infds = ::select(maxfd + 1, &tmpfds, &tmpfds1, NULL, &timeout);
if(infds < 0){
perror("select error:");
break;
}
else if(infds == 0){
cout << "select timeout" << endl;
}
for(int eventfd = 0; eventfd <= maxfd; eventfd++){
if(FD_ISSET(eventfd, &tmpfds) == 0){
continue;
}
if(eventfd == ssocket.fd()){
int cfd = ssocket.accept();
if(cfd < 0){
continue;
}
FD_SET(cfd, &readfds);
FD_SET(cfd, &writefds);
if(maxfd < cfd) maxfd = cfd;
}
else{
ClientSocket csocket(eventfd);
char readbuf[1024];
memset(readbuf, 0, sizeof(readbuf));
if(csocket.recv(readbuf, sizeof(readbuf)) <= 0){
csocket.close();
FD_CLR(eventfd, &readfds);
FD_CLR(eventfd, &writefds);
if(eventfd == maxfd){
for(int ii = maxfd; ii > 0; ii--){
if(FD_ISSET(ii, &readfds)){
maxfd = ii;
break;
}
}
}
}
else{
cout << "Recieved: " << readbuf << endl;
}
}
}
for(int eventfd = 0; eventfd <= maxfd; eventfd++){
if(FD_ISSET(eventfd, &tmpfds1) == 0){
continue;
}
ClientSocket csocket(eventfd);
char sendbuf[] = "This is message from server";
if(csocket.send(sendbuf, sizeof(sendbuf)) <= 0){
csocket.close();
FD_CLR(eventfd, &readfds);
FD_CLR(eventfd, &writefds);
if(eventfd == maxfd){
for(int ii = maxfd; ii > 0; ii--){
if(FD_ISSET(ii, &writefds)){
maxfd = ii;
break;
}
}
}
}
}
}
ssocket.close();
return 0;
}
poll模型:使用结构体pollfd数组存放需要监听的socket。
读事件设置为POLLIN,写事件为POLLOUT。
将所有fd初始化位-1,poll自动忽略为-1的描述符。
在程序中poll的数据结构是数组,拷贝进内核后变成列表,依旧采用遍历的方法。
poll的事件:
POLLIN、POLLOUT、POLLOUT
POLLRDNORM 数据可读 POLLRDBAND 优先级带数据可读 POLLPRI 高优先级可读数据
POLLWRNORM 数据可写 POLLWRBAND 优先级带数据可写 POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件
#include <serversocket.h>
#include <clientsocket.h>
#include <poll.h>
#include <iostream>
using namespace std;
int main(){
ServerSocket ssocket;
if(ssocket.fd() < 0){
ssocket.close();
exit(1);
}
char host[] = "127.0.0.1";
int port = 9000;
if(!ssocket.bind(host, port)){
ssocket.close();
exit(1);
}
if(!ssocket.listen(128)){
ssocket.close();
exit(1);
}
pollfd fds[1024];
for(int ii=0; ii<1024; ii++){
fds[ii].fd = -1;
}
fds[ssocket.fd()].fd = ssocket.fd();
fds[ssocket.fd()].events = POLLIN;
int maxfd = ssocket.fd();
while(true){
int infds = ::poll(fds, maxfd + 1, 10000);
if(infds < 0){
perror("poll error:");
break;
}
else if(infds == 0){
cout << "poll timeout" << endl;
continue;
}
for(int eventfd=0; eventfd<=maxfd; eventfd++){
if(fds[eventfd].fd < 0) continue;
if((fds[eventfd].revents & POLLIN)==0) continue;
if(eventfd == ssocket.fd()){
int cfd = ssocket.accept();
if(cfd < 0){
continue;
}
fds[cfd].fd = cfd;
fds[cfd].events = POLLIN;
if(maxfd < cfd) maxfd = cfd;
}
else{
char recvbuf[1024];
memset(recvbuf, 0, sizeof(recvbuf));
ClientSocket csocket(eventfd);
if(csocket.recv(recvbuf, sizeof(recvbuf)) <= 0){
csocket.close();
fds[eventfd].fd = -1;
if(eventfd == maxfd){
for(int ii = maxfd; ii > 0; ii--){
if(fds[ii].fd != -1){
maxfd = ii;
break;
}
}
}
}
else{
cout << recvbuf << endl;
char sendbuf[] = "Server recieved your message!";
csocket.send(sendbuf, sizeof(sendbuf));
}
}
}
}
ssocket.close();
return 0;
}
epoll:仅将发生事件的描述符从内核复制给用户。
epoll使用一颗红黑树存储要监听的文件描述符,只需要将文件描述符从用户态拷贝到内核态一次。没有最大文件描述符数量的限制。
发生变化的文件描述符作为数组返回。
epoll边沿触发ET和水平触发LT:默认为LT
LT模式:
读事件:若接收缓冲区中有数据、没读完,下次调用epoll_wait时立即触发读事件
写事件:若写缓冲区未满,下次调用epoll_wait时立即触发写事件
ET模式:
读事件:epoll_wait触发读事件后,不管程序有没有处理,都不会再触发,直到有新的数据到达
写事件:epoll_wait触发写事件后,若发送缓冲区没有满,不会触发写事件,只有当发送缓冲区由满变为不满时才再次触发
EPOLL_CTL_ADD EPOLL_CTL_DEL EPOLL_CTL_MOD //修改
#include <serversocket.h>
#include <clientsocket.h>
#include <sys/epoll.h>
#include <iostream>
using namespace std;
int main(){
ServerSocket ssocket;
if(ssocket.fd() < 0){
ssocket.close();
exit(1);
}
char host[] = "127.0.0.1";
int port = 9000;
if(!ssocket.bind(host, port)){
ssocket.close();
exit(1);
}
if(!ssocket.listen(128)){
ssocket.close();
exit(1);
}
int epollfd = epoll_create(1);
epoll_event ev;
ev.data.fd = ssocket.fd();
ev.events = EPOLLIN;
epoll_ctl(epollfd, EPOLL_CTL_ADD, ssocket.fd(), &ev);
epoll_event evs[10];
while(true){
int infds = ::epoll_wait(epollfd, evs, 10, -1);
if(infds < 0){
perror("epoll error:");
break;
}
else if(infds == 0){
cout << "epoll timeout" << endl;
continue;
}
for(int ii = 0; ii < infds; ii++){
if(evs[ii].data.fd == ssocket.fd()){
int cfd = ssocket.accept();
if(cfd < 0){
continue;
}
ev.data.fd = cfd;
ev.events = EPOLLIN;
epoll_ctl(epollfd, EPOLL_CTL_ADD, cfd, &ev);
}
else{
ClientSocket csocket(evs[ii].data.fd);
char recvbuf[1024];
if(csocket.recv(recvbuf, sizeof(recvbuf)) <= 0){
csocket.close();
epoll_ctl(epollfd, EPOLL_CTL_DEL, csocket.fd(), NULL);
}
else{
cout << recvbuf << endl;
char sendbuf[] = "Server recieved your message!";
//因为是非阻塞io,这里需要处理发送缓冲区满的情况
csocket.send(sendbuf, sizeof(sendbuf));
}
}
}
}
ssocket.close();
return 0;
}