网络编程——select模型(总结)

栏目: 服务器 · 发布时间: 5年前

内容简介:答:解决基本C/S模型中,其实select模型解决了两个模型都存在recv(),send()执行阻塞问题

为什么要使用select模型?

答:解决基本C/S模型中, accept()recv()send() 阻塞的问题

select模型与C/S模型的不同点

  • C/S模型中accept()会阻塞一直傻等socket来链接
  • select模型只解决accept()傻等的问题,不解决recv(),send()执行阻塞问题

其实select模型解决了 实现多个客户端链接 ,与多个客户端分别通信

两个模型都存在recv(),send()执行阻塞问题

  • 由于服务器端,客户端不需要(客户端只有一个socket,可以通过加线程解决同时recv和send)

select模型逻辑

  1. 将所有的socket(服务器端+客户端)装进一个数组中
  2. 通过select()遍历socket数组
  3. 取出有相应的socket放进另一个数组(都是有响应的socket)
  4. 对装有响应的socket数组集中处理
  5. 服务器socket响应:客户端链接,调用accept()
  6. 客户端socket响应:客户端通信,调用send()或recv()
graph TD
A(装有所有的socket数组)==>|遍历数组|B(有响应的socket)
B(有响应的socket)==>C[服务器端socket]
B(有响应的socket)==>D[客户端socket]
C[服务器端socket]==>E{"accept()"}
D[客户端socket]==>F{"recv()或send()"}

select()

fd_set

作用:定义一个用来装socket的 结构体

#ifndef FD_SETSIZE
#define FD_SETSIZE      64   /*默认64个*/
#endif /* FD_SETSIZE */

