编写 Windows Socket TCP 客户端其实并不困难,Windows 提供了6种 I/O 通信模型供大家选择。但本座看过很多客户端程序都把 Socket 通信和业务逻辑混在一起,剪不断理还乱。每个程序都 Copy / Parse 类似的代码再进行修改,实在有点情何以堪。因此本座利用一些闲暇时光写了一个基于 IOCP 的通用异步 Windows Socket TCP 高性能服务端组件和一个通用异步 Windows Socket TCP 客户端组件供各位看官参详参详,希望能激发下大家的灵感。本篇文章讲述客户端组件。闲话少说,我们现在步入正题。

  • 最重要的第一个问题:如何才能达到通用?

  答:很简单。

    1、限制组件的职能,说白了,通信组件的唯一职责就是接受和发送字节流,绝对不能参与上层协议解析等工作。不在其位不谋其政就是这个意思。

    2、与上层使用者解耦、互不依赖,组件与使用者通过接口方法进行交互,组件实现 ISocketClient 接口为上层提供操作方法;使用者通过 IClientSocketListener 接口把自己注册为组件的 Listener,接收组件通知。因此,任何使用者只要实现了 IClientSocketListener 接口都可以使用组件;另一方面,你甚至可以自己重新写一个实现方式完全不同的组件实现给使用者调用,只要该组件遵从 ISocketClient 接口。这也是 DIP 设计原则的体现(若想了解更多关于设计原则的内容请猛击这里 ^_^)。

  • 最重要的第二个问题:可用性如何,也就是说使用起来是否是否方便?

  答:这个问题问得很好,可用性对所有通用组件都是至关重要的,如果太难用还不如自己重头写一个来得方便。因此,ISocketClient 和 IClientSocketListener 接口设计得尽量简单易用(通俗来说就是“傻瓜化”),这两个接口的主要方法均不超过 5 个。

  • 最重要的第三个问题:组件的性能如何?

  作为底层的通用组件,性能问题是必须考虑的,绝对不能成为系统的瓶颈。而另一方面,从实际出发,毕竟只是一个客户端组件,它的并发性要求远没有服务端那么高。因此,组件在设计上充分考虑了性能、现实使用情景、可用性和实现复杂性等因素,确保满足性能要求的同时又不会写得太复杂。做出以下两点设计决策:

    1. 在单独线程中实现 Socket 通信交互。这样可以避免与主线程或其他线程相互干扰。
    2. I/O 模型选择 WSAEventSelect。细说一下选择这种 I/O 模型的原因:(各种 I/O 模型的性能比较可以参考:《Windows 网络编程(中文第二版)》第 154 页)
      • 阻塞模型:(不解析,你懂的^_^)
      • 非阻塞模型:(性能太低)
      • WSAAsyncSelect: (两个原因:a、性能太低;b、对于纯 Console 程序还要背负 HWND 实在是伤不起呀!)
      • 重叠 I/O:(有点复杂了)
      • 完成端口:(何必呢?)

  唉,理论的东西就先别吹那么多了,直接上代码吧,求你了 !!

  OK!先看看 ISocketClient 和 IClientSocketListener 的接口定义:

// 组件操作类型
enum EnSocketOperation
{
SO_UNKNOWN = 0,
SO_ACCEPT = 1,
SO_CONNECT = 2,
SO_SEND = 3,
SO_RECEIVE = 4,
}; // 组件监听器基接口
class ISocketListener
{
public:
enum EnHandleResult
{
HR_OK = 0,
HR_IGNORE = 1,
HR_ERROR = 2,
}; public:
  // 已发出数据通知
  virtual EnHandleResult OnSend(DWORD dwConnectionID, const BYTE* pData, int iLength) = 0;

  // 已接收数据通知
  virtual EnHandleResult OnReceive(DWORD dwConnectionID, const BYTE* pData, int iLength) = 0;

  // 关闭连接通知
  virtual EnHandleResult OnClose(DWORD dwConnectionID) = 0;

