C++ socket编程select模型

在做Socket编程时,当要处理一个server对应多个client,这种可以每个客户端用一个线程来处理,但是客户端太多,程序的性能会降低。Windows提供了select模型,很好的处理了一对多的模型。select的申明如下:

Int WSAAPI select(
_In_ int nfds,   //0,无意义
_Inout_opt_ fd_set FAR * readfds,  //检查可读性集合
_Inout_opt_ fd_set FAR * writefds, //检查可写性集合
_Inout_opt_ fd_set FAR * exceptfds,//进行异常检测的Socket
_In_opt_ const struct timeval FAR * timeout //非阻塞模式中设置最大等待时间;


         从msdn可查看参数的详细解释:

         Any two of the parameters, readfds, writefds, or exceptfds, can be given as null. At least one must be non-null, and any non-nulldescriptor set must contain at least one handle to a socket.

        In summary, a socket will be identified in a particular set when select returns if:

       其中任何两个参数readfds、writefds或exceptfds都可以被指定为null。至少一个必须是非空的,任何非空描述符集必须包含一个套接字的至少一个句柄。

        总之,当select返回以下情况时,套接字将在特定集合中标识:

readfds:

If listen has been called and a connection is pending, accept will succeed.
       如果监听已被调用,且连接正在挂起,此时调用accept函数会成功

Data is available for reading (includes OOB data if SO_OOBINLINE is enabled).
       有数据可读,如果启用SO_OOBINLINE,则包含OOB数据

Connection has been closed/reset/terminated.
       连接已经关闭,重置或终止

writefds:

If processing a connect call (nonblocking), connection has succeeded.
Data can be sent.
exceptfds:

If processing a connect call (nonblocking), connection attempt failed.
OOB data is available for reading (only if SO_OOBINLINE is disabled).
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;
 
#define FD_SETSIZE      64

可知select模型最多可以处理64个套接字

Return Value
    The select function returns the total number of socket handles that are ready and contained in the fd_set structures, zero if the time limit expired, or SOCKET_ERROR if an error occurred. If the return value is SOCKET_ERROR, WSAGetLastError can be used to retrieve a specific error code.

          调用成功时select函数返回准备好并包含在fd_set结构中的套接字句柄的总数,如果时间限制过期,则返回零,如果发生错误,则返回SOCKET_ERROR。如果返回值是SOCKET_ERROR,则可以使用WSAGetLastError来检索特定的错误代码。

select模型紧密结合的四个宏来简化对socket的操作:

          FD_CLR( s,*set) 从队列set删除句柄s。

          FD_ISSET( s, *set) 检查句柄s是否存在与队列set中。

          FD_SET( s,*set )把句柄s添加到队列set中。

           FD_ZERO( *set ) 把set队列初始化成空队列。

select判断sockets是否刻度的步骤:
1.将该套接字加入到readfds集合;
2.readfds作为第二个参数调用select函数;
3.当select函数返回时,应用程序判断该套接字是否任然存在于readfds集合;
4.如果该套接字存在于readfds集合,则表明该套接字刻度,此时可以调用recv函数接受数据,否则,
 

下面的代码是一个server对应多个client, 仅做测试demo, 如果是实际项目,需实际处理。

服务端代码

#include<winsock2.h>
#include<iostream>
 
using  namespace  std;
 
#pragma comment(lib,"Ws2_32.lib")
 
 
int  main()
{
    //初始化winsock的环境
    WSADATA  wd;
    if (WSAStartup(MAKEWORD(2, 2, ), &wd) == SOCKET_ERROR)
    {
        cout << "WSAStartup  error:" << GetLastError() << endl;
        return 0;
    }
 
    //1.创建监听套接字
    SOCKET  sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sListen == INVALID_SOCKET)
    {
        cout << "socket  error:" << GetLastError() << endl;
        return 0;
    }
 
    //2.绑定到ip与端口
    sockaddr_in  addr;
    addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(8000);
    addr.sin_family = AF_INET;
    int len = sizeof(sockaddr_in);
    if (bind(sListen, (SOCKADDR*)&addr, len) == SOCKET_ERROR)
    {
        cout << "bind  error:" << GetLastError() << endl;
        return 0;
    }
 
    //3.监听套接字
    if (listen(sListen, 5) == SOCKET_ERROR)
    {
        cout << "listen  error:" << GetLastError() << endl;
        return 0;
    }
 
    //4. select开始了
    fd_set  readSet;//定义一个读(接受消息)的集合
    FD_ZERO(&readSet);//初始化集合
    FD_SET(sListen, &readSet);
 
    //不停的select才可以读取套接字的状态改变
    while (true)
    {
        fd_set tmpSet;//定义一个临时的集合
        FD_ZERO(&tmpSet);//初始化集合
        tmpSet = readSet;// 每次循环都是所有的套接字
 
        //利用select选择出集合中可以读写的多个套接字,有点像筛选
        int ret = select(0, &tmpSet, NULL, NULL, NULL);//最后一个参数为NULL,一直等待,直到有数据过来
        if (ret == SOCKET_ERROR)
        {
            continue;
        }
 
        //成功筛选出来的tmpSet可以发送或者接收的socket
        for (int i = 0; i < tmpSet.fd_count; ++i)
        {
            //获取到套接字
            SOCKET  s = tmpSet.fd_array[i];
 
            //接收到客户端的链接
            if (s == sListen)
            {
                SOCKET  c = accept(s, NULL, NULL);
                //fd_set集合最大值为64
                if (readSet.fd_count < FD_SETSIZE)
                {
                    //往集合中添加客户端套接字
                    FD_SET(c, &readSet);
                    cout << "欢迎" << c << "进入聊天室!" << endl;
 
                    //给客户端发送欢迎
                    char buf[100] = { 0 };
                    sprintf(buf, "欢迎%d进入聊天室!", c);
                    send(c, buf, 100, 0);
                }
                else
                {
                    cout << "达到客户端容量上线!" << endl;
                }
 
            }
            else//一定是客户端
            {
                //接收客户端的数据
                char buf[100] = { 0 };
                ret = recv(s, buf, 100, 0);
                if (ret == SOCKET_ERROR || ret == 0)
                {
                    closesocket(s);
                    FD_CLR(s, &readSet);
                    cout << s << "离开聊天室!" << endl;
                }
                else
                {
                    cout << s << "说:" << buf << endl;
                }
            }
        }
    }
 
    //关闭监听套接字
    closesocket(sListen);
 
    //清理winsock环境
    WSACleanup();
 
    return 0;
}

 