typedef struct fd_set {
        u_int fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

默认装socket大小为64,可以通过在winsock2.h头文件前声明宏,给一个更大的值

#define FD_SETSIZE 128
#include <WinSock2.h>

因为原理就是不停遍历检测,越多效率越低,延迟越大,所以合适大小最好。

select模型应用,就是 小用户量访问

四个操作fd_set的操作宏

操作宏 作用 代码
FD_ZERO 将客户端socket集合清零 FD_ZERO(&clientSockets);
FD_SET 添加一个socket(超过默认值大小不再处理) FD_SET(socketListen,&clientSockets);
FD_CLR 从集合中删除指定的socket, 一定要close,手动释放 FD_CLR(socketListen, &clientSockets);closesocket(socketListen);
FD_ISSET 查询socket是否在集合中, 不存在返回0,存在返回非0 int a = FD_ISSET(socketListen, &clientSockets);

select()函数

作用:监视socket集合,如果某个socket发生响应(链接或者收发数据),通过返回值以及参数告诉我们哪个socket有响应

int WSAAPI select(
  int           nfds,   /*填0*/
  fd_set        *readfds, /*检查是否有可读的socket*/
  fd_set        *writefds, /*检查是否有可写的socket*/
  fd_set        *exceptfds, /*检查socket上的异常错误*/
  const timeval *timeout
);

参数1: 忽略填0

为了兼容Berkeley sockets

参数2:指向一组socket数组,用来保存有响应的数组

  • 只针对recv()、accept()
  • 这个用来保存有响应的数组 初始化为包含所有的socket ,通过select函数投放给系统,系统遍历数组后,只将有响应的socket再赋值回来,调用后,这个参数 只剩下有请求的socket

参数3:指向一组socket数组,用来保存可发送的数组

  • 可以给哪些客户端socket发送消息,针对send
  • 这个用来保存可发送的数组 初始化为包含所有的socket ,通过select函数投放给系统,系统遍历数组后,只将可发送的socket再赋值回来,调用后,这个参数 只剩下可发送的socket

参数4:检查socket上的异常错误

用法和参数2、3一样, 将有异常错误的socket装进来,反馈给我们

/*得到异常socket上的具体错误码*/
getsockopt(socket, SOL_SOCKET, SO_ERROR, buf, buflen);

如果调用这个函数(针对这个getsockopt函数)没有错误,返回0,否则返回SOCKET_ERROR,并且可以调用WSAGetLastError来得到错误代码。

参数5:最大等待时间

一个结构体

struct timeval {
        long    tv_sec;         /* seconds */
        long    tv_usec;        /* and microseconds */
};

当客户端没有响应时,select可以选择等一段时间,不等,等到有socket响应,三种方式

tv_sec tv_usec 作用
0 0 不等待,立刻返回
3 4 等待3秒4微秒没有消息再返回

NULL:死等,直到有socket响应

返回值

  • 0 :在等待时间没有客户端socket响应,continue进行下一次等待
  • >0 :有客户端socket响应‘
  • SOCKET_ERROR :发送错误

select模型代码

fd_set allsockets;
    //清零
    FD_ZERO(&allSockets);
    //服务器装进去
    FD_SET(socketServer, &allSockets);
    while (1)
    {
        fd_set readSockets = allSockets;
        fd_set writeSockets = allSockets;
        fd_set errorSockets = allSockets;

        //时间段
        struct timeval st;
        st.tv_sec = 3;
         st.tv_usec = 0;

        //select
        int nRes = select(0, &readSockets, &writeSockets, &errorSockets, &st);
        if (0 == nRes) //没有响应的socket
        {
            continue;
        }
        else if (nRes > 0)
        {
            //处理错误
            for (u_int i = 0; i < errorSockets.fd_count; i++)
            {
                char str[100] = { 0 };
                int len = 99;
                if (SOCKET_ERROR == getsockopt(errorSockets.fd_array[i], SOL_SOCKET, SO_ERROR, str, &len))
                {
                    printf("无法得到错误信息\n");
                }
                printf("%s\n", str);    
            }

            for (u_int i = 0; i < writeSockets.fd_count; i++)
            {
                //printf("服务器%d,%d:可写\n", socketServer, writeSockets.fd_array[i]);
                if (SOCKET_ERROR == send(writeSockets.fd_array[i], "ok", 2, 0))
                {
                    int a = WSAGetLastError();
                }
            }

            //有响应
            for (u_int i = 0; i < readSockets.fd_count; i++)
            {
                if (readSockets.fd_array[i] == socketServer)
                {
                    //accept
                    SOCKET socketClient = accept(socketServer, NULL, NULL);
                    if (INVALID_SOCKET == socketClient)
                    {
                        //链接出错
                        continue;
                    }
                    
                    FD_SET(socketClient, &allSockets);
                    //send
                }
                else
                {
                    char strBuf[1500] = { 0 };
                    //客户端吧
                    int nRecv = recv(readSockets.fd_array[i], strBuf, 1500, 0);
                    //send
                    if (0 == nRecv)
                    {
                        //客户端下线了
                        //从集合中拿掉
                        SOCKET socketTemp = readSockets.fd_array[i];
                        FD_CLR(readSockets.fd_array[i], &allSockets);
                        //释放
                        closesocket(socketTemp);
                    }
                    else if (0 < nRecv)
                    {
                        //接收到了消息
                        printf(strBuf);
                    }
                    else //SOCK_ERROR
                    {
                        //强制下线也叫出错 10054
                        int a = WSAGetLastError();
                        switch (a)
                        {
                        case 10054:
                            {
                                SOCKET socketTemp = readSockets.fd_array[i];
                                FD_CLR(readSockets.fd_array[i], &allSockets);
                                //释放
                                closesocket(socketTemp);
                            }    
                        }
                    }    
                }
            }
        }

网络编程——select模型(总结)

总结

select模型

将一组socket数组投递给系统,然后在系统里去查询socket是否有信号,过程都是在select函数里面去进行的,再到返回有操作的socket集合

select()函数本质

select()函数执行遍历和返回有响应的socket,整个过程中也是阻塞的。

等待时间 阻塞
不等待 执行阻塞
半等待 执行阻塞+软阻塞
全等待 执行阻塞+硬阻塞

与CS模型对比

使用CS模型时,当链接了一个客户端,执行完了recv,while循环又回到了accept(),傻等着客户端来链接,无法多客户端链接通信。

使用select模型时,是select在遍历着socket数组,有响应的socket再取出来,没有就一直遍历,虽然select()函数的执行也是阻塞的。可以理解为,每次都是在处理只有响应的socket,所以可以进行多客户端链接通信。

当第一个客户端socket来链接时,select()函数将服务端socket从allsocket取出来,将新建的含有客户端socket添加到allsockets数组中,接着又在遍历allsocket,查看着时候有响应,所以不会像CS模型那种,在accept()函数阻塞着,傻等着。

select()函数主要解决的是accept()函数阻塞问题,而没有解决recv()和send()函数阻塞问题


以上所述就是小编给大家介绍的《网络编程——select模型(总结)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Node.js开发指南

Node.js开发指南

郭家寶(BYVoid) / 人民邮电出版社 / 2012-7 / 45.00元

Node.js是一种方兴未艾的新技术,诞生于2009年。经过两年的快速变化,Node.js生态圈已经逐渐走向稳定。Node.js采用了以往类似语言和框架中非常罕见的技术,总结为关键词就是:非阻塞式控制流、异步I/O、单线程消息循环。不少开发者在入门时总要经历一个痛苦的思维转变过程,给学习带来巨大的障碍。 而本书的目的就是帮助读者扫清这些障碍,学会使用Node.js进行Web后端开发,同时掌握事件驱......一起来看看 《Node.js开发指南》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具