  // 通信错误通知
  virtual EnHandleResult OnError(DWORD dwConnectionID, EnSocketOperation enOperation, int iErrorCode) = 0;
public:
virtual ~ISocketListener() {}
}; // 服务端组件监听器接口(暂时无视之)
class IServerSocketListener : public ISocketListener
{
public:
// 接收连接通知
virtual EnHandleResult OnAccept(DWORD dwConnectionID) = 0;
// 服务关闭通知
virtual EnHandleResult OnServerShutdown() = 0;
}; // 客户端组件监听器接口
class IClientSocketListener : public ISocketListener
{
public:
  // 连接完成通知
  virtual
EnHandleResult OnConnect(DWORD dwConnectionID) = 0;
}; // 服务端组件接口(暂时无视之)
class ISocketServer
{
public:
enum En_ISS_Error
{
ISS_OK = 0,
ISS_SOCKET_CREATE = 1,
ISS_SOCKET_BIND = 2,
ISS_SOCKET_LISTEN = 3,
ISS_CP_CREATE = 4,
ISS_WORKER_THREAD_CREATE = 5,
ISS_SOCKE_ATTACH_TO_CP = 6,
ISS_ACCEPT_THREAD_CREATE = 7,
}; public:
virtual BOOL Start (LPCTSTR pszBindAddress, USHORT usPort, long lThreadCount) = 0;
virtual BOOL Stop () = 0;
virtual BOOL Send (DWORD dwConnID, const BYTE* pBuffer, int iLen) = 0;
virtual BOOL HasStarted () = 0;
virtual En_ISS_Error GetLastError () = 0;
virtual LPCTSTR GetLastErrorDesc() = 0;
virtual BOOL GetConnectionAddress(DWORD dwConnID, CString& strAddress, USHORT& usPort) = 0; public:
virtual ~ISocketServer() {}
}; // 服务端组件接口智能指针
typedef auto_ptr<ISocketServer> ISocketServerPtr; // 客户端组件接口
class ISocketClient
{
public:
// 操作结果码
enum En_ISC_Error
{
ISC_OK = 0,
ISC_CLIENT_HAD_STARTED = 1,
ISC_CLIENT_NOT_STARTED = 2,
ISC_SOCKET_CREATE_FAIL = 3,
ISC_CONNECT_SERVER_FAIL = 4,
ISC_WORKER_CREATE_FAIL = 5,
ISC_NETWORK_ERROR = 6,
ISC_PROTOCOL_ERROR = 7,
}; public:
  // 启动通信
  virtual BOOL Start (LPCTSTR pszRemoteAddress, USHORT usPort) = 0;

  // 关闭通信
  virtual BOOL Stop () = 0;

  // 发送数据
  virtual BOOL Send (DWORD dwConnID, const BYTE* pBuffer, int iLen) = 0;

  // 是否已启动
  virtual BOOL HasStarted () = 0;

  // 获取错误码
  virtual En_ISC_Error GetLastError () = 0;

  // 获取错误描述
  virtual LPCTSTR GetLastErrorDesc() = 0;
public:
virtual ~ISocketClient() {}
}; // 客户端组件接口智能指针
typedef auto_ptr<ISocketClient>
ISocketClientPtr;

  

  ISocketClient 接口主要有以下三个方法:

  • Start():启动通信
  • Send():发送数据
  • Stop():停止通信

  IClientSocketListener 接口有以下五个通知方法:

  • OnConnect()
  • OnSend()
  • OnReceive()
  • OnClose()
  • OnError()

  够简单了吧^_^,使用者只需通过三个方法操作组件,然后处理五个组件通知。下面我们再看看组件的具体实现,先看组件类定义:

