~$ 存档

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

select模式的思想

创建FD_SET fd_all,并初始化FD_ZERO(&fd_all);

Step1  初始时: 

Step2   加入一个套接字之后,比如FD_SET(sServer,&fd_all);

Step3   调用select函数之后,有两种情况

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

上述的select假设是在非阻塞的情况下。由图示可以看出, select对FD_SET的结构进行了动态改变,没有变化的会置为0,有变化的会保持为1,这就是select的思想。

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

保持FD_SET的状态

假设有1,2,3,4四个标号的套接字加入FD_SET

//伪码
FD_SET(1,&fd_all);
FD_SET(2,&fd_all);
FD_SET(3,&fd_all);
FD_SET(4,&fd_all);
for(;;)
{
    ...
    select(0,&fd_all,0,NULL,NULL);
    ...
}

在进行选择之后,有些套接字可能暂时没有数据收发就被select函数过滤掉了,所以要保持套接字的状态,模式如下:

//保持套接字状态的方法示例

    while(true)
    {
        fd_read=fd_all;
        fd_write=fd_all;
        select(0,&fd_read,0,NULL,NULL);//①阻塞;

        for(UINT i=0;i<fd_all.fd_count;i++)
        {
            if(FD_ISSET(fd_all.fd_array[i],&fd_read))
            {
                if(fd_all.fd_array[i]==sServer)//
                {
                    sockaddr_in addrClient;
                    int addrClientlen = sizeof(addrClient);
                    sClient = accept(sServer,(sockaddr FAR*)&addrClient, &addrClientlen);//

                    FD_SET(sClient,&fd_all);//
                }
                else //⑤ 收发数据
                {
                    ZeroMemory(buf, BUF_SZIE);
                    retVal = recv(fd_all.fd_array[i], buf, BUF_SZIE-1, 0);//
                    if (SOCKET_ERROR == retVal)
                    {
                        printf("recv failed!\n");        
                        closesocket(sServer);
                        closesocket(sClient);    //关闭套接字        
                        WSACleanup();
                        return -1;
                    }
                    printf("%s\n", buf);
                }
            }
        }

 

版本一(有点问题,需要修正):

/**************************************************************************************************************

2018/10/9号进行修正:

原因:

1. 在select轮询时,如果没有连接返回-1
2. 在while循环时,需要Sleep(100),不然CPU空转太厉害导致CPU使用率上升!

/**************************************************************************************************************

#include <WinSock2.h>
#include <iostream>

#include <stdio.h>

#pragma comment(lib,"ws2_32.lib")

#define PORT 8000
#define MSGSIZE 255
#define SRV_IP "127.0.0.1"

int g_nSockConn = 0;                //请求连接的数目

struct ClientInfo
{
    SOCKET sockClient;                //客户端套接字
    SOCKADDR_IN addrClient;            //客户端地址
};

ClientInfo g_Client[FD_SETSIZE];    //客户端套接字集合;
DWORD WINAPI WorkThread(LPVOID lpParameter);

int main(int argc, char *argv[])
{
    WSADATA wsd;
    WSAStartup(MAKEWORD(2, 2), &wsd);

    SOCKET sockListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//创建套接字
    
    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr = inet_addr(SRV_IP);
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(PORT);
    bind(sockListen, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));//绑定

    listen(sockListen, 64);//监听

    DWORD dwThreadIDRecv = 0;
    DWORD dwThreadIDWrite = 0;

    HANDLE hand = CreateThread(NULL, 0, WorkThread, NULL, 0, &dwThreadIDRecv);//工作线程
    if (hand == NULL)
    {
        std::cout <<"创建线程失败!"<<std::endl;
        return -1;
    }

    SOCKET sockClient;
    SOCKADDR_IN addrClient;
    int nLenAddrClient = sizeof(SOCKADDR_IN);

    while (true)
    {
        sockClient = accept(sockListen, (SOCKADDR*)&addrClient, &nLenAddrClient);
        if (sockClient != INVALID_SOCKET)
        {
            g_Client[g_nSockConn].addrClient = addrClient;//保存客户端地址信息
            g_Client[g_nSockConn].sockClient = sockClient;
            g_nSockConn++;
        }
    }
    closesocket(sockListen);
    WSACleanup();
    return 0;
}

DWORD WINAPI WorkThread(LPVOID lpParameter)
{
    std::cout << "线程开始执行...." << std::endl;
    FD_SET fdRead;
    int nRet = 0;        //记录发送或者接受的字节数
    
    TIMEVAL tv;            //设置超时等待时间
    tv.tv_sec = 1;
    tv.tv_usec = 0;

    char buf[MSGSIZE] = { '\0' };

    while (true)
    {
        FD_ZERO(&fdRead);
        for (int i = 0; i < g_nSockConn; i++)
        {
            FD_SET(g_Client[i].sockClient, &fdRead);
        }

        //只处理read事件,不过后面还是会有读写消息发送的
        nRet = select(0, &fdRead, NULL, NULL, &tv);
        std::cout << "g_nSockConn=" << g_nSockConn << std::endl;
        std::cout << "nRet=" << nRet << std::endl;

        if (nRet == -1)//没有连接或者没有读事件
        {
       Sleep(100);  //需睡眠
continue; } for (int i = 0; i < g_nSockConn; i++) { std::cout << "已连接的套接字数目g_nSockConn=" << g_nSockConn << std::endl; Sleep(1000); if (FD_ISSET(g_Client[i].sockClient, &fdRead)) { nRet = recv(g_Client[i].sockClient, buf, sizeof(buf), 0); if (nRet == 0 || (nRet == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET)) { std::cout << "Client " << inet_ntoa(g_Client[i].addrClient.sin_addr) << "closed" <<std::endl; closesocket(g_Client[i].sockClient); if (i < g_nSockConn - 1) { //将失效的sockClient剔除,用数组的最后一个补上去 g_Client[i--].sockClient = g_Client[--g_nSockConn].sockClient; } } else { std::cout <<"客户端地址为:"<<inet_ntoa(g_Client[i].addrClient.sin_addr) << ": " << std::endl; std::cout << buf <<std::endl; strcpy_s(buf, "Hello!"); nRet = send(g_Client[i].sockClient, buf, strlen(buf) + 1, 0); g_nSockConn--; } } } } return 0; }

 版本二(正常运行):(2022年12月29号重新编辑)

 

#include "stdafx.h"

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

int main()
{
    WSADATA wsd;
    WSAStartup(MAKEWORD(2, 2), &wsd);

    USHORT nPort = 8000;
    SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(nPort);
    sin.sin_addr.S_un.S_addr = INADDR_ANY;
    if (::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
    {
        printf(" Failed bind() \n");
        return -1;
    }
    ::listen(sListen, 5);
    
    fd_set fd_all;
    FD_ZERO(&fd_all);
    FD_SET(sListen, &fd_all);
    while (TRUE)
    {
        fd_set fdRead = fd_all;
        int nRet = select(0, &fdRead, NULL, NULL, NULL);
        printf("loop...\n");
        if (nRet > 0)
        {
            for (int i = 0; i < (int)fd_all.fd_count; i++){
                if (FD_ISSET(fd_all.fd_array[i], &fdRead)){
                    //如果是监听套接字
                    if (fd_all.fd_array[i] == sListen){
                        if (fd_all.fd_count < FD_SETSIZE){
                            sockaddr_in addrRemote;
                            int nAddrLen = sizeof(addrRemote);
                            SOCKET sNew = ::accept(sListen, (SOCKADDR*)&addrRemote, &nAddrLen);
                            FD_SET(sNew, &fd_all);
                            printf("接收到连接(%s)\n", ::inet_ntoa(addrRemote.sin_addr));
                        }
                        else
                        {
                            printf(" Too much connections! \n");
                            continue;
                        }
                    }
                    else
                    {
                        char szText[256];
                        int nRecv = ::recv(fd_all.fd_array[i], szText, 256, 0);

                        //Sleep(1000);
                        if (nRecv > 0)          //  (2)可读 
                        {
                            szText[nRecv] = '\0';
                            printf("接收到数据:%s \n", szText);
                        }
                        else
                        {
                            printf("连接断开...!");
                            ::closesocket(fd_all.fd_array[i]);
                            FD_CLR(fd_all.fd_array[i], &fd_all);
                        }
                    }
                }
            }
        }
        else
        {
            printf(" Failed select() \n");
            break;
        }
    }
    return 0;
}

 

posted on 2017-10-20 11:32  LuoTian  阅读(311)  评论(0编辑  收藏  举报