帮助文档
专业提供香港服务器、香港云服务器、香港高防服务器租用、香港云主机、台湾服务器、美国服务器、美国云服务器vps租用、韩国高防服务器租用、新加坡服务器、日本服务器租用 一站式全球网络解决方案提供商!专业运营维护IDC数据中心,提供高质量的服务器托管,服务器机房租用,服务器机柜租用,IDC机房机柜租用等服务,稳定、安全、高性能的云端计算服务,实时满足您的多样性业务需求。 香港大带宽稳定可靠,高级工程师提供基于服务器硬件、操作系统、网络、应用环境、安全的免费技术支持。
服务器资讯 / 香港服务器租用 / 香港VPS租用 / 香港云服务器 / 美国服务器租用 / 台湾服务器租用 / 日本服务器租用 / 官方公告 / 帮助文档
高并发服务器 poll模型 非阻塞 代码已跑
发布时间:2024-03-07 21:58:10   分类:帮助文档
高并发服务器 poll模型 非阻塞 代码已跑 经过十几次修改终于发现了问题! 现在代码完全可以运行了!排除BUG太麻烦了 之前错误现象 开了N个客户端,排查netstat -a | grep 8888 全部establilsed了,网络连接正常!但是仅仅有一个客户端正常反应,别的客户端仿佛被堵住了!只有那个正常的客户端发一句,好像才能把被堵住的字母流出来似的! 找到原因: 是在遍历数组所有元素的时候,只找到一个不是-1的fd 就开始read起来!!完全没判断它的revent 是不是POLLIN ,所以有一个客户端的read 就把别人的read全阻塞了 所以在 //为什么不从0开始遍历?0是你的监听描述符        for (i =1;i <1024;i++){ 这里面加了一句   if (fdarray[i].revents ==POLLIN){  但是!!!!神奇的是,异常现象仍然存在!!!  跟老师的代码已经完全一样了!怎么办!只能自己蒙着加了一个else . 也就是在if ( fdarray[0].fd 不等于 -1的情况下,再去判断 if ( revents == POLLIN )  也就是说!! revents == POLLIN的时候那个对应的fd还可能是-1 !!虽然不知道怎么搞的但是这下改动后代码可以跑了!! B站就业班视频代码搬运 对应课程 1.函数原型 int poll(struct pollfd *fds, nfds_t nfds, int timeout);  poll 模型和select模型的作用相似,用于I/O 多路复用 1.1 函数参数; 1.1.1    struct pollfd *   fds 是结构体pollfd 组成的 数组的首地址, struct pollfd { int fd; short events; short revents; }; (1)int fd :文件描述符。如果fd == -1 表明内核不再监控。 (2)short events :输入参数,让内核去监视的事件,就像是 select 中让内核去监视读事件,写事件,异常事件 (3)short revents :输出参数,表示告诉内核,有哪些事件发生了改变。 调用 poll 函数,当监视的文件描述符有事件发生,内核修改的是 revents,而不修改 events 和 fd,这样做events 和 fd 就可以重用;poll 返回的是 revents 。 1.1.2   int ndfs: 表示内核监控的范围,具体:数组最大下标  +1 也就是说,现在数组开头6个文件描述符,  0  , 100, 200, 0,300, 400,然后后面的文件描述符都是0 ,ndfs就是 5 ,因为最大的不是0的文件描述符400的下标是5 ,表示poll监视着6个的变化。所以这个参数的含义是:第一个参数中,让内核监控的文件描述符的个数 1.1.3 timeout 单位是毫秒:   =0  不阻塞,立刻返回  -1  表示一直阻塞,直到有事件发生,  > 0 表示阻塞时长,在时长范围内如果有事件发生会立刻返回,如果超时,也会立刻返回, POLLIN  表示监控读事件 POLLPRI 紧急事件去读 POLLOUT  可写事件 POLLERR  错误事件。但是仅仅返回在revents , 在events 中错误被忽略了。 读事件,客户端有连接过来,才能读, 而写事件基本上用不着监控(主动写发送的),除非写的太多把缓冲区写满了。。 1.2.   返回值 一个正数, 代表revents 不是0的 个数,表示的意思是有事件返回或者有错误返回的文件描述符的总数。 如果返回0,表示没有任何文件描述符有变化。 如果返回-1 ,表明有一个错误errno没有被正确设置。 2.使用pol l 模型实现服务器的流程 创建socket ,得到监听文件描述符 lisenfd;     --socket()设置端口复用  --setsockopt();绑定 --bind ()定义pollfd结构体的数组fdarray[1024] 老师设置了一个变量maxindex, 表示poll函数的第一个参数中,不为-1的 文件描述符的那个最大位置的下标,例如, 这里有8个文件描述符,  -1, 20,-1,-1,10, 1, -1,    3,  -1那么maxindex =7, 指向那个3  。 maxindex +1 就是poll函数的第二个参数,写进去。表示内核监控 这8个文件描述符。最后那个-1 不管( 数组里,都是下标从0 开始,最后一个 - 1  下标是 8) #include #include #include #include #include #include #include #include #include #include #include int main() { int sfd = socket(AF_INET, SOCK_STREAM, 0); //设置端口复用 int opt =1; setsockopt ( sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)); //定义一个服务器地址的 结构体,利用本机任意网络地址接口,端口为8888 struct sockaddr_in servad; bzero(&servad, sizeof(servad)); servad.sin_family =AF_INET; servad.sin_port =htons(8888); servad.sin_addr.s_addr = htonl (INADDR_ANY); int ret = bind(sfd, (struct sockaddr*)&servad, sizeof( servad)); //将socket从主动变为被动(服务器必备),监听来自客户的请求 listen(sfd,128); //定义pollfd 结构体,并建立一个1024长度的数组fdarray , struct pollfd fdarray[1024]; //把all文件描述符都变成-1 int i =0; for (i =0; i<1024; i++){ fdarray[i].fd = -1; } fdarray[0].fd = sfd; fdarray[0].events = POLLIN; //zhuy注意别写成revents了,现在监听呢,没有什么返回,没有revents int newfd ; int nready =0; int sockfd; int maxindex =0; while (1) { //因为超时时间设置为NULL ,所以进程会无限阻塞在这一步,除非有了变化才往下走 nready = poll (fdarray, maxindex+1, -1); //第二个参数每次有变化的描述符+1 ,第二个参数也跟着加1, 表明客户端连接来的 //越多,内核监控的描述符跟着增多 if (nready < 0) { if (errno == EINTR){ continue; } break; } //第一种情况,有客户端的新请求fdarray[0].返回事件等于可写事件 //但是我现在不懂为什么新连接请求是pollin .新链接=可写事件?? if (fdarray[0].revents == POLLIN) { newfd = accept (sfd,NULL, NULL); //寻找在fdarray中最靠近的不是-1的位置方下newfd for (i =1;i<1024;i++){ if( fdarray[i].fd ==-1){ fdarray[i].fd = newfd; fdarray[i].events = POLLIN; break; } } if (i==1024){ close (newfd); continue; } if (maxindex #include #include #include #include #include #include #include int main() { int sfd2 = socket(AF_INET, SOCK_STREAM, 0); if (sfd2 <0){ printf("socket fun error/n"); return -1; } //定义一个服务器地址的结构体。 struct sockaddr_in seradd; //清空 memory bzero(&seradd, sizeof(seradd)); seradd.sin_family =AF_INET; seradd.sin_port = htons(8888); //int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen); //参数1:目的服务器的socket描述符; //参数2:服务器端的IP地址和端口号的地址结构体指针; //但是,服务器的IP 现在是一个字符串,我们必须用一个函数,把它转化为网络大端格式, //然后,存储到seradd的sin_addr的s_addr中, int s = inet_pton( AF_INET,"127.0.0.1",&seradd.sin_addr.s_addr); if (s <= 0) { if (s == 0) fprintf(stderr, "Not in presentation format"); else perror("inet_pton"); exit(EXIT_FAILURE); } int ret333 = connect(sfd2,(struct sockaddr* )&seradd,sizeof(seradd)); if (ret333<0){ printf("connect error\n"); return -1; } int n1 =0, n2=0; char buf2[256]; while (1 ){ memset(buf2,0x00,sizeof(buf2)); //从电脑键盘输入数据 printf("这里是客户端type in a word\n"); n1 =read (STDIN_FILENO,buf2,sizeof(buf2)); //发送数据给服务器 write(sfd2,buf2,n1); //读取服务器返回的数据 memset(buf2,0x00,sizeof(buf2)); n2 = read (sfd2,buf2,sizeof(buf2)); printf("这里是客户端,接受大写字母为%s\n", buf2); if (n2<=0){ printf("这里是客户端 server已关闭,或者读到的字符为0\n"); break; } } close(sfd2); return 0; } 3. 自带手册里的代码 #include #include #include #include #include #include #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ } while (0) int main(int argc, char *argv[]){ int nfds, num_open_fds; struct pollfd *pfds; if (argc < 2) { fprintf(stderr, "Usage: %s file...\n", argv[0]); exit(EXIT_FAILURE); } num_open_fds = nfds = argc - 1; pfds = calloc(nfds, sizeof(struct pollfd)); if (pfds == NULL){ errExit("malloc"); } /* Open each file on command line, and add it 'pfds' array */ for (int j = 0; j < nfds; j++) { pfds[j].fd = open(argv[j + 1], O_RDONLY); if (pfds[j].fd == -1){ errExit("open"); } printf("Opened \"%s\" on fd %d\n", argv[j + 1], pfds[j].fd); pfds[j].events = POLLIN; } /* Keep calling poll() as long as at least one file descriptor is open */ while (num_open_fds > 0) { int ready; printf("About to poll()\n"); ready = poll(pfds, nfds, -1); if (ready == -1){ errExit("poll"); } printf("Ready: %d\n", ready); /* Deal with array returned by poll() */ for (int j = 0; j < nfds; j++) { char buf[10]; if (pfds[j].revents != 0) { printf(" fd=%d; events: %s%s%s\n", pfds[j].fd, (pfds[j].revents & POLLIN) ? "POLLIN " : "", (pfds[j].revents & POLLHUP) ? "POLLHUP " : "", (pfds[j].revents & POLLERR) ? "POLLERR " : ""); } if (pfds[j].revents & POLLIN) { ssize_t s = read(pfds[j].fd, buf, sizeof(buf)); if (s == -1) errExit("read"); printf(" read %zd bytes: %.*s\n",s, (int) s, buf); } else { /* POLLERR | POLLHUP */ printf(" closing fd %d\n", pfds[j].fd); if (close(pfds[j].fd) == -1) errExit("close"); num_open_fds--; } } } printf("All file descriptors closed; bye\n"); exit(EXIT_SUCCESS); } /* Suppose we run the program in one terminal, asking it to open a FIFO:            $ mkfifo myfifo            $ ./poll_input myfifo        In a second terminal window, we then open the FIFO for writing, write some data to  it,  and  close  the        FIFO:            $ echo aaaaabbbbbccccc > myfifo        In the terminal where we are running the program, we would then see: */ 这段代码首先定义了 2个整数 nfds  和 num_open_fds ,让它们等于你写的参数的个数,。也就是说它默认你必须在命令行写出   ./脚本名   文件名  (文件名可以不止一个),你写几个文件,这篇代码就打开几个文件,然后把open函数返回的 fd 文件描述符,全部纳入内核的监控,然后让poll 函数管理。        然后当这些描述符有变化时,先判断事件类型  (pfds[j].revents & POLLIN)  ? "POLLIN "  : "", 这行的意思是,返回结果revents和POLLIN这个宏,做“ 按位与” 运算,如果结果是POLLIN ,说明revents 就是POLLIN, 那么这行字符串结果就是 'POLLIN'   不然这个字符串就是 “” ,然后当revents 就是POLLIN时,开始读取,你刚才在另一终端写的东西都会体现在当前终端的屏幕上。