/* 组件实现类 */
class CSocketClient : public ISocketClient
{
// ISocketClient 接口方法
public:
  virtual BOOL Start (LPCTSTR pszRemoteAddress, USHORT usPortt);
  virtual BOOL Stop ();
  virtual BOOL Send (DWORD dwConnID, const BYTE* pBuffer, int iLen);
  virtual BOOL HasStarted () {return m_bStarted;}
  virtual En_ISC_Error GetLastError () {return sm_enLastError;}
  virtual LPCTSTR GetLastErrorDesc(); private:
BOOL CreateClientSocket();
BOOL ConnectToServer(LPCTSTR pszRemoteAddress, USHORT usPort);
BOOL CreateWorkerThread();
// 网络事件处理方法
BOOL ProcessNetworkEvent();
void WaitForWorkerThreadEnd();
BOOL ReadData();
BOOL SendData(); void SetLastError(En_ISC_Error code, LPCTSTR func, int ec); // 通信线程函数
static
#ifndef _WIN32_WCE
UINT
#else
DWORD
#endif
WINAPI WorkerThreadProc(LPVOID pv); private:
static const int RECEIVE_BUFFER_SIZE = 8 * 1024;
static const int WORKER_THREAD_END_TIME = 3 * 1000; static const long DEFALUT_KEEPALIVE_TIMES = 3;
static const long DEFALUT_KEEPALIVE_INTERVAL = 10 * 1000; // 构造函数
public:

  CSocketClient(IClientSocketListener* pListener)
  : m_pListener(pListener) // 设置监听器对象
  , m_soClient(INVALID_SOCKET)

  , m_evSocket(NULL)
  , m_dwConnID(0)
  , m_hWorker(NULL)
  , m_dwWorkerID(0)
  , m_bStarted(FALSE)
#ifdef _WIN32_WCE
  , sm_enLastError(ISC_OK)
#endif
  {
    ASSERT(m_pListener);
  } virtual ~CSocketClient() {if(HasStarted()) Stop();} private:
  // 这是神马 ???
  CInitSocket m_wsSocket;
SOCKET m_soClient;
HANDLE m_evSocket;
DWORD m_dwConnID; CCriSec m_scStop;
CEvt m_evStop;
HANDLE m_hWorker; #ifndef _WIN32_WCE
UINT
#else
DWORD
#endif
m_dwWorkerID; CBufferPtr m_sndBuffer;
CCriSec m_scBuffer;
CEvt m_evBuffer; volatile BOOL m_bStarted; private:
  // 监听器对象指针
  IClientSocketListener* m_pListener; #ifndef _WIN32_WCE
__declspec(thread) static En_ISC_Error sm_enLastError;
#else
volatile En_ISC_Error sm_enLastError;
#endif
};

  从上面的定义可以看出,组件实现类本身并没有提供额外的公共方法,它完全是可以被替换的。组件在构造函数中接收监听器对象,并且保存为其成员属性,因此可以在需要的时候向监听器发送事件通知。

  另外,不知各位看官是否注意到一个奇怪的成员属性:“CInitSocket m_wsSocket; ”,这个属性在其它地方从来都不会用到,那么它是干嘛的呢?在回答这个问题之前,首先想问问大家:Windows Socket 操作的整个操作过程中,第一个以及最后一个被调用的方法是什么?是 socket()、connect()、bind()、还是 closesocket() 吗?都错!答案是 —— ::WSAStartup() 和 ::WSACleanup()。每个程序都要调用一下这两个方法确实是很烦的,又不雅观。 其实,m_wsSocket 的唯一目的就是为了避免手工调用者两个方法,看看它的定义就明白了:

class CInitSocket
{
public:
CInitSocket(LPWSADATA lpWSAData = NULL, BYTE minorVersion = 2, BYTE majorVersion = 2)
{
LPWSADATA lpTemp = lpWSAData;
if(!lpTemp)
lpTemp = (LPWSADATA)_alloca(sizeof(WSADATA)); m_iResult = ::WSAStartup(MAKEWORD(minorVersion, majorVersion), lpTemp);
} ~CInitSocket()
{
if(IsValid())
      ::WSACleanup();
} int GetResult() {return m_iResult;}
BOOL IsValid() {return m_iResult == 0;} private:
int m_iResult;
};

  现在我们看看组件类实现文件中几个重要方法的定义:

