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

Socket模型(一):select模型, 异步I/O模型

Posted on 2011-05-13 20:57  +石头+  阅读(2837)  评论(0编辑  收藏  举报

为什么要采用Socket模型,而不直接使用Socket?

原因源于recv()方法是堵塞式的,当多个客户端连接服务器时,其中一个socket的recv调用时,会产生堵塞,使其他链接不能继续。

这样我们又想到用多线程来实现,每个socket链接使用一个线程,这样效率十分低下,根本不可能应对负荷较大的情况。

于是便有了各种模型的解决方法,总之都是为了实现多个线程同时访问时不产生堵塞。此篇文章介绍其中两种常见的模型,Select模型和异步I/O模型。

select的函数原型

int select(

int nfds, //Winsock中此参数无意义

fd_set* readfds, //进行可读检测的Socket

fd_set* writefds, //进行可写检测的Socket

fd_set* exceptfds, //进行异常检测的Socket

const struct timeval* timeout //非阻塞模式中设置最大等待时间

);

Select模型的客户端:

#include <WinSock.h>

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

 

#define MAXSOCKETCON 200

SOCKET ClientSockets[MAXSOCKETCON];

int NowClientSocket = 0;

SOCKET socketServer;

int CreateServerSocket(int Port)

{

    int iErrCode;

    WSADATA wsaData;

    iErrCode = WSAStartup(0x0202, &wsaData);

    int iRes;

    //创建Socket

    socketServer = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);

    //绑定

    SOCKADDR_IN addr;

    addr.sin_family = AF_INET;

    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    //addr.sin_addr.s_addr = 0x010000ff;

    addr.sin_port = htons(Port);

    iRes = bind(socketServer,(LPSOCKADDR)&addr,sizeof(addr));

    if(iRes == SOCKET_ERROR)

    {

        closesocket(socketServer);

        return -1;

    }

    iRes = listen(socketServer, MAXSOCKETCON);

    if(iRes == SOCKET_ERROR)

    {

        closesocket(socketServer);

        return -1;

    }

 

    return 1;

}

DWORD WINAPI RecvProc(LPVOID pParam)

{

    char Data[256];

    int iBuffer = 0;

    TIMEVAL TimeOut = {1, 0};

    while (1)

    {

        if(NowClientSocket == 0)

        {

            continue;

        }

        fd_set ReadSet;

        FD_ZERO(&ReadSet);

        for (int i = 0; i<NowClientSocket;i++)

        {

            FD_SET(ClientSockets[i], &ReadSet);

        }

        select(0, &ReadSet, NULL, NULL, &TimeOut);

        for (int i=0; i<NowClientSocket;i++)

        {

            if(FD_ISSET(ClientSockets[i], &ReadSet))

            {

                iBuffer = recv(ClientSockets[i], Data, 256, 0);

                if(iBuffer>0)

                {

                    printf("(%d)%s", i , Data);

                }

            }

        }

    }

}

DWORD WINAPI AcceptProc(LPVOID pParam)

{

    SOCKADDR_IN addr;

    int addrLen = sizeof(addr);

    SOCKET tempSocket;

    while (1)

    {

        tempSocket = accept(socketServer, (sockaddr*)&addr, &addrLen);

        if (tempSocket == INVALID_SOCKET)

        {

            return 0;

        }

        ClientSockets[NowClientSocket] = tempSocket;

        NowClientSocket++;

    }

}

int _tmain(int argc, _TCHAR* argv[])

{

    if(CreateServerSocket(6565)<1)

        return 0;

    CreateThread(NULL, NULL, &AcceptProc, NULL, NULL, NULL);

    CreateThread(NULL, NULL, &RecvProc, NULL, NULL, NULL);

    while(1)

    {

        Sleep(1000);

    }

    return 0;

}

Winsock提供了一个有用的异步I/O模型。利用这个模型,应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知:

  • 首先必须创建一个窗口,再为该窗口提供一个窗口例程支持函数(Winproc)。
  • 设置好窗口的框架后,便可开始创建套接字。
  • 建好一个套接字后调用WSAAsyncSelect函数。 WSAAsyncSelect函数定义如下:

    int WSAAsyncSelect(

    SOCKET s, //检查的套接字

    HWND hWnd, //接收消息的窗口句柄

    unsigned int uMsg, //发生网络事件时要接收的消息

    long lEvent //网络事件

);

应用:在CreateSocketServer中最后调用:WSAAsyncSelect(sktServer, hWnd, WM_SOCKET, FD_ACCEPT|FD_READ|FD_WRITE|FD_CLOSE);

在window的消息处理函数中:

case WM_SOCKET:

        switch(lParam)

        {

        case FD_ACCEPT:

            tempSocket = accept(wParam, (sockaddr*)&ADDR, &AddrLen);

            if(tempSocket != INVALID_SOCKET)

            {

                ClientSockets[NowClientCount++] = tempSocket;

            }

            break;

        case FD_READ:

            iBuffer = recv(wParam, Buffer, 256, 0);

            if(iBuffer > 0)

            {

                Sleep(0);

            }

            break;

        }