对套接字简单的封装

// SockObj.h: interface for the CSockObj class.
//
//

#if !defined(SOCKETOBJ_H)
#define SOCKETOBJ_H

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "../std.h"

const int SOCKET_SUCCESS = 0;//与SOCKET_ERROR对应的成功标志
const int _SOCKET_MAJOR_VERSION = 2;
const int _SOCKET_MINOR_VERSION = 2;
const int _LISTEN_COUNT = 200;
const DWORD _SHUTDOWN_RECV_TIMEOUT = 4*1000; // 完美关闭时延迟4秒
const DWORD _RECV_TIMEOUT = 120;// 120秒 接收超时
const DWORD _SEND_TIMEOUT = 120;// 120秒 发送超时
const DWORD _ACCEPT_TIMEOUT = 12;// 120秒 接受超时
const DWORD _BLOCKED_SNDRCV_SLEEP = 100; // 100毫秒(发生阻塞时等待[睡眠]时间)
const DWORD _RECV_BUFF_LEN = 8192;

bool GetIpBySock(SOCKET Sock, char* IP);

class CSockObj 
{
public:
 CSockObj();
 virtual ~CSockObj();

public:
 static void CleanupLibrary(void);
 static int InitLibrary(void);

protected:
 int m_nErrorNo;

public:
 //创建具有重叠IO能力的套接字
 SOCKET CreateSocket(int nAddressFamily= AF_INET,
  int nType= SOCK_STREAM,int nProtocol= 0);
 //设置套接字属性
 int SetSocketOption(SOCKET hSocket);

 //设置套接字是否为阻塞的
 int BlockSocket(SOCKET hSocket, BOOL bBlock);

 //绑定套接字
 int BindSocketEx(SOCKET hSocket,int nPort);
 //基本参数绑定套接字
 int BindSocket(SOCKET hSocket, struct sockaddr * pSocketAddress, int nAddrLen);
    //连接套接字
 int Connect(SOCKET hSocket, const char* pIP, int nPort);
 //监听套接字
 int ListenSocket(SOCKET hSocket, int nConnections=_LISTEN_COUNT);
 //阻塞ACCEPT,没有响应不返回
 SOCKET Accept_Block(SOCKET hSocket, struct sockaddr* pSocketAddress=NULL, int *nAddrLen=NULL);

 //异步ACCEPT,超时返回
 SOCKET Accept(SOCKET hSocket, struct sockaddr* pSocketAddress=NULL, int *nAddrLen=NULL,
  DWORD dwTimeout=_ACCEPT_TIMEOUT);

 //int RecvDataFrom_Block( SOCKET hSocket, struct sockaddr* pFrom,  int nAddrlen,
 // char* pszBuffer, int nBufferSize, DWORD dwTimeout=_RECV_TIMEOUT);
 //阻塞接收数据,该函数停止是在该端口被关闭(接收长度为0),或出错误的时候
 int RecvData_Block(SOCKET hSocket, char *pszBuffer, int nBufferSize,
  DWORD dwTimeout= _RECV_TIMEOUT);

 int RecvData_Event(SOCKET hSocket, char *pszBuffer, int nBufferSize,
  DWORD dwTimeout= _RECV_TIMEOUT);
 //接收所有数据,注意在这个函数调用之前必须确认是否有接收消息到来
 int RecvData(SOCKET hSocket, char *pszBuffer, int nBufferSize);

 //发送全部缓冲区中数据,阻塞
 int SendData_Block(SOCKET hSocket,char const * pszBuffer, int nBufferSize,
  DWORD dwTimeout=_SEND_TIMEOUT);
 //发送数据,阻塞
 int Send_Block(SOCKET hSocket, char const * pszBuffer,  int nBufferSize,
  DWORD dwTimeout=_SEND_TIMEOUT);

 //发送一次缓冲区数据,不过有可能没有全部发送
 //int Send_Event(SOCKET hSocket, char const * pszBuffer, int nBufferSize,
 // DWORD dwTimeout=_SEND_TIMEOUT);
 //关闭套接字
 void CloseSocket(SOCKET hSocket, BOOL bHardClose=FALSE);

 inline void SetLastError(int iErrorCode)//设置错误代码
 {
  InterlockedExchange((LPLONG)&m_nErrorNo, iErrorCode);  
 };

 inline int GetLastError()//获取最近一次操作的错误代码
 {
  int iErrorNo;
  InterlockedExchange((LPLONG)&iErrorNo, m_nErrorNo);
  return (iErrorNo);
 };
};

#include "SockObj.h"

