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

/************************************
 作者:  wangweixing2000
 Revision By: 0.01
 Revised on 2005-6-29 11:52:03
 Comments: SelectModeWinSock 该程序中存在bug,没有处理多线程数据访问的冲突和判断客户端断开的有效处理!哪位有兴趣可以加上!

************************************/
//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;
}

『转载』 作者Blog:http://blog.csdn.net/wangweixing2000/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值