windows网络编程之Winsock非阻塞select模式服务器

//select(选择)模型,是利用select函数实现对i/o的管理。select函数可以用于
//判断套接字上是否存在数据,或者能否向一个套接字写入数据。
/*
int select(
int nfds,                       //被忽略参数,为了保持和早期Berkeley套接字应用程序兼容而保留的这个参数
fd_set* readfds,                //检查可读性fd_set*参数
fd_set* writefds,               //检查可写性fd_set*参数
fd_set* exceptfds,              //带外数据
const struct timeval* timeout   //指定select等待i/o操作完成时,最多等待多长时间。
);

typedef struct timeval 

long tv_sec;     //以秒为单位指定等待的时间
long tv_usec;    //以毫秒为单位指定的等待时间
} timeval;
用select对套接字进行监听前,应用程序必须将套接字句柄分配给一个集合,设置好一个或所有的读、写以及例外的
fd_set结构。将一个套接字分配给任何一个集合后,再来调用select,便可知道某个套接字上是否正在发生i/o活动。

Winsock提供了几个宏用来处理fd_set:
FD_ZERO(*set) //清空fd_set*
FD_CLR(s,*set) //从set中删除s套接字
FD_ISSET(s,*set) //检查s是否是set集合的一名成员,如果是返回TRUE
FD_SET(s,*set)   //设置套接字s加入集合set中
*/
#include "stdafx.h"
#include <iostream>
#include <Winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib,"WS2_32.lib")
using namespace std;
struct SocketObj
{
SOCKET socket;   //当前对象的socket
BOOL   listening; //该套接字是否已经
    SocketObj *next, //向后
        *prev; //向前
};
SocketObj *g_pSocketList = NULL; //Socket连表
SocketObj *g_pSocketEnd = NULL;   //连表的尾部
int        g_nSocketCount = 0;
HANDLE g_hSelect;

//创建SocketObj
SocketObj* GetSocketObj(SOCKET s,BOOL listening)
{
SocketObj *newSocketObj = NULL;
newSocketObj = (SocketObj*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(SocketObj));
if(newSocketObj == NULL)
{
   cout<<"GetSocketObj: HeapAlloc failed: "<< GetLastError()<<endl;
        ExitProcess(-1);     //结束进程
}
newSocketObj->socket = s;
newSocketObj->listening = listening;
return newSocketObj;
}


