socket select 模型(详细注释版本)

模型原理地址:http://blog.csdn.net/qing666888/article/details/50427685

--------------------------------------------------------------------------------

// MySelectServer.cpp : 定义控制台应用程序的入口点。
//SOCKET Select TCP服务器模型

#include "stdafx.h"
#include <WinSock2.h>
#include <string>

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

#define PORT           5150  

int    g_iTotalConn = 0;  
SOCKET g_CliSocketArr[FD_SETSIZE];  


DWORD WINAPI WorkerThread(LPVOID lpParameter); 

static int getPeerToString(SOCKET sock, std::string &ip, unsigned short &port);


int _tmain(int argc, _TCHAR* argv[])
{
    //加载socket动态链接库
    WORD wVersionRequesed;        //保存要加载的winsocket版本信息
    WSADATA    wsaData;            //这结构是用于接收Windows Socket的结构信息的  
    

    wVersionRequesed = MAKEWORD(2,2);    //WINSOCK2.2版本

    int err = WSAStartup(wVersionRequesed,&wsaData);
    if (err!=0)
    {
        return -1;  // 返回值为零的时候是表示成功申请WSAStartup  
    }

    // 检查这个高字节是不是1,低字节是不是1 以确定是否我们所请求的2.2版本  
    // 否则的话,调用WSACleanup()清除信息,结束函数 
    if ( HIBYTE( wsaData.wVersion ) != 2 || LOBYTE( wsaData.wVersion ) != 2)
    {  
        WSACleanup( );  
        return -1;   
    } 


    // 创建socket操作,建立流式套接字,返回套接字号sockSrv  
    // SOCKET socket(int af, int type, int protocol);  
    // 第一个参数,指定地址簇(TCP/IP只能是AF_INET,也可写成PF_INET)  
    // 第二个,选择套接字的类型(流式套接字),第三个,特定地址家族相关协议(0为自动)
    SOCKET    sockSrv = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);


    // 套接字sockSrv与本地地址相连  
    // int bind(SOCKET s, const struct sockaddr* name, int namelen);  
    // 第一个参数,指定需要绑定的套接字;  
    // 第二个参数,指定该套接字的本地地址信息,该地址结构会随所用的网络协议的不同而不同  
    // 第三个参数,指定该网络协议地址的长度  
    // PS: struct sockaddr{ u_short sa_family; char sa_data[14];};  
    //                      sa_family指定该地址家族, sa_data起到占位占用一块内存分配区的作用  
    //     在TCP/IP中,可使用sockaddr_in结构替换sockaddr,以方便填写地址信息  
    //   
    //     struct sockaddr_in{ short sin_family; unsigned short sin_port; struct in_addr sin_addr; char sin_zero[8];};  
    //     sin_family表示地址族,对于IP地址,sin_family成员将一直是AF_INET。  
    //     sin_port指定将要分配给套接字的端口。  
    //     sin_addr给出套接字的主机IP地址。  
    //     sin_zero[8]给出填充数,让sockaddr_in与sockaddr结构的长度一样。  
    //     将IP地址指定为INADDR_ANY,允许套接字向任何分配给本地机器的IP地址发送或接收数据。  
    //     如果想只让套接字使用多个IP中的一个地址,可指定实际地址,用inet_addr()函数。  
    SOCKADDR_IN addrSrv;  
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);    // 将INADDR_ANY转换为网络字节序,调用 htonl(long型)或htons(整型)  
    addrSrv.sin_port = htons(PORT);

    bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 第二参数要强制类型转换  

    // 将套接字设置为监听模式(连接请求), listen()通知TCP服务器准备好接收连接  
    // int listen(SOCKET s,  int backlog);  
    // 第一个参数指定需要设置的套接字,第二个参数为(等待连接队列的最大长度)  
    listen(sockSrv, 10);

    // accept(),接收连接,等待客户端连接  
    // SOCKET accept(  SOCKET s,  struct sockaddr* addr,  int* addrlen);  
    // 第一个参数,接收一个处于监听状态下的套接字  
    // 第二个参数,sockaddr用于保存客户端地址的信息  
    // 第三个参数,用于指定这个地址的长度  
    // 返回的是向与这个监听状态下的套接字通信的套接字  


    // 客户端与用户端进行通信  
    // send(), 在套接字上发送数据  
    // int send( SOCKET s,  const char* buf,  int len,  int flags);  
    // 第一个参数,需要发送信息的套接字,  
    // 第二个参数,包含了需要被传送的数据,  
    // 第三个参数是buffer的数据长度,  
    // 第四个参数,一些传送参数的设置  

    // recv(), 在套接字上接收数据  
    // int recv(  SOCKET s,  char* buf,  int len,  int flags);  
    // 第一个参数,建立连接后的套接字,  
    // 第二个参数,接收数据  
    // 第三个参数,接收数据的长度,  
    // 第四个参数,一些传送参数的设置  

  
    DWORD       dwThreadId;
    // 创建一个工作者线程
    CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId); 

    SOCKADDR_IN  addrClient;  
    int len = sizeof(SOCKADDR);

    while (g_iTotalConn < FD_SETSIZE)  
    {  
        // 接受一个客户端连接
        SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);


        printf("客户端已经连接:%s:%d\n", inet_ntoa(addrClient.sin_addr), ntohs(addrClient.sin_port));   

        // 把已经连接的客户端添加到全局数组中
        g_CliSocketArr[g_iTotalConn] = sockConn; 

        g_iTotalConn++;
    }   

    return 0;
}