bool GetIpBySock(SOCKET Sock, char* IP)
{
 struct sockaddr sa;
 int len = sizeof(sa);

 if (0 != getpeername(Sock, &sa, &len))
  return false;

 for (size_t i =0; i<6; ++i) {
  IP[i] = sa.sa_data[i + 2];
 }

 return true;
}

CSockObj::CSockObj()
{
 
}

CSockObj::~CSockObj()
{
 //CleanupLibrary();
}

void CSockObj::CleanupLibrary(void)
{
 WSACleanup();
}

int CSockObj::InitLibrary(void)
{
 WSADATA WSD;
 WORD wVersionRequired = MAKEWORD( _SOCKET_MAJOR_VERSION, _SOCKET_MINOR_VERSION );
 ZeroMemory(&WSD,sizeof(WSADATA));
 int nErrorNo = WSAStartup(wVersionRequired, &WSD);
 if ( SOCKET_SUCCESS != nErrorNo )
 {
  //SetLastError( nErrorNo );
  return ( SOCKET_ERROR );
 }
 if ( LOBYTE( WSD.wVersion ) != _SOCKET_MINOR_VERSION ||
   HIBYTE( WSD.wVersion ) != _SOCKET_MAJOR_VERSION )
 {
  WSACleanup( );
  //SetLastError( WSAVERNOTSUPPORTED );
  return (SOCKET_ERROR);
 }
 //成功初始化
 return (SOCKET_SUCCESS);
}

//设置套接字属性
//入口:套接字
//出口:如果正确那么返回0,错误返回-1
int CSockObj::SetSocketOption(SOCKET hSocket)
{
    int nActivate = 1;
 //允许地址重用
    if (setsockopt(hSocket, SOL_SOCKET, SO_REUSEADDR, (const char *) &nActivate,
  sizeof(nActivate)) == SOCKET_ERROR )
    {
        SetLastError( WSAGetLastError() );
        return (SOCKET_ERROR);//return (-1)
    }
 /*
 bool bDontLinger=true;
    if (setsockopt(hSocket, SOL_SOCKET, SO_DONTLINGER, (const char *) &bDontLinger,
  sizeof(bDontLinger)) == SOCKET_ERROR )
    {
        SetLastError( WSAGetLastError() );
        return (SOCKET_ERROR);//return (-1)
    }
  
 linger Linger={0,0};
    if (setsockopt(hSocket, SOL_SOCKET, SO_LINGER, (const char *) &Linger,
  sizeof(Linger)) == SOCKET_ERROR )
    {
        SetLastError( WSAGetLastError() );
        return (SOCKET_ERROR);//return (-1)
    }*/
 return (SOCKET_SUCCESS);
}

//设置套接字是否为阻塞的
//入口:套接字,是否需要阻塞的
//出口:如果正确那么返回0,错误返回-1
int CSockObj::BlockSocket(SOCKET hSocket, BOOL bBlock/*FALSE*/)
{
 u_long IoctlLong = (bBlock) ? 0 : 1;
 if (ioctlsocket(hSocket, FIONBIO, &IoctlLong) == SOCKET_ERROR)
 {
  SetLastError( WSAGetLastError() );
  return (SOCKET_ERROR);
    }
 return (SOCKET_SUCCESS);
}

int CSockObj::Connect(SOCKET hSocket, const char* pIP, int nPort)
{
 SOCKADDR_IN sockAddr;
 ZeroMemory(&sockAddr,sizeof(sockAddr));
 
 sockAddr.sin_family   = AF_INET;
 sockAddr.sin_addr.s_addr = inet_addr(pIP);
 sockAddr.sin_port   = htons((u_short)nPort);

 return connect(hSocket, (struct sockaddr*)&sockAddr, sizeof(SOCKADDR_IN));
}