客户端代码

#include<winsock2.h>//winsock2的头文件
#include<iostream>
using  namespace std;
 
//勿忘,链接dll的lib
#pragma comment(lib, "ws2_32.lib")
 
int  main()
{
    //加载winsock2的环境
    WSADATA  wd;
    if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
    {
        cout << "WSAStartup  error:" << GetLastError() << endl;
        return 0;
    }
 
    //1.创建流式套接字
    SOCKET  s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (s == INVALID_SOCKET)
    {
        cout << "socket  error:" << GetLastError() << endl;
        return 0;
    }
 
    //2.连接服务器
    sockaddr_in   addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8000);
    addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
 
    int len = sizeof(sockaddr_in);
    if (connect(s, (SOCKADDR*)&addr, len) == SOCKET_ERROR)
    {
        cout << "connect  error:" << GetLastError() << endl;
        return 0;
    }
 
    //3接收服务端的消息
    char buf[100] = { 0 };
    recv(s, buf, 100, 0);
    cout << buf << endl;
 
    //3随时给服务端发消息
    int  ret = 0;
    do
    {
        char buf[100] = { 0 };
        cout << "请输入聊天内容:";
        cin >> buf;
        ret = send(s, buf, 100, 0);
    } while (ret != SOCKET_ERROR && ret != 0);
 
    //4.关闭监听套接字
    closesocket(s);
 
    //清理winsock2的环境
    WSACleanup();
 
    return 0;
}


例如,测试结果如下,在服务端窗口可以看到客户端的连接情况。

select模型只处理了64个socket, 如果多于64个如何处理呢,Hp-Socket, IOCP这两种可以试试, 暂时没有项目用到,以后再研究研究。

原文链接:https://blog.csdn.net/yao_hou/article/details/102728970

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值