//插入一个SocketObj
void InserSocketObj(SocketObj *obj)
{
obj->next = obj->prev = NULL;
if(g_pSocketList == NULL)
{
   g_pSocketList = g_pSocketEnd = obj;
}
else
{
   obj->prev = g_pSocketEnd;
   g_pSocketEnd->next = obj;
   g_pSocketEnd = obj;
}
g_nSocketCount++;
}
//删除
void RemoveSocketObj(SocketObj *obj)
{
if(obj->prev)
{
   obj->prev->next = obj->next;
}
if(obj->next)
{
   obj->next->prev = obj->prev;
}
if(obj == g_pSocketList)
{
   g_pSocketList = obj->next;
}
if(obj == g_pSocketEnd)
{
   g_pSocketEnd = obj->prev;
}
g_nSocketCount--;
HeapFree(GetProcessHeap(),0,obj);
}
//监听线程
DWORD WINAPI ListenThread(void *pVoid)
{
cout<<"server start listening!"<<endl;

SOCKADDR_IN ClientAddr;                   // 定义一个客户端得地址结构作为参数
int addr_length=sizeof(ClientAddr);
SOCKET *listen = (SOCKET*)pVoid;
SocketObj *pSocketObj = NULL;
while(1)
{
   SOCKET Client = accept(*listen,(sockaddr*)&ClientAddr,&addr_length);
   if(Client == INVALID_SOCKET)
   {
    cout<<"accept failed!"<<endl;
    continue;
   }
   // 这里可以取得客户端的IP和端口,但是我们只取其中的SOCKET编号
   LPCTSTR lpIP = inet_ntoa(ClientAddr.sin_addr);
   UINT nPort = ClientAddr.sin_port;
   cout<<"一个客户端已经连接!IP:"<<lpIP<<"SOCKET 端口号:"<<nPort<<endl;
   //创建SocketObj并添加如list
   pSocketObj = GetSocketObj(Client,TRUE);
   InserSocketObj(pSocketObj);
   if(g_nSocketCount == 1)   //如果有一个客户端就唤起select线程
   {
    ResumeThread((HANDLE)g_hSelect);
   }
}
return 0;
}
//select线程函数
DWORD WINAPI SelectThread(void *pVoid)
{
SocketObj *sptr = NULL;
fd_set readfds,
     writefds,
     exceptfds;
timeval timeout;
char    Buffer[4096];
while(TRUE)
{
   //清空fd_set
   FD_ZERO(&readfds);
   FD_ZERO(&writefds);
   FD_ZERO(&exceptfds);

   //设置timeout
   timeout.tv_sec = 5;
   timeout.tv_usec = 0;

   sptr = g_pSocketList;
   //设置fd_set
   while(sptr)         
   {
    FD_SET(sptr->socket,&readfds);
    FD_SET(sptr->socket,&writefds);
    FD_SET(sptr->socket,&exceptfds);
    sptr = sptr->next;
   }

   //开始select
   int ret = select(0,&readfds,&writefds,NULL,&timeout);
   if(ret == SOCKET_ERROR)
   {
    cout<<"select failed!"<<endl;
    WSACleanup();
    return -1;
   }
   else if(ret == 0)
   {
    //超时
    cout<<"time out!"<<endl;
    continue;
   }
   else
   {
    sptr = g_pSocketList;
    while(sptr)
    {
     if(FD_ISSET(sptr->socket,&readfds))
     {
      ZeroMemory(Buffer,4096);
      int re = recv(sptr->socket,Buffer,4096,0);
      if(re == SOCKET_ERROR)
      {
       return -1;
      }
      else if(re == 0)
      {
       //该客户端已经断开
       closesocket(sptr->socket);
       SocketObj *tmp = sptr;
       sptr = sptr->next;
       RemoveSocketObj(tmp);
       continue;
      }
      else
      {
       cout<<"recv buffer:"<<Buffer<<endl;
      }
     }
     if(FD_ISSET(sptr->socket,&writefds))
     {
      //发送数据给当前客户端
      char *sendBuffer = "wwx send msg!";
      int re = send(sptr->socket,sendBuffer,16,0);
      if(re == SOCKET_ERROR)
      {
       return -1;
      }
      else if(re == 0)
      {
       continue;
      }
      else
      {
       //这里可以显示发送是否成功
       //cout<<"send successed!"<<endl;
      }
     }
     sptr = sptr->next;
    }
   }
}

return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
WSAData wsaData;
SOCKET Listen;
SocketObj *pSockobj = NULL;
SOCKET Accept = INVALID_SOCKET;
SOCKADDR_IN ServerAddr;

//初始化Winsock库
int ret = WSAStartup(MAKEWORD(2,2),&wsaData);
if(ret != 0)
{
   cout<<"WSAStartup error!"<<endl;
   WSACleanup();
   return -1;
}

Listen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(Listen == INVALID_SOCKET)
{
   cout<<"Listen create failed!"<<endl;
   WSACleanup();
   return -1;
}

//绑定
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
ServerAddr.sin_port = htons(10012);
ret = bind(Listen,(sockaddr*)&ServerAddr,sizeof(SOCKADDR_IN));
if(ret == SOCKET_ERROR)
{
   cout<<"bind failed!"<<endl;
   closesocket(Listen);
   WSACleanup();
   return -1;
}

//监听
listen(Listen,200);

//开启监听线程
HANDLE hListen = CreateThread(NULL,0,ListenThread,&Listen,0,NULL);
if(hListen == NULL)
{
   cout<<"Create ListenThread failed!"<<endl;
}
//创价挂起的select线程,因为刚开始没有连接的客户端
g_hSelect = CreateThread(NULL,0,SelectThread,NULL,CREATE_SUSPENDED,NULL);
if(g_hSelect == NULL)
{
   cout<<"Create SelectThread failed!"<<endl;
}
system("PAUSE");
return 0;
}

你可能感兴趣的:(windows网络编程之Winsock非阻塞select模式服务器)