//关闭套接字
//入口:套接字,是否强行关闭(如果bHardClose==FALSE,那么接收剩余的数据后关闭连接)
void CSockObj::CloseSocket(SOCKET hSocket, BOOL bHardClose)
{
 //不需要捕获错误
 if (!bHardClose) //优雅关闭 Graceful close
 {
  //不再发送数据,对于TCP套接字,在所有的数据都发送完毕之后,
  //将发送一个 FIN ,通知接收方所有数据已经发送完毕。
  shutdown(hSocket, SD_SEND);

  //接收缓冲区有可能还有未接收的数据,在关闭套接字之前应该先
  //读取残留的数据。
  int  nRecvResult;
  HANDLE hSocketEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  //为残留数据提供的缓冲区
  char szBuffer[256];
  do
  {
   if (hSocketEvent != NULL)
   {
    //注册网络事件
    WSAEventSelect(hSocket,(WSAEVENT) hSocketEvent, FD_READ | FD_CLOSE);
    WSAWaitForMultipleEvents(1,&hSocketEvent, TRUE, _SHUTDOWN_RECV_TIMEOUT, TRUE);
    //清除网络事件
    WSAEventSelect(hSocket, (WSAEVENT) hSocketEvent, 0);
   }
   ZeroMemory(szBuffer,256);
   //接收残留数据
   nRecvResult = RecvData(hSocket, szBuffer, sizeof(szBuffer));
  } while (nRecvResult > 0);

  if (hSocketEvent != NULL)
   CloseHandle(hSocketEvent);
  //不再允许接收和发送
  shutdown(hSocket, SD_BOTH);
 }
 // 关闭套接字
 if (0 != hSocket)
  closesocket(hSocket);
}

// 绑定套接字
//入口:套接字,绑定的地址信息,长度
//出口:正确0,错误-1
int CSockObj::BindSocket(SOCKET hSocket, struct sockaddr * pSocketAddress,
        int nAddrLen)
{
 if (bind(hSocket, pSocketAddress, nAddrLen) == SOCKET_ERROR)
 {
  SetLastError( WSAGetLastError() );
  return (SOCKET_ERROR);
 }
 return (SOCKET_SUCCESS);
}

// 绑定套接字
//入口:套接字,端口号
//出口:正确0,错误-1
int CSockObj::BindSocketEx(SOCKET hSocket,int nPort)
{
 SOCKADDR_IN sockAddr;
 ZeroMemory(&sockAddr,sizeof(sockAddr));

 sockAddr.sin_family   = AF_INET;
 sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
 sockAddr.sin_port   = htons((u_short)nPort);

 return BindSocket(hSocket,(SOCKADDR *)&sockAddr, sizeof(sockAddr));
}

//创建具有重叠IO能力的套接字
//入口:协议,协议类型(TCP/U),协议
//出口:返回创建的重叠IO SOCKET
//注意:使用SOCKET()函数创建的套接字默认具有重叠IO能力
SOCKET CSockObj::CreateSocket(int nAddressFamily /*= AF_INET*/,
           int nType/*= SOCK_STREAM*/,
           int nProtocol/*= 0*/)
{
 SOCKET hSocket = WSASocket(nAddressFamily, nType, nProtocol,
  NULL,0,WSA_FLAG_OVERLAPPED); 
 if ( hSocket == INVALID_SOCKET )
 {
  SetLastError( WSAGetLastError() );
  return (INVALID_SOCKET);
 }

 //设置套接字选项
 if ( SOCKET_ERROR == SetSocketOption(hSocket) ) //设置属性失败
 {
  CloseSocket(hSocket, TRUE);
  return (INVALID_SOCKET);
 }
 return (hSocket);
}

//阻塞ACCEPT,没有响应不返回
//入口:套接字,主机地址,长度
//出口:正确返回端口号,否则返回INVALID_SOCKET
SOCKET CSockObj::Accept_Block(SOCKET hSocket, struct sockaddr* pSocketAddress,
         int *nAddrLen)
{
 if (INVALID_SOCKET == hSocket)
  return (INVALID_SOCKET);
 SOCKET hAccept = accept(hSocket, pSocketAddress, nAddrLen);

 //如果该端口错误
 if (INVALID_SOCKET == hAccept)
  SetLastError(WSAGetLastError());
 return hAccept;
}

SOCKET CSockObj::Accept(SOCKET hSocket, struct sockaddr* pSocketAddress, int *nAddrLen,
        DWORD dwTimeout)

 if (INVALID_SOCKET == hSocket)
  return (SOCKET_ERROR);

 //dwTimeout = 1000*1000;
 SOCKET hAccept;
 bool bNodelay;
 FD_SET fd = {1, hSocket};
 TIMEVAL tv = {dwTimeout, 0};
 unsigned long flag = 1;
 int nBytesSent=0;
 //printf("进入select%d time=%d/r/n", hSocket,dwTimeout);
 int iError = select(0, &fd, NULL, NULL, &tv);
 if(iError == 0)
 {
  //printf("离开select/r/n");
  goto CLEAR;//选择发送超时
 }else if (SOCKET_ERROR == iError)
 {
  //printf("select error/r/n");
 }else
 {
  hAccept = accept(hSocket, pSocketAddress, nAddrLen);
  //如果该端口错误
  if (INVALID_SOCKET == hAccept)
   SetLastError(WSAGetLastError());  
  else
  {
   setsockopt(hAccept,IPPROTO_TCP,TCP_NODELAY,(char *)&bNodelay,sizeof(bNodelay));
   //ioctlsocket(hAccept, FIONBIO, &flag);
  }
  return hAccept;
 }
