朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型
来源:互联网 发布:linux怎么看cpu占用 编辑:程序博客网 时间:2024/05/19 23:57
和朴素模型一样,我们首先要创建一个监听socket,然后调用listen去监听服务器端口。不同的是,我们要对make_socket方法传递1,因为我们要创建一个异步的socket。
- listen_sock = make_socket(1);
- if (listen(listen_sock, SOMAXCONN) < 0) {
- perror("listen error");
- exit(EXIT_FAILURE);
- }
- FD_ZERO(&active_fd_set);
- FD_SET(listen_sock, &active_fd_set);
- fd_set active_fd_set, read_fd_set;
- /* Initialize the set of active sockets. */
- while (1) {
- timeout.tv_sec = 0;
- timeout.tv_usec = 500;
- /* Service all the sockets with input pending. */
- read_fd_set = active_fd_set;
- switch(select(FD_SETSIZE, &read_fd_set, NULL, NULL, &timeout)) {
- case -1 : {
- perror("select error\n");
- exit(EXIT_FAILURE);
- }break;
- case 0 : {
- //perror("select timeout\n");
- }break;
- default: {
- default: {
- for (index = 0; index < FD_SETSIZE; ++index) {
- if (FD_ISSET(index, &read_fd_set)) {
- if (listen_sock == index) {
- /* Connection request on original socket. */
- int new_sock;
- new_sock = accept(listen_sock, NULL, NULL);
- if (new_sock < 0) {
- perror("accept error");
- exit(EXIT_FAILURE);
- }
- request_add(1);
- //set_block_filedes_timeout(new_sock);
- FD_SET(new_sock, &active_fd_set);
- } else {
- if (0 == server_read(index)) {
- server_write(index);
- }
- close(index);
- FD_CLR(index, &active_fd_set);
- }
- }
- }
- }
- }
- }
- return 0;
我们使用一个for循环遍历每个socket。如果该socket通过FD_ISSET宏判断不处于我们关注的可读事件fd_set中,则忽略它。
如果处在可读fd_set中,则看看其是否是监听socket。
如果是监听socket,则使用accpet方法获取接入的socket。并使用request_add让请求数量加一。还要使用FD_SET宏将该socket加入到活动状态的fd_set中。之后该活动状态的fd_set将被赋值给需要关注可读事件的fd_set中。
如果不是监听socket,则是接入的socket。于是我们调用《朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型》一文中介绍的server_read和server_write方法读取内容并回包。最后我们还要关闭socket,并使用FD_CLR宏将该socket从活动状态的fd_set中去掉。之后的select函数将不会在关注该socket了。
整个过程非常简单。但是这其中却包含了很多值得思考的问题。
首先我抛出一个问题,我在default中使用了一个从0到FD_SETSIZE的遍历行为。并且将遍历的游标——index作为socket去操作——使用server_read和server_write去读取。于是问题就来了,使用make_socket创建的socket值和使用accept接收到的socket的值怎么和游标产生关联?代码中似乎没有任何让它们产生关联的逻辑,而且它们的关系是严格的“相等”的关系!那么只有一个假设,就是make_socket和accept返回的socket值在FD_SETSIZE和0之间。但是目前我没有找到文档对这个问题进行说明,而我也没深入研究这两个函数考证到其值就是在这个范围之内,那么为什么我还要这么去用呢?
我们先记下这个问题,深入到linux的源码中取解释这个使用的正确性。
我们先看下fd_set的定义
- /* The fd_set member is required to be an array of longs. */
- typedef long int __fd_mask;
- /* Some versions of <linux/posix_types.h> define this macros. */
- #undef __NFDBITS
- /* It's easier to assume 8-bit bytes than to get CHAR_BIT. */
- #define __NFDBITS (8 * (int) sizeof (__fd_mask))
- #define __FD_ELT(d) ((d) / __NFDBITS)
- #define __FD_MASK(d) ((__fd_mask) 1 << ((d) % __NFDBITS))
- /* fd_set for select and pselect. */
- typedef struct
- {
- /* XPG4.2 requires this member name. Otherwise avoid the name
- from the global namespace. */
- #ifdef __USE_XOPEN
- __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
- # define __FDS_BITS(set) ((set)->fds_bits)
- #else
- __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
- # define __FDS_BITS(set) ((set)->__fds_bits)
- #endif
- } fd_set;
- /* Maximum number of file descriptors in `fd_set'. */
- #define FD_SETSIZE __FD_SETSIZE
以上我是在我ubuntu系统的/usr/include/x86_64-linux-gnu/sys/select.h文件中找到的定义。我们看到fd_set的主体就是一个long int型数组__fds_bits。该数组的个数是两个数的商。被除数__FD_SETSIZE就是我们程序中使用的FD_SETSIZE,也就是1024。除数__NFDBITS是64。于是fd_set中数组元素的个数是1024/64=16。注意一下这个值是16,而我们程序中关注的socket的最大个数是FD_SETSIZE——1024,这是为什么?其实这就是该结构设计的一个精妙之处。fd_set的__fds_bits是一个16个元素的long int型数组,其总长度就是16*64=1024位。于是可以使用每一位表示一个socket。
我们到/usr/include/x86_64-linux-gnu/bits/select.h 文件中看看linux是如何让socket和这个空间中每一位进行对应的。我们查看FD_SET宏
- #define __FD_SET(d, set) \
- ((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d)))
有了上面的认识,我们就知道select模型最大只能支持FD_SETSIZE个数的socket,而且socket的值也只能在FD_SETSIZE之内。如果socket()或accept()函数返回的socket值大于FD_SETSIZE,则select模型将出现错误——上面的计算将溢出。基于这种反向推理,我们可以放心大胆的使用0到FD_SETSIZE的值去当socket的值去计算。我看网上有很多select例子需要使用一个数组去维护接入的socket,如果在不考虑效率的前提下是不必要的。但是如果你追求极致的Select模型性能,还是建议使用一个数组去维护Socket,这样不至于出现大量的浪费操作。这块分析我们将在后文中给出。
这儿再多言一句,正是因为这种位操作,我们才需要在使用fd_set之前调用FD_ZERO去清空所有空间
- # if __WORDSIZE == 64
- # define __FD_ZERO_STOS "stosq"
- # else
- # define __FD_ZERO_STOS "stosl"
- # endif
- # define __FD_ZERO(fdsp) \
- do { \
- int __d0, __d1; \
- __asm__ __volatile__ ("cld; rep; " __FD_ZERO_STOS \
- : "=c" (__d0), "=D" (__d1) \
- : "a" (0), "0" (sizeof (fd_set) \
- / sizeof (__fd_mask)), \
- "1" (&__FDS_BITS (fdsp)[0]) \
- : "memory"); \
- } while (0)
- #define __FD_CLR(d, set) \
- ((void) (__FDS_BITS (set)[__FD_ELT (d)] &= ~__FD_MASK (d)))
再看下客户端的输出
可见当前环境下,select模型的处理能力大概是每秒7000多连接。(和下一章介绍的Poll模型差距不大,而且如果使用数组维护Socket还可以提高性能)
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll模型
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll模型
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Epoll模型
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Epoll模型
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——模型比较
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——模型比较
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll、Epoll模型处理长连接性能比较
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll、Epoll模型处理长连接性能比较
- linux网络编程 select,poll,epoll模型
- python实现select和epoll模型socket网络编程
- IO模型和Select/Poll/Epoll解析
- select和epoll网络模型
- 网络编程中select模型和poll模型学习(linux)
- select、poll、epoll 网络模型比较
- 常用的外部排序方法
- 小程序-简易搭建步骤
- spring的启动过程04.2-AnnotationAwareAspectJAutoProxyCreator处理器
- 括号配对问题
- tomcat io 与 nio性能比较
- 朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型
- 命令行方式编译C++代码(Windows)
- 一些实用时间JS代码
- 关于url传参乱码的解决
- MySQL 5.6.17 版本发布及下载地址(mysql-5.6.17-winx64.zip)
- MapReduce: Simplified Data Processing on Large Clusters
- HDU1028:Ignatius and the Princess III(dp入门 & 母函数)
- React-Native版本升级的实践方案
- 编程珠玑: 13章 搜索 13.2使用线性结构,生成[0 ,maxval]范围内m各随机整数的有序序列 -------解题总结