#define MSGSIZE        1024    //接受的最大缓冲区大小

DWORD WINAPI WorkerThread(LPVOID lpParam)  
{  
    int            i;  
    fd_set         fdread;  
    int            ret;  
    struct timeval tv = {1, 0};  
    char           szMessage[MSGSIZE];   

    while (TRUE)  
    {  
        FD_ZERO(&fdread);
        for (i = 0; i < g_iTotalConn; i++)  
        {  
            FD_SET(g_CliSocketArr[i], &fdread);  
        }   


        //int select
        //    (
        //    int nfds, //Winsock中此参数无意义
        //    fd_set* readfds, //进行可读检测的Socket
        //    fd_set* writefds, //进行可写检测的Socket
        //    fd_set* exceptfds, //进行异常检测的Socket
        //    const struct timeval* timeout //非阻塞模式中设置最大等待时间
        //    )

        /*struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态:
        第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
        第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
        第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
        */
        // 只关心读取事件
        ret = select(0, &fdread, NULL, NULL, &tv);   

        //因为如果读集中没有任何套接字,select函数会立刻返回-1,这将导致工作者线程成为一个毫无停顿的死循环,CPU的占用率马上达到100%。
        if (ret == -1)
        {
            Sleep(1);
        }

        //0:等待超时,没有可读写或错误的文件
        if (ret == 0)  
        {  
            // Time expired  
            continue;  
        }   

        for (i = 0; i < g_iTotalConn; i++)
        {  
            //判断描述符fd是否在给定的描述符集fdset中,通常配合select函数使用,由于select函数成功返回时会将未准备好的描述符位清零。
            //通常我们使用FD_ISSET是为了检查在select函数返回后,某个描述符是否准备好,以便进行接下来的处理操作。
            if (FD_ISSET(g_CliSocketArr[i], &fdread))  
            {  
                // 一个读取事件发生在 g_CliSocketArr[i]  
                ret = recv(g_CliSocketArr[i], szMessage, MSGSIZE, 0);  

                if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))  
                {
                    // 客户端关闭 把后面的SOCKET往前移动一个
                    printf("Client socket %d closed.\n", g_CliSocketArr[i]);  
                    closesocket(g_CliSocketArr[i]);  

                    //把当前关闭的SOCKET后面的SOCKET数组前移动一个位置
                    if (i<=g_iTotalConn -1)
                    {
                        for (int j =0;j<g_iTotalConn - i -1;j++)
                        {
                            g_CliSocketArr[i+j] = g_CliSocketArr[i+j+1];
                        }
                        g_iTotalConn--;
                    }
                }  
                else  
                {  
                    //获取SOCKET的IP和端口
                    std::string Ip;
                    unsigned short port;
                    getPeerToString(g_CliSocketArr[i],Ip,port);

                    // 从客户端接受到的信息
                    szMessage[ret] = '\0';
                    printf("客户端:%s 端口:%d 接收到客户端数据:%s\n", Ip.c_str(),port,szMessage);  


                    //把接收到信息再发送给客户端
                    send(g_CliSocketArr[i], szMessage, strlen(szMessage), 0);  
                }  
            }  
        }  
    }   

    return 0;  
}   


//获取SOCKET的IP和端口
static int getPeerToString(SOCKET sock, std::string &ip, unsigned short &port)  
{
    struct sockaddr_storage sa;  
    int salen = sizeof(sa);  
    if (::getpeername(sock, (struct sockaddr*)&sa, &salen) == -1) 
    {  
        ip = "?";  
        port = 0;  
        return -1;  
    }  

    if (sa.ss_family == AF_INET) 
    {  
        struct sockaddr_in *s = (struct sockaddr_in*)&sa;  
        ip = ::inet_ntoa(s->sin_addr);  
        port = ::ntohs(s->sin_port);  
        return 0;  
    }  
    return -1;  

 

转载于:https://my.oschina.net/ffs/blog/1612598

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值