CLEAR:
 SetLastError(WSAGetLastError());//超时
 return(INVALID_SOCKET);
}

//监听套接字
//入口:套接字,接入的等待队列长度
//出口:SOCKET_ERROR/SOCKET_SUCCESS
int CSockObj::ListenSocket(SOCKET hSocket, int nConnections)
{
 if(listen(hSocket, nConnections) == SOCKET_ERROR)
 {
  SetLastError( WSAGetLastError() );
  return (SOCKET_ERROR);
 }
 return (SOCKET_SUCCESS);
}

//阻塞接收数据,该函数停止是在该端口被关闭(接收长度为0),或出错误的时候
//入口:套接字,接收缓冲区和长度,超时数
//出口:正确返回接收长度,否则返回SOCKET_ERROR
int CSockObj::RecvData_Block(SOCKET hSocket, char *pszBuffer, int nBufferSize,
  DWORD dwTimeout)
{
 if (hSocket==INVALID_SOCKET || pszBuffer==NULL)
  return (SOCKET_ERROR);

 //dwTimeout = 1000*1000;
 FD_SET fd = {1, hSocket};
 TIMEVAL tv = {dwTimeout, 0};
 int nBytesReceived=0;
 int nSelectRet = select(0, &fd, NULL, NULL, &tv);
 //返回大于0的值时,表示与条件相符的SOCKET数
 //返回0表示超时
 //失败时返回SOCKET_ERROR
 if (0 == nSelectRet  || //超时
  SOCKET_ERROR == nSelectRet)//套接字错误
  goto CLEAR;

 if((nBytesReceived = recv(hSocket, pszBuffer, nBufferSize, 0)) == SOCKET_ERROR)
  goto CLEAR;
 //成功时返回收到的字节数.
 //如果连接被中断则返回0
 //失败时返回 SOCKET_ERROR
 return nBytesReceived;

CLEAR:
 SetLastError(WSAGetLastError());//超时
 return(SOCKET_ERROR);
}

//接收所有数据,注意在这个函数调用之前必须确认是否有接收消息到来
//入口:套接字,数据缓冲区,缓冲区大小
//出口:如果正确那么返回接收的字节数量,错误返回错误代码
int CSockObj::RecvData(SOCKET hSocket, char *pszBuffer, int nBufferSize)
{
 if (hSocket==INVALID_SOCKET || pszBuffer==NULL)
  return (SOCKET_ERROR);

 DWORD  dwRtxBytes = 0,
    dwRtxFlags = 0;
 WSABUF  WSABuff;

 //清空缓冲
 ZeroMemory(&WSABuff,sizeof(WSABUF));

 WSABuff.len = nBufferSize;
 WSABuff.buf = pszBuffer;
 //如果正确就返回本次接收的字节个数,如果错误返回错误号码(负数)
 return ((WSARecv(hSocket, &WSABuff, 1, &dwRtxBytes, &dwRtxFlags,NULL, NULL)
  == SOCKET_SUCCESS) ? (int) dwRtxBytes : -WSAGetLastError());
}