// 组件事件触发宏定义
#define FireConnect(id) m_pListener->OnConnect(id)
#define FireSend(id, data, len) (m_bStarted ? m_pListener->OnSend(id, data, len) : ISocketListener::HR_IGNORE)
#define FireReceive(id, data, len) (m_bStarted ? m_pListener->OnReceive(id, data, len) : ISocketListener::HR_IGNORE)
#define FireClose(id) (m_bStarted ? m_pListener->OnClose(id) : ISocketListener::HR_IGNORE)
#define FireError(id, op, code) (m_bStarted ? m_pListener->OnError(id, op, code) : ISocketListener::HR_IGNORE) // 启动组件
BOOL CSocketClient::Start(LPCTSTR pszRemoteAddress, USHORT usPort)
{
BOOL isOK = FALSE; if(HasStarted())
{
SetLastError(ISC_CLIENT_HAD_STARTED, _T(__FUNCTION__), 0);
return isOK;
} // 创建 socket
if(CreateClientSocket())
{
// 连接服务器(内部会调用 FireConnect()
if(ConnectToServer(pszRemoteAddress, usPort))
{
// 创建工作线程
if(CreateWorkerThread())
isOK = TRUE;
else
SetLastError(ISC_WORKER_CREATE_FAIL, _T(__FUNCTION__), 0);
}
else
SetLastError(ISC_CONNECT_SERVER_FAIL, _T(__FUNCTION__), ::WSAGetLastError());
}
else
SetLastError(ISC_SOCKET_CREATE_FAIL, _T(__FUNCTION__), ::WSAGetLastError()); isOK ? m_bStarted = TRUE : Stop(); return isOK;
} // 关闭组件
BOOL CSocketClient::Stop()
{
{
CCriSecLock locallock(m_scStop); m_bStarted = FALSE; if(m_hWorker != NULL)
{
// 停止工作线程
if(::GetCurrentThreadId() != m_dwWorkerID)
WaitForWorkerThreadEnd(); ::CloseHandle(m_hWorker);
m_hWorker = NULL;
m_dwWorkerID = 0;
} if(m_evSocket != NULL)
{
// 关闭 WSAEvent
::WSACloseEvent(m_evSocket);
m_evSocket = NULL;
} if(m_soClient != INVALID_SOCKET)
{
// 关闭socket
shutdown(m_soClient, SD_SEND);
closesocket(m_soClient);
m_soClient = INVALID_SOCKET;
} m_dwConnID = 0;
} // 释放其它资源
m_sndBuffer.Free();
m_evBuffer.Reset();
m_evStop.Reset(); return TRUE;
} // 发送数据
BOOL CSocketClient::Send(DWORD dwConnID, const BYTE* pBuffer, int iLen)
{
ASSERT(iLen > 0); if(!HasStarted())
{
SetLastError(ISC_CLIENT_NOT_STARTED, _T(__FUNCTION__), 0);
return FALSE;
} CCriSecLock locallock(m_scBuffer); // 把数据存入缓冲器
m_sndBuffer.Cat(pBuffer, iLen);
  // 唤醒工作现场,发送数据
m_evBuffer.Set(); return TRUE;
} // 工作线程函数
#ifndef _WIN32_WCE
UINT
#else
DWORD
#endif
WINAPI CSocketClient::WorkerThreadProc(LPVOID pv)
{
CSocketClient* pClient = (CSocketClient*)pv; TRACE0("---------------> 启动工作线程 <---------------\n"); HANDLE hEvents[] = {pClient->m_evSocket, pClient->m_evBuffer, pClient->m_evStop}; while(pClient->HasStarted())
{
// 等待 socket 事件、发送数据事件和停止通信事件
DWORD retval = ::MsgWaitForMultipleObjectsEx(3, hEvents, WSA_INFINITE, QS_ALLINPUT, MWMO_INPUTAVAILABLE); if(retval == WSA_WAIT_EVENT_0)
{
// 处理网络消息
if(!pClient->ProcessNetworkEvent())
{
if(pClient->HasStarted())
pClient->Stop(); break;
}
}
else if(retval == WSA_WAIT_EVENT_0 + 1)
{
// 发送数据(内部调用 FireSend()
if(!pClient->SendData())
{
if(pClient->HasStarted())
pClient->Stop(); break;
}
}
else if(retval == WSA_WAIT_EVENT_0 + 2)
break;
else if(retval == WSA_WAIT_EVENT_0 + 3)
// 消息循环
::PeekMessageLoop();
else
ASSERT(FALSE);
} TRACE0("---------------> 退出工作线程 <---------------\n"); return 0;
} // 处理网络消息
BOOL CSocketClient::ProcessNetworkEvent()
{
::WSAResetEvent(m_evSocket); WSANETWORKEVENTS events; int rc = ::WSAEnumNetworkEvents(m_soClient, m_evSocket, &events); if(rc == SOCKET_ERROR)
{
int code = ::WSAGetLastError();
SetLastError(ISC_NETWORK_ERROR, _T(__FUNCTION__), code);
     FireError(m_dwConnID, SO_UNKNOWN, code); return FALSE;
} /* 可读取 */
if(events.lNetworkEvents & FD_READ)
{
int iCode = events.iErrorCode[FD_READ_BIT]; if(iCode == 0)
// 读取数据(内部调用 FireReceive()
return ReadData();
else
     {
      SetLastError(ISC_NETWORK_ERROR, _T(__FUNCTION__), iCode);
      FireError(m_dwConnID, SO_RECEIVE, iCode);
      return FALSE;
}
} /* 可发送 */
if(events.lNetworkEvents & FD_WRITE)
{
int iCode = events.iErrorCode[FD_WRITE_BIT]; if(iCode == 0)
// 发送数据(内部调用 FireSend()
return SendData();
else
{
SetLastError(ISC_NETWORK_ERROR, _T(__FUNCTION__), iCode);
        FireError(m_dwConnID, SO_SEND, iCode);
return FALSE;
}
} /* socket 已关闭 */
if(events.lNetworkEvents & FD_CLOSE)
{
int iCode = events.iErrorCode[FD_CLOSE_BIT]; if(iCode == 0)
      FireClose(m_dwConnID);
else
{
SetLastError(ISC_NETWORK_ERROR, _T(__FUNCTION__), iCode);
        FireError(m_dwConnID, SO_UNKNOWN, iCode);
} return FALSE;
} return TRUE;
}

  

  从上面的代码可以看出:通信过程中,组件的使用者不需要对通信过程进行任何干预,整个底层通信过程对使用者来说是透明的,使用只需集中精力处理好几个组件通知。下面来看看组件的一个使用示例:

/* 组件使用者:实现 IClientSocketListener */
class CMainClient : public IClientSocketListener
{
// 这些方法会操作组件
public:
bool Login(LPCTSTR pszAddress, USHORT usPort, const T_101_Data* pData);
bool Logout(const T_201_Data* pData);
BOOL SendData(EnCommandType enCmdType, const TCommandData* pCmdData, WORD wCmdDataLen);
long GetLastError();
LPCTSTR GetLastErrorDesc(); // 实现 IClientSocketListener
public:
  virtual EnHandleResult OnConnect(DWORD dwConnectionID);
  virtual EnHandleResult OnSend(DWORD dwConnectionID, const BYTE* pData, int iLength);
  virtual EnHandleResult OnReceive(DWORD dwConnectionID, const BYTE* pData, int iLen);
  virtual EnHandleResult OnClose(DWORD dwConnectionID);
  virtual EnHandleResult OnError(DWORD dwConnectionID, EnSocketOperation enOperation, int iErrorCode); private:
BOOL ParseReceiveBuffer(); // 其它方法 。。。 // 构造函数
public:
  CMainClient()
  // 创建组件,并把自己设置为组件的监听器
  : m_pscClient(new CSocketClient(this))

  , m_dwConnID(0)
  {
  } virtual ~CMainClient() {} private:
  // 组件属性
  ISocketClientPtr m_pscClient;
DWORD m_dwConnID; // 其它属性 。。。
};
BOOL CMainClient::Login(LPCTSTR pszAddress, USHORT usPort, const T_101_Data* pData)
{
// 启动通信
return m_pscClient->Start(pszAddress, usPort) &&
SendData(CS_C_LOGIN_REQ, pData, sizeof(T_101_Data));
} BOOL CMainClient::Logout(const T_201_Data* pData)
{
if(pData)
{
SendData(CS_C_SET_STATUS, pData, sizeof(T_201_Data));
::WaitWithMessageLoop(LOGOUT_WAIT_TIME);
} // 停止通信
return m_pscClient->Stop();
} BOOL CMainClient::SendData(EnCommandType enCmdType, const TCommandData* pCmdData, WORD wCmdDataLen)
{
const WORD wBufferLen = CMD_ADDITIVE_SIZE + wCmdDataLen;
CPrivateHeapByteBuffer buffer(m_hpPrivate, wBufferLen);
BYTE* pBuffer = buffer; memcpy(pBuffer, &wBufferLen, CMD_LEN_SIZE);
pBuffer += CMD_LEN_SIZE;
memcpy(pBuffer, &enCmdType, CMD_TYPE_SIZE);
pBuffer += CMD_TYPE_SIZE;
memcpy(pBuffer, pCmdData, wCmdDataLen);
pBuffer += wCmdDataLen;
memcpy(pBuffer, &CMD_FLAG, CMD_FLAG_SIZE); // 发送数据
return m_pscClient->Send(m_dwConnID, buffer, wBufferLen);
} long CMainClient::GetLastError()
{
// 获取通信错误码
return m_pscClient->GetLastError();
} LPCTSTR CMainClient::GetLastErrorDesc()
{
// 获取通信错误描述
return m_pscClient->GetLastErrorDesc();
} /* 处理连接成功事件 */
ISocketListener::EnHandleResult CMainClient::OnConnect(DWORD dwConnectionID)
{
TRACE1("<CNNID: %d> 已连接\n", dwConnectionID);
m_dwConnID = dwConnectionID;
return HR_OK;
} /* 处理数据已发出事件 */
ISocketListener::EnHandleResult CMainClient::OnSend(DWORD dwConnectionID, const BYTE* pData, int iLength)
{
TRACE2("<CNNID: %d> 发出数据包 (%d bytes)\n", dwConnectionID, iLength);
return HR_OK;
} /* 处理接收到数据事件*/
ISocketListener::EnHandleResult CMainClient::OnReceive(DWORD dwConnectionID, const BYTE* pData, int iLen)
{
TRACE2("<CNNID: %d> 接收数据包 (%d bytes)\n", dwConnectionID, iLen); ASSERT(pData != NULL && iLen > 0); // 保存数据
m_rcBuffer.Cat(pData, iLen); // 解析数据
return ParseReceiveBuffer() ? HR_OK : HR_ERROR;;
} /* 处理通信关闭事件*/
ISocketListener::EnHandleResult CMainClient::OnClose(DWORD dwConnectionID)
{
TRACE1("CNNID: %d> 关闭连接\n", dwConnectionID); // 清理缓冲区
m_rcBuffer.Realloc(0);
return HR_OK;
} /* 处理通信错误事件 */
ISocketListener::EnHandleResult CMainClient::OnError(DWORD dwConnectionID, EnSocketOperation enOperation, int iErrorCode)
{
TRACE3("<CNNID: %d> 网络错误 (OP: %d, CO: %d)\n", dwConnectionID, enOperation, iErrorCode); // 清理缓冲区
m_rcBuffer.Realloc(0); return HR_OK;
}

  好了,码了一个晚上的字,累啊!到此为止吧,感谢收看~

  敬请继续收看《基于 IOCP 的通用异步 Windows Socket TCP 高性能服务端组件的设计与实现》,晚安 ^_^

  (想看源代码的朋友请狂点这里

通用异步 Windows Socket TCP 客户端组件的设计与实现的更多相关文章

  1. 基于 IOCP 的通用异步 Windows Socket TCP 高性能服务端组件的设计与实现

    设计概述 服务端通信组件的设计是一项非常严谨的工作,其中性能.伸缩性和稳定性是必须考虑的硬性质量指标,若要把组件设计为通用组件提供给多种已知或未知的上层应用使用,则设计的难度更会大大增加,通用性.可用 ...

  2. 通用高性能 Windows Socket 组件 HP-Socket v2.2.2 正式发布

    HP-Socket 是一套通用的高性能 Windows Socket 组件包,包含服务端组件(IOCP 模型)和客户端组件(Event Select 模型),广泛适用于 Windows 平台的 TCP ...

  3. 通用高性能 Windows Socket 组件 HP-Socket v2.2.2 更新发布

    HP-Socket 是一套通用的高性能 Windows Socket 组件包,包含服务端组件(IOCP 模型)和客户端组件(Event Select 模型),广泛适用于 Windows 平台的 TCP ...

  4. cross socket tcp客户端开发

    cross socket tcp客户端开发 uses Net.SocketAPI, Net.CrossSocket.Base, Net.CrossSocket FCrossTcp: ICrossSoc ...

  5. socket | tcp客户端 tcp服务器 udp客户端 udp 服务器 创建方法

    tcp服务器 #coding=utf-8 ''' 这里是tcp服务器端,要先启动 ''' import socket import threading bind_ip = "0.0.0.0& ...

  6. Socket TCP客户端和服务器的实现

    import java.io.*; import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.S ...

  7. 高性能 Windows Socket 组件 HP-Socket v2.2.3 正式发布

    HP-Socket 是一套通用的高性能 Windows Socket 组件包,包含服务端组件(IOCP 模型)和客户端组件(Event Select 模型),广泛适用于 Windows 平台的 TCP ...

  8. Windows Socket 组件 HP-Socket v2.2.3

    高性能 Windows Socket 组件 HP-Socket v2.2.3 正式发布 HP-Socket 是一套通用的高性能 Windows Socket 组件包,包含服务端组件(IOCP 模型)和 ...

  9. 高性能 Windows Socket 组件 HP-Socket v3.0.2 正式发布

    HP-Socket 是一套通用的高性能 Windows Socket 组件包,包含服务端组件(IOCP 模型)和客户端组件(Event Select 模型),广泛适用于 Windows 平台的 TCP ...

随机推荐

  1. FastDFS介绍和搭建(转载)

    FastDFS介绍和配置过程--http://blog.51cto.com/ylw6006/948729 FastDFS的五篇文章--http://www.cnblogs.com/smartycity ...

  2. 【Java TCP/IP Socket】基于线程池的TCP服务器(含代码)

    了解线程池 在http://blog.csdn.net/ns_code/article/details/14105457(读书笔记一:TCP Socket)这篇博文中,服务器端采用的实现方式是:一个客 ...

  3. spring启动时加载字典表数据放入map

    import java.util.HashMap; import java.util.List; import org.springframework.beans.factory.annotation ...

  4. 【面试 JVM】【第六篇】JVM调优

    六部分内容: 一.内存模型 1.程序计数器,方法区,堆,栈,本地方法栈的作用,保存那些数据 可以画个大图出来,很清晰 jvm内存模型主要指运行时的数据区,包括5个部分. 栈也叫方法栈,是线程私有的,线 ...

  5. MyReport报表引擎1.2.0.1新功能

    一维码(Code128B)转换显示.  多联标题. 修正BugSum统计函数问题报表编辑器保存时没有生成新加入的单元格相关的xml数据 相关链接MyReport演示.产品站点 相关文章MyReport ...

  6. Dynamics CRM 2015/2016 Web API:新的数据查询方式

    今天我们来看看Web API的数据查询功能,尽管之前介绍CRUD的文章里面提到过怎么去Read数据,可是并没有详细的去深究那些细节,今天我们就来详细看看吧.事实上呢,Web API的数据查询接口也是基 ...

  7. Katalon

    Katalon---一款好用的selenium自动化测试插件 selenium框架是目前使用较广泛的开源自动化框架,一款好的.基于界面的录制工具对于初学者来说可以快速入门:对于老手来说可以提高开发自动 ...

  8. 菜鸟系列之C/C++经典试题(三)

    设计包括min函数的栈 题目:定义栈的数据结构,要求加入一个min函数,可以得到栈的最小元素.要求函数min.push以及pop的时间复杂度都是O(1). 分析:这是2006年google的一道面试题 ...

  9. ArcGIS教程:分水岭

    摘要 确定栅格中一组像元之上的汇流区域. 使用方法 · 各个分水岭的值将取自输入栅格中源的值或者要素倾泻点数据.假设倾泻点为栅格数据集,则使用像元值.假设倾泻点为点要素数据集,则从指定的字段中获取值. ...

  10. Apache Qpid Broker云

    一.     什么是Broker云 Apathe Qpid 支持Broker Federation ,也就是Broker联盟或者叫做Broker云.Broker Federation可以通过配置消息路 ...