前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >网络编程第六讲Select模型

网络编程第六讲Select模型

作者头像
IBinary
发布2019-05-25 16:47:17
5610
发布2019-05-25 16:47:17
举报
文章被收录于专栏:逆向技术逆向技术

一丶Select模型是什么

    以前我们讲过一个迭代模型.就是只服务一个客户端连接.但是实际网络编程中.复杂的很多. 比如一个 C/S架构程序 (客户端/服务端) 客户端很多的情况下.都要连接服务器.

不可能一个服务器只服务一个客户端. 就像现在很火的一款游戏 .PUBG. 绝地求生. 他就是 CS结构程序. 玩的人很多.不可能买了很多很多服务器吧.所以我们就要写模型. 来管理这些客户端的Socket

并对去进行读写操作. 当前 Select模型只针对小网络程序使用. 不可能应用到游戏上. 因为它能管理的Socket 实在有限. 如果是Windows的话可能以后会接触到事件模型.消息模型.以及IOCP模型.

其实说白了就是对Socket进行管理.有效的进行读写.减少开销. 随着模型等级越高.所需要的知识点就越多.就越来越困难.

二丶Select 方法

   socket 套接字为我们提供了一个Select 方法.

代码语言:javascript
复制
int WSAAPI select(
  int           nfds,            //默认为0
  fd_set        *readfds,        //一个指针集合.. 针对读操作 accept,recg
  fd_set        *writefds,       //针对写操作 connect send
  fd_set        *exceptfds,      //针对出现的异常
  const timeval *timeout         //超时设置,为NULL就是一直等待结果.
);
返回值:
  1超时返回0
  2.出错返回 SOCKET_ERROR

其中FD_SET是一个结构体.

代码语言:javascript
复制
typedef struct fd_set {
        u_int fd_count;               /* how many are SET? */  有多少套接字
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */ 套接字的数据
} fd_set;

FD_SETSIZE = 64 
所以说不能针对大网络.因为只有64个.当然你可以更改.不过更改之后还不如用更好的模型.

常用的方法;

  FD_ZERO 清零

  FD_SET 添加套接字 也就是往数组里面添加指针

  FD_ISSET 校验函数.如果参数是集合的成员.则返回 非0 ,否则返回0

代码语言:javascript
复制
int getsockopt(
  SOCKET s,            //查询的套接字
  int    level,        //选项的等级. SOL_SOCKET IPPROTO_TCP 原始套接字还是
  int    optname,      //SOCKET选项名字 SO_ERROR SO_ACCEPTCONN
  char   *optval,      //用于接受数据的缓冲区
  int    *optlen       //缓冲区大小
);
函数作用: 查看套接字的状态
返回值:  成功0 失败 SOCKET_ERROR

具体作用可以查询MSDN

select 函数的作用:

  上面说了怎么多.可能大家就很晕.该如何编写代码. 编写代码前说一下.或许能豁然开朗.

其实我们 定义了数组(集合) 当有事件来的时候.select会返回. 返回的时候.会把我们集合里面的值进行设置. 这样我们可以对集合的值进行判断.如果是accept 则调用accept.

代码如下:

代码语言:javascript
复制
// Server.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")



int main()
{
   
        WSADATA wsaData;
        int iResult;
        u_long iMode = 0;

        iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
        if (iResult != NO_ERROR)
            printf("Error at WSAStartup()\n");

        //-------------------------
        // Create a SOCKET object.
        SOCKET m_socket;
        m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (m_socket == INVALID_SOCKET) {
            printf("Error at socket(): %ld\n", WSAGetLastError());
            WSACleanup();
            return 0;
        }

        sockaddr_in hServerAddr;
        hServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
        hServerAddr.sin_family = AF_INET;
        hServerAddr.sin_port = htons(8564);

        bind(m_socket, (sockaddr *)&hServerAddr, sizeof(hServerAddr));

        //监听

        listen(m_socket, 1);

        //主要是进行连接的时候.代码不一样了.因为是非阻塞.所以返回的错误是资源暂时不可用.
        sockaddr_in hClientAddr;
        int nAddrSize = sizeof(hClientAddr);
        SOCKET hClientSocket;

        fd_set Read, Write, Except; //定义三个集合.并且清零

        FD_ZERO(&Read);
        FD_ZERO(&Write);
        FD_ZERO(&Except);

        //添加SOCKET 到集合中
        FD_SET(m_socket, &Read);
        FD_SET(m_socket, &Write);
        FD_SET(m_socket, &Except);

        //使用Select 进行监听 无限监听
        int nRet = select(0, &Read, &Write, &Except,NULL);

        //如果有读操作.则Read集合则被改变.

        nRet = FD_ISSET(m_socket, &Read);//只判断读
       
        char szBuf[0x1000] = { 0 };
        if (SOCKET_ERROR != nRet)
        {
               //我们就可以进行接收了.因为读操作只有接收.不可能是Recv
            hClientSocket = accept(m_socket, (sockaddr*)&hClientAddr, &nAddrSize);
            //继续进行清零.看看是否是读操作还是写操作.

            recv(hClientSocket, szBuf, 0x1000, 0);
            FD_ZERO(&Read);
            FD_ZERO(&Write);
            FD_ZERO(&Except);

            //讲客户端套接字跟服务端套接字都放到集合中.

            FD_SET(m_socket, &Read);
            FD_SET(m_socket, &Write);
            FD_SET(m_socket, &Except);

            FD_SET(hClientSocket, &Read);
            FD_SET(hClientSocket, &Write);          // 如果都设置了.那么在此进行Select监听的时候.服务端套Read集合则为2.因为要接受.客户端的Write则为1.因为他发送了send
            FD_SET(hClientSocket, &Except);

            nRet = select(0, &Read, &Write, &Except, NULL);

            nRet = FD_ISSET(hClientSocket, &Write); //客户端的写操作回来.所以影响的是客户端的write 服务端的不影响.

            int nRet2 = FD_ISSET(m_socket, &Write);
           

        }
    return -1;
}

当有客户端连接的时候.我们的集合就重置了.

例如下图:

可以看到套接字是一个f4 有一个.所以下方我们进行判断是否是读操作.如果是读操作我们就进行接受连接

接受连接之后.我们把客户端的套接字也设置到集合中.当监听客户端操作的时候.写操作就会来了.

总结一下:

    1.send recv connect accept 都是一个状态.

    2.slecet 会根据这些来设置我们集合中数组的状态.

    3.我们根据判断集合中数组的状态.对其进行操作即可.

如果出现异常.我们就需要用

代码语言:javascript
复制
getsockopt 来检索错误值了.
因为我们使用的FD_xxx都是宏. 如果在使用GetLastError 则会出现错误.结果不准确. 所以直接使用这个函数进行错误代码的获取.
例子:
代码语言:javascript
复制
if (FD_ISSET(hSocket,&Except))  //如果出现异常了.
{
    int nErrCode;
    int nErrCodeLen;
    getsocket(hSocket,SOL_SOCKET,SO_ERROR,&nErrcode,&nErrcodelen);
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-09-20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一丶Select模型是什么
  • 二丶Select 方法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档