//接收数据(阻塞直至收到数据为止)
//入口:套接字,数据缓冲区,缓冲区大小,超时数
//出口:如果正确那么返回接收的字节数量,错误返回错误代码
int CSockObj::RecvData_Event(SOCKET hSocket, char *pszBuffer,
          int nBufferSize, DWORD dwTimeout)
{
 if (hSocket==INVALID_SOCKET || pszBuffer==NULL)
  return (SOCKET_ERROR);

 HANDLE hReadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
 if (hReadEvent == NULL)
 {
  SetLastError( (int)GetLastError() );
  return ( SOCKET_ERROR );
 }

 int  nRecvBytes = 0;
 DWORD dwWaitResult ;
 for (;;)
 {
  // 注册FD_READ | FD_CLOSE 事件
  // (因为可能在等待FD_READ事件中,对方关闭套接字,所以要关注FD_CLOSE)
  if( WSAEventSelect(hSocket, (WSAEVENT) hReadEvent, FD_READ | FD_CLOSE)
   == SOCKET_ERROR)
  {
   CloseHandle(hReadEvent);
   SetLastError( WSAGetLastError() );
   return (SOCKET_ERROR);
  }
 
  // 等等FD_READ | FD_CLOSE事件的发生
  dwWaitResult = WSAWaitForMultipleEvents(1, &hReadEvent, TRUE,dwTimeout, TRUE);

  if (dwWaitResult != WSA_WAIT_EVENT_0)
  {
   // 清除事件
   WSAEventSelect(hSocket, (WSAEVENT) hReadEvent, 0);
   CloseHandle(hReadEvent);
   SetLastError( WSAGetLastError() );
   return (SOCKET_ERROR);
  }
  
  //
  /// 注意:即使 dwWaitResult == WSA_WAIT_EVENT0 ,也应该
  ///   进一步检查网络是否发生错误
  ///
  WSANETWORKEVENTS NetEvent;
  if(WSAEnumNetworkEvents(hSocket,(WSAEVENT)hReadEvent,&NetEvent) == SOCKET_ERROR)
  {
   // 清除事件
   WSAEventSelect(hSocket, (WSAEVENT) hReadEvent, 0);
   CloseHandle(hReadEvent);
   SetLastError( WSAGetLastError() );
   return (SOCKET_ERROR);
  }
  //判断发生了什么事件 FD_READ 或 FD_CLOSE 
  if( ( NetEvent.lNetworkEvents == FD_CLOSE ) ||
    ( NetEvent.lNetworkEvents == FD_READ &&
      NetEvent.iErrorCode[FD_READ_BIT] !=0 ) ) // 发生错误
  {   
   // 清除事件
   WSAEventSelect(hSocket, (WSAEVENT) hReadEvent, 0);
   CloseHandle(hReadEvent);
   SetLastError(WSAGetLastError() );
   return (SOCKET_ERROR);
  }
  
  // 清除事件
  WSAEventSelect(hSocket, (WSAEVENT) hReadEvent, 0);
 
  // 接收数据
  printf("socket[%d] into RecvData/r/n", hSocket);
  if ((nRecvBytes = RecvData(hSocket, pszBuffer, nBufferSize)) >= 0)
   break; // 跳出循环
  printf("socket[%d] into RecvData, nRecvBytes=%d /r/n", hSocket, nRecvBytes);
  //Recv返回的是错误代码的负数,所以需要调转过来
  int nErrorCode = -nRecvBytes;

  if ( nErrorCode != WSAEWOULDBLOCK ) //太多的未完成重叠操作
  {
   CloseHandle(hReadEvent);
   SetLastError( nErrorCode );
   return (SOCKET_ERROR);
  }
  //阻塞住了
  Sleep(_BLOCKED_SNDRCV_SLEEP);
    }
    CloseHandle(hReadEvent);
 printf("socket[%d] out RecvData/r/n", hSocket);
    return (nRecvBytes);
}

//发送数据,阻塞
//入口:套接字,发送的字串,字串长度,超时值
int CSockObj::Send_Block(SOCKET hSocket,char const * pszBuffer,
  int nBufferSize, DWORD dwTimeout)
{
 if (hSocket==INVALID_SOCKET || pszBuffer==NULL)
  return (SOCKET_ERROR);

 FD_SET fd = {1, hSocket};
 TIMEVAL tv = {dwTimeout, 0};
 int nBytesSent=0;
 if(select(0, NULL, &fd, NULL, &tv) == 0)
  goto CLEAR;//选择发送超时
 if((nBytesSent = send(hSocket, pszBuffer, nBufferSize, 0)) == SOCKET_ERROR)
  goto CLEAR;//发送出错误
 return nBytesSent;

CLEAR:
 SetLastError(WSAGetLastError());//超时
 return(SOCKET_ERROR);
}

//发送全部缓冲区中数据,阻塞
//入口:套接字,发送的字串,字串长度,超时值
//出口:正确返回发送的字节数量,错误返回SOCKET_ERROR
int CSockObj::SendData_Block(SOCKET hSocket,char const * pszBuffer,
  int nBufferSize, DWORD dwTimeout)

{
 if (hSocket==INVALID_SOCKET || pszBuffer==NULL)
  return (SOCKET_ERROR);

 int nBytesSent = 0;
 int nBytesThisTime;
 const char* pszTemp = pszBuffer;
 do {
  nBytesThisTime = Send_Block(hSocket,pszTemp, nBufferSize-nBytesSent, dwTimeout);
  if(nBytesThisTime<0)
   return(SOCKET_ERROR);
  //如果一次没有发送成功
  nBytesSent += nBytesThisTime;
  //改变当前字符指针
  pszTemp += nBytesThisTime;
 } while(nBytesSent < nBufferSize);
 return nBytesSent;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值