侧边栏壁纸
博主头像
半生瓜のblog

THIS IS NO END.

  • 累计撰写 278 篇文章
  • 累计创建 3 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

【网络编程】select模型

xuanxuan
2021-08-28 / 0 评论 / 0 点赞 / 17 阅读 / 0 字 / 正在检测是否收录...
温馨提示:
本文最后更新于2024-02-14,若内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

我的小站——半生瓜のblog


@TOC

select模型

特点

  1. 解决基本c/s模型中,accept,rcev傻等的问题。

    • 傻等阻塞
    • 执行阻塞 send recv accept 在执行的复制粘贴的过程中都是阻塞的。

    (网络模型就是解决阻塞问题的)

  2. 实现多个客户端链接,与多个客户端分别通信。

  3. 用于服务器,因为客户端就一个socket。

服务器端

网络头文件 网络库
打开网络库
校验版本
创建socket
绑定地址与端口
开始监听

select    

逻辑

  1. 每个客户端都有socket,服务器也有自己的socket,将所有的socket装进一个数据结构里,即数组。
  2. 通过select函数,遍历1中的socket数组,当某个socket有相应,select就会通过其参数/返回值反馈出来。
  3. 处理。如果见得到的是服务器socket,那就有客户端链接,调用accept。如果检测到客户端socket,那就是客户端请求通信,调用send或者recv。

定义一个装客户端的socket结构体

fd_set

是网络库中定义好的类型。

typedef struct fd_set {
        //几个有效的
        u_int fd_count;               /* how many are SET? */
        //数组
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

默认FD_SERSIZE 是64,重新宏定义要写在网络库前。
尽量不要太大,大用户量应该用更高级的网络模型。
select模型应用就是小用户量访问,几十几百,简单方便。

    fd_set socketClient;

四个参数宏

FD_ZERO
    #define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)
    将定义好的集合清零

    FD_ZERO(&socketClient);
FD_SET 
    #define FD_SET(fd, set) do { \
    u_int __i; \
    for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \
        if (((fd_set FAR *)(set))->fd_array[__i] == (fd)) { \
            break; \
        } \
    } \
    if (__i == ((fd_set FAR *)(set))->fd_count) { \
        if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) { \
            ((fd_set FAR *)(set))->fd_array[__i] = (fd); \
            ((fd_set FAR *)(set))->fd_count++; \
        } \
    } \
} while(0, 0)
   向集合中添加socket  
FD_CLR
   #define FD_CLR(fd, set) do { \
    u_int __i; \
    for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count ; __i++) { \
        if (((fd_set FAR *)(set))->fd_array[__i] == fd) { \
            while (__i < ((fd_set FAR *)(set))->fd_count-1) { \
                ((fd_set FAR *)(set))->fd_array[__i] = \
                    ((fd_set FAR *)(set))->fd_array[__i+1]; \
                __i++; \
            } \
            ((fd_set FAR *)(set))->fd_count--; \
            break; \
        } \
    } \
} while(0, 0)
    从集合中删除某个元素,要手动释放,closesocket(socketServer)
    同链表删除。
FD_ISSET
    #define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set))
    判断集合中是否有某个元素
    有-返回非0
    没有-返回0

select

int WSAAPI select(
    int nfds,
    fd_set *readfds,
    fd_set *writefds,
    fd_set *exceptfds,
);

作用

监视socket集合,如果某个socket发生事件,(链接或者收发数据),通过返回值以及参数告诉我们。

参数1

Ignored忽略,填0,仅为了兼容(向下兼容性)Berkeley sockets。

参数2

检查是否有可读的scoket。(是否有消息recv/accept/)

即客户端发来消息了,该socket就会被设置。

初始化所有的socket,通过select投放给系统,系统将有事件发生的socket再复制回来,调用后,这个参数就只剩下有请求的socket。

返回有响应的socket。用个中间变量接收。

参数3

检查是否有可写的socket。

从头到尾遍历出来。

即,使可以给哪些客户端套接字发消息,即send,只要链接成功建立起来了,该客户端套接字就是可写的。

初始化所有的socket,通过select投放给系统,系统将可以写的socket在复制回来,调用后,这个参数就是装着可以被send数据的客户端socket。

参数4

检查套接字上的异常错误,用法同参数23。将所有的socket投放进去。

得到异常套接字上的具体错误码。

getsockopt(socket,SOL_SOCKET,SO_ERROR,buf,buflen);

参数5

最大等待时间,比如当客户端没有请求时,那么select函数可以等一会儿,一段时间过后,还没有,就继续执行select下面的语句,如果有了,就立刻执行下面的语句。

TIMEVAL
    tv_sec 秒
    tv_usec 微秒
    0 0非阻塞状态,立刻返回
    3 4那就再无客户端相应的情况下等待3秒4微秒
NULL
    select完全阻塞,知道客户端有反应,我才继续

返回值

0 客户端在等待时间内没有反应  处理——continue>0 有客户端请求交流了SOCKET_ERROR 发生了错误      得到错误码WSAGetLaseError()

流程总结

socket集合    socket判断有没有相应的      返回0,没有,继续挑      返回>0,有相应                 可读的accept                         recv                  可写的send                 异常的getsockopt       SOCK_ERROR

select是阻塞的。

不等待——执行阻塞

半等待——执行阻塞+软阻塞

全等待——执行阻塞+硬阻塞 死等

完整代码

(仅熟悉流程)

#define _WINSOCK_DEPRECATED_NO_WARNINGS 
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<WinSock2.h> 
#pragma comment(lib,"Ws2_32.lib")

//装所有的socket
fd_set allSocket;

BOOL WINAPI fun(DWORD dwCtrlType)
{
    switch (dwCtrlType)
    {

    case CTRL_CLOSE_EVENT:
        for (u_int i = 0; i < allSocket.fd_count; i++)
        {
            closesocket(allSocket.fd_array[i]);
        }
        WSACleanup();
    }
    return TRUE;
}

int main(void)
{
    //投递一个监视
    //关闭事件
    //控制台点叉退出
    SetConsoleCtrlHandler(fun, TRUE);   

    WORD wdVersion = MAKEWORD(2, 2);
    WSADATA wdSockMsg;
    int nRes = WSAStartup(wdVersion, &wdSockMsg);

    if (nRes != 0)
    {
        printf("网络库打开失败");
        return 0;
    }

    if (HIBYTE(wdSockMsg.wVersion) != 2 || LOBYTE(wdSockMsg.wVersion) != 2)
    {
        printf("版本不对");
        WSACleanup();
        return 0;
    }

    SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socketServer == INVALID_SOCKET)
    {
        printf("创建服务器socket失败");
        int a = WSAGetLastError();
        WSACleanup();
        return 0;
    }
    SOCKADDR_IN si;
    si.sin_family = AF_INET;
    si.sin_port = htons(12345);
    si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

    if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))
    {
        printf("绑定错误");
        int a = WSAGetLastError();
        closesocket(socketServer);
        WSACleanup();
        return 0;
    }

    if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
    {
        int a = WSAGetLastError();
        closesocket(socketServer);
        WSACleanup();
        return 0;
    }

    //清零
    FD_ZERO(&allSocket);
    //把服务器装进去
    FD_SET(socketServer, &allSocket);

    while (1)
    {
        //可读
        fd_set readSocket = allSocket;
        //可写
        fd_set writeSocket = allSocket;
        FD_CLR(socketServer, &writeSocket);
        fd_set errorSocket = allSocket;

        //时间段
        struct timeval st;
        st.tv_sec = 3;
        st.tv_usec = 0;
        //不用哪个哪个位置就写NULL
        int nRes = select(0, &readSocket, &writeSocket, &errorSocket, &st);
        if (nRes == 0)//没有响应的socket
        {
            continue;
        }
        else if (nRes > 0)
        {

            for (u_int i = 0; i < errorSocket.fd_count; i++)
            {
                char str[100] = { 0 };
                int len = 99;
                if (SOCKET_ERROR == getsockopt(errorSocket.fd_array[i], SOL_SOCKET, SO_ERROR,str,&len))
                {
                    printf("无法得到错误信息\n");
                }
                printf("%s\n", str);

            }

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

            //有响应
            //遍历socket
            for (u_int i = 0; i < readSocket.fd_count; i++)
            {
                if (readSocket.fd_array[i] == socketServer)
                {
                    //有链接(响应)-accept
                    SOCKET socketClient = accept(socketServer, NULL, NULL);
                    if (socketClient == SOCKET_ERROR)
                    {
                        //链接出错
                        continue;
                    }
                    FD_SET(socketClient, &allSocket);
                    //SEND
                    send(readSocket.fd_array[i], "服务器链接成功!", sizeof("服务器链接成功"),0);
                }
                else
                {
                    char strBuf[1500] = { 0 };
                    //客户端socket
                    int nRecv = recv(readSocket.fd_array[i], strBuf, 1500, 0);
                    if (nRecv == 0)
                    {
                        //客户端下线了
                        //从集合中去掉        
                        SOCKET socketTemp = readSocket.fd_array[i];
                        FD_CLR(readSocket.fd_array[i],&allSocket);
                        //释放
                        closesocket(socketTemp);

                    }
                    else if(nRecv > 0)
                    {
                        //接收到了消息
                        printf(strBuf);
                    }
                    else
                    {
                        //强制下线10054

                        //出错了SOCK_ERROR
                        int a = WSAGetLastError();
                        switch (a)
                        {
                            case 10054:
                            {
                            SOCKET socketTemp = readSocket.fd_array[i];
                            FD_CLR(readSocket.fd_array[i], &allSocket);
                            closesocket(socketTemp);
                            }
                        }
                        printf("%d", a);
                    }
                }
            }
        }
        else
        {
            //发生错误了
            break;
        }
    }

    //释放socket集合
    for (u_int i = 0; i < allSocket.fd_count; i++)
    {
        closesocket(allSocket.fd_array[i]);
    }

    WSACleanup();//正常关闭
    system("pause");
    return 0;
}
0

评论区