原文链接:http://www.cnblogs.com/lancidie/archive/2013/04/13/3019359.html

头文件:

#pragma once

#ifdef WIN32
#include <windows.h>
#include <WinSock.h>
#else
#include <sys/socket.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h> #define SOCKET int
#define SOCKET_ERROR -1
#define INVALID_SOCKET -1 #endif #ifndef CHECKF
#define CHECKF(x) \
do \
{ \
if (!(x)) { \
log_msg("CHECKF", #x, __FILE__, __LINE__); \
return ; \
} \
} while ()
#endif #define _MAX_MSGSIZE 16 * 1024 // 暂定一个消息最大为16k
#define BLOCKSECONDS 30 // INIT函数阻塞时间
#define INBUFSIZE (64*1024) //? 具体尺寸根据剖面报告调整 接收数据的缓存
#define OUTBUFSIZE (8*1024) //? 具体尺寸根据剖面报告调整。 发送数据的缓存,当不超过8K时,FLUSH只需要SEND一次 class CGameSocket {
public:
CGameSocket(void);
bool Create(const char* pszServerIP, int nServerPort, int nBlockSec = BLOCKSECONDS, bool bKeepAlive = false);
bool SendMsg(void* pBuf, int nSize);
bool ReceiveMsg(void* pBuf, int& nSize);
bool Flush(void);
bool Check(void);
void Destroy(void);
SOCKET GetSocket(void) const { return m_sockClient; }
private:
bool recvFromSock(void); // 从网络中读取尽可能多的数据
bool hasError(); // 是否发生错误,注意,异步模式未完成非错误
void closeSocket(); SOCKET m_sockClient; // 发送数据缓冲
char m_bufOutput[OUTBUFSIZE]; //? 可优化为指针数组
int m_nOutbufLen; // 环形缓冲区
char m_bufInput[INBUFSIZE];
int m_nInbufLen;
int m_nInbufStart; // INBUF使用循环式队列,该变量为队列起点,0 - (SIZE-1)
};

.cpp文件

    #include "stdafx.h"
#include "Socket.h" CGameSocket::CGameSocket()
{
// 初始化
memset(m_bufOutput, , sizeof(m_bufOutput));
memset(m_bufInput, , sizeof(m_bufInput));
} void CGameSocket::closeSocket()
{
#ifdef WIN32
closesocket(m_sockClient);
WSACleanup();
#else
close(m_sockClient);
#endif
} bool CGameSocket::Create(constchar* pszServerIP, int nServerPort, int nBlockSec, bool bKeepAlive /*= FALSE*/)
{
// 检查参数
if(pszServerIP == || strlen(pszServerIP) > ) {
returnfalse;
} #ifdef WIN32
WSADATA wsaData;
WORD version = MAKEWORD(, );
int ret = WSAStartup(version, &wsaData);//win sock start up
if (ret != ) {
returnfalse;
}
#endif // 创建主套接字
m_sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(m_sockClient == INVALID_SOCKET) {
closeSocket();
returnfalse;
} // 设置SOCKET为KEEPALIVE
if(bKeepAlive)
{
int optval=;
if(setsockopt(m_sockClient, SOL_SOCKET, SO_KEEPALIVE, (char *) &optval, sizeof(optval)))
{
closeSocket();
returnfalse;
}
} #ifdef WIN32
DWORD nMode = ;
int nRes = ioctlsocket(m_sockClient, FIONBIO, &nMode);
if (nRes == SOCKET_ERROR) {
closeSocket();
returnfalse;
}
#else
// 设置为非阻塞方式
fcntl(m_sockClient, F_SETFL, O_NONBLOCK);
#endif unsigned long serveraddr = inet_addr(pszServerIP);
if(serveraddr == INADDR_NONE) // 检查IP地址格式错误
{
closeSocket();
returnfalse;
} sockaddr_in addr_in;
memset((void *)&addr_in, , sizeof(addr_in));
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(nServerPort);
addr_in.sin_addr.s_addr = serveraddr; if(connect(m_sockClient, (sockaddr *)&addr_in, sizeof(addr_in)) == SOCKET_ERROR) {
if (hasError()) {
closeSocket();
returnfalse;
}
else // WSAWOLDBLOCK
{
timeval timeout;
timeout.tv_sec = nBlockSec;
timeout.tv_usec = ;
fd_set writeset, exceptset;
FD_ZERO(&writeset);
FD_ZERO(&exceptset);
FD_SET(m_sockClient, &writeset);
FD_SET(m_sockClient, &exceptset); int ret = select(FD_SETSIZE, NULL, &writeset, &exceptset, &timeout);
if (ret == || ret < ) {
closeSocket();
returnfalse;
} else // ret > 0
{
ret = FD_ISSET(m_sockClient, &exceptset);
if(ret) // or (!FD_ISSET(m_sockClient, &writeset)
{
closeSocket();
returnfalse;
}
}
}
} m_nInbufLen = ;
m_nInbufStart = ;
m_nOutbufLen = ; struct linger so_linger;
so_linger.l_onoff = ;
so_linger.l_linger = ;
setsockopt(m_sockClient, SOL_SOCKET, SO_LINGER, (constchar*)&so_linger, sizeof(so_linger)); returntrue;
} bool CGameSocket::SendMsg(void* pBuf, int nSize)
{
if(pBuf == || nSize <= ) {
returnfalse;
} if (m_sockClient == INVALID_SOCKET) {
returnfalse;
} // 检查通讯消息包长度
int packsize = ;
packsize = nSize; // 检测BUF溢出
if(m_nOutbufLen + nSize > OUTBUFSIZE) {
// 立即发送OUTBUF中的数据,以清空OUTBUF。
Flush();
if(m_nOutbufLen + nSize > OUTBUFSIZE) {
// 出错了
Destroy();
returnfalse;
}
}
// 数据添加到BUF尾
memcpy(m_bufOutput + m_nOutbufLen, pBuf, nSize);
m_nOutbufLen += nSize;
returntrue;
} bool CGameSocket::ReceiveMsg(void* pBuf, int& nSize)
{
//检查参数
if(pBuf == NULL || nSize <= ) {
returnfalse;
} if (m_sockClient == INVALID_SOCKET) {
returnfalse;
} // 检查是否有一个消息(小于2则无法获取到消息长度)
if(m_nInbufLen < ) {
// 如果没有请求成功 或者 如果没有数据则直接返回
if(!recvFromSock() || m_nInbufLen < ) { // 这个m_nInbufLen更新了
returnfalse;
}
} // 计算要拷贝的消息的大小(一个消息,大小为整个消息的第一个16字节),因为环形缓冲区,所以要分开计算
int packsize = (unsigned char)m_bufInput[m_nInbufStart] +
(unsigned char)m_bufInput[(m_nInbufStart + ) % INBUFSIZE] * ; // 注意字节序,高位+低位 // 检测消息包尺寸错误 暂定最大16k
if (packsize <= || packsize > _MAX_MSGSIZE) {
m_nInbufLen = ; // 直接清空INBUF
m_nInbufStart = ;
returnfalse;
} // 检查消息是否完整(如果将要拷贝的消息大于此时缓冲区数据长度,需要再次请求接收剩余数据)
if (packsize > m_nInbufLen) {
// 如果没有请求成功 或者 依然无法获取到完整的数据包 则返回,直到取得完整包
if (!recvFromSock() || packsize > m_nInbufLen) { // 这个m_nInbufLen已更新
returnfalse;
}
} // 复制出一个消息
if(m_nInbufStart + packsize > INBUFSIZE) {
// 如果一个消息有回卷(被拆成两份在环形缓冲区的头尾)
// 先拷贝环形缓冲区末尾的数据
int copylen = INBUFSIZE - m_nInbufStart;
memcpy(pBuf, m_bufInput + m_nInbufStart, copylen); // 再拷贝环形缓冲区头部的剩余部分
memcpy((unsigned char *)pBuf + copylen, m_bufInput, packsize - copylen);
nSize = packsize;
} else {
// 消息没有回卷,可以一次拷贝出去
memcpy(pBuf, m_bufInput + m_nInbufStart, packsize);
nSize = packsize;
} // 重新计算环形缓冲区头部位置
m_nInbufStart = (m_nInbufStart + packsize) % INBUFSIZE;
m_nInbufLen -= packsize;
return true;
} bool CGameSocket::hasError()
{
#ifdef WIN32
int err = WSAGetLastError();
if(err != WSAEWOULDBLOCK) {
#else
int err = errno;
if(err != EINPROGRESS && err != EAGAIN) {
#endif
returntrue;
} returnfalse;
} // 从网络中读取尽可能多的数据,实际向服务器请求数据的地方
bool CGameSocket::recvFromSock(void)
{
if (m_nInbufLen >= INBUFSIZE || m_sockClient == INVALID_SOCKET) {
returnfalse;
} // 接收第一段数据
int savelen, savepos; // 数据要保存的长度和位置
if(m_nInbufStart + m_nInbufLen < INBUFSIZE) { // INBUF中的剩余空间有回绕
savelen = INBUFSIZE - (m_nInbufStart + m_nInbufLen); // 后部空间长度,最大接收数据的长度
} else {
savelen = INBUFSIZE - m_nInbufLen;
} // 缓冲区数据的末尾
savepos = (m_nInbufStart + m_nInbufLen) % INBUFSIZE;
CHECKF(savepos + savelen <= INBUFSIZE);
int inlen = recv(m_sockClient, m_bufInput + savepos, savelen, );
if(inlen > ) {
// 有接收到数据
m_nInbufLen += inlen; if (m_nInbufLen > INBUFSIZE) {
returnfalse;
} // 接收第二段数据(一次接收没有完成,接收第二段数据)
if(inlen == savelen && m_nInbufLen < INBUFSIZE) {
int savelen = INBUFSIZE - m_nInbufLen;
int savepos = (m_nInbufStart + m_nInbufLen) % INBUFSIZE;
CHECKF(savepos + savelen <= INBUFSIZE);
inlen = recv(m_sockClient, m_bufInput + savepos, savelen, );
if(inlen > ) {
m_nInbufLen += inlen;
if (m_nInbufLen > INBUFSIZE) {
returnfalse;
}
} elseif(inlen == ) {
Destroy();
returnfalse;
} else {
// 连接已断开或者错误(包括阻塞)
if (hasError()) {
Destroy();
returnfalse;
}
}
}
} elseif(inlen == ) {
Destroy();
returnfalse;
} else {
// 连接已断开或者错误(包括阻塞)
if (hasError()) {
Destroy();
returnfalse;
}
} returntrue;
} bool CGameSocket::Flush(void) //? 如果 OUTBUF > SENDBUF 则需要多次SEND()
{
if (m_sockClient == INVALID_SOCKET) {
returnfalse;
} if(m_nOutbufLen <= ) {
returntrue;
} // 发送一段数据
int outsize;
outsize = send(m_sockClient, m_bufOutput, m_nOutbufLen, );
if(outsize > ) {
// 删除已发送的部分
if(m_nOutbufLen - outsize > ) {
memcpy(m_bufOutput, m_bufOutput + outsize, m_nOutbufLen - outsize);
} m_nOutbufLen -= outsize; if (m_nOutbufLen < ) {
returnfalse;
}
} else {
if (hasError()) {
Destroy();
returnfalse;
}
} returntrue;
} bool CGameSocket::Check(void)
{
// 检查状态
if (m_sockClient == INVALID_SOCKET) {
returnfalse;
} char buf[];
int ret = recv(m_sockClient, buf, , MSG_PEEK);
if(ret == ) {
Destroy();
returnfalse;
} elseif(ret < ) {
if (hasError()) {
Destroy();
returnfalse;
} else { // 阻塞
returntrue;
}
} else { // 有数据
returntrue;
} returntrue;
} void CGameSocket::Destroy(void)
{
// 关闭
struct linger so_linger;
so_linger.l_onoff = ;
so_linger.l_linger = ;
int ret = setsockopt(m_sockClient, SOL_SOCKET, SO_LINGER, (constchar*)&so_linger, sizeof(so_linger)); closeSocket(); m_sockClient = INVALID_SOCKET;
m_nInbufLen = ;
m_nInbufStart = ;
m_nOutbufLen = ; memset(m_bufOutput, , sizeof(m_bufOutput));
memset(m_bufInput, , sizeof(m_bufInput));
}

使用例子:

    // 发送消息
bSucSend = m_pSocket->SendMsg(buf, nLen); // 接收消息处理(放到游戏主循环中,每帧处理)
if (!m_pSocket) {
return;
} if (!m_pSocket->Check()) {
m_pSocket = NULL;
// 掉线了
onConnectionAbort();
return;
} // 发送数据(向服务器发送消息)
m_pSocket->Flush(); // 接收数据(取得缓冲区中的所有消息,直到缓冲区为空)
while (true)
{
char buffer[_MAX_MSGSIZE] = { };
int nSize = sizeof(buffer);
char* pbufMsg = buffer;
if(m_pSocket == NULL)
{
break;
}
if (!m_pSocket->ReceiveMsg(pbufMsg, nSize)) {
break;
} while (true)
{
MsgHead* pReceiveMsg = (MsgHead*)(pbufMsg);
uint16 dwCurMsgSize = pReceiveMsg->usSize;
// CCLOG("msgsize: %d", dwCurMsgSize); if((int)dwCurMsgSize > nSize || dwCurMsgSize <= ) { // broken msg
break;
} CMessageSubject::instance().OnMessage((constchar*)pReceiveMsg, pReceiveMsg->usSize); pbufMsg += dwCurMsgSize;
nSize -= dwCurMsgSize;
if(nSize <= ) {
break;
}
}
}

这样的一个Socket封装,适用于windows mac ios android等平台, Socket处理是异步非阻塞的,所以可以放心的放到主线程处理消息, 最大支持64k的接收消息缓冲(一般一个消息不可能大于3k)。

这里展示这个,目的并不是说这个封装有多么优异,多么高科技,多么牛x。  恰恰是想表达它的简单。  这个简单的封装完全可以胜任一个mmo客户端的消息底层(注意是客户端,服务器对消息底层的性能要求要远远大于客户端),甚至是魔兽世界这类的大型mmo 都可以用这么一个小的封装来做消息底层。

对于游戏客户端消息底层的要求非常简单,根本不需要boost::asio什么的开源库。

1、非阻塞模型,这样我才放心把消息处理放到主线程,多线程处理消息其实很浪费。不知道得多大型的mmo才会用到。

2、消息接收缓存处理,避免大消息被截掉。

3、没了,剩下的一些特殊处理应该是上层逻辑来考虑的。比如掉线重连等。

我不是本文作者,作者的代码没有问题,只是有个注释打错了,可能会对新手造成误解,所以我单独提出来说一下,即:

.cpp文件里面

// 计算要拷贝的消息的大小(一个消息,大小为整个消息的第一个16字节  位即2字节,有些游戏是把长度放在第二个16位,那就把m_nInbufStart再+2,即向后移2字节取2字节),因为环形缓冲区,所以要分开计算
int packsize = (unsigned char)m_bufInput[m_nInbufStart] +
(unsigned char)m_bufInput[(m_nInbufStart + ) % INBUFSIZE] * ; // 注意字节序,高位+低位

跨平台的游戏客户端Socket封装,调整的更多相关文章

  1. 《Unity 3D游戏客户端基础框架》概述

    框架概述: 做了那么久的业务开发,也做了一年多的核心战斗开发,最近想着自己倒腾一套游戏框架,当然暂不涉及核心玩法类型和战斗框架,核心战斗的设计要根据具体的游戏类型而定制,这里只是一些通用的基础系统的框 ...

  2. C# .NET Socket封装

    Socket封装,支持多客户端,支持大文件传输,支持多线程并发,对较大的Socket包进行分块传输. 封装所要达到的效果,是可以像下面这样使用Socket和服务端通信,调用服务端的方法,让你在使用So ...

  3. Unity3D ——强大的跨平台3D游戏开发工具(六)

    第十一章 制作炮台的旋转 大家知道,炮台需要向四周不同的角度发射炮弹,这就需要我们将炮台设置成为会旋转的物体,接下来我们就一起制作一个会旋转的炮台. 第一步:给炮台的炮筒添加旋转函数. 给炮台的炮筒部 ...

  4. 微信小游戏下socket.io的使用

    参考: 微信小游戏:socket.io 一 在微信小游戏 中使用socket.io报错 因为项目需求,后端要使用nodejs简单搭建一个服务器,通讯用json格式. 使用Egret提供的socket. ...

  5. Linux C socket 封装

    /************************************************************************** * Linux C socket 封装 * 声明 ...

  6. java 网络编程基础 TCP/IP协议:服务端ServerSocket;客户端Socket; 采用多线程方式处理网络请求

    1.Java中客户端和服务器端通信的简单实例 Java中能接收其他通信实体连接请求的类是ServerSocket,ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一 ...

  7. python进阶__用socket封装TCP

    想要理解socket协议,点击链接,出门左转 一.TCP 通信的服务器端编程的基本步骤: 服务器端先创建一个 socket 对象. 服务器端 socket 将自己绑定到指定 IP 地址和端口. 服务器 ...

  8. ACE - 代码层次及Socket封装

    原文出自http://www.cnblogs.com/binchen-china,禁止转载. ACE源码约10万行,是c++中非常大的一个网络编程代码库,包含了网络编程的边边角角.在实际使用时,并不是 ...

  9. 跨平台的加密算法XXTEA 的封装

    跨平台的加密算法XXTEA 的封装 XXTEA算法的结构非常简单,只需要执行加法.异或和寄存的硬件即可,且软件实现的代码非常短小,具有可移植性. 维基百科地址:http://en.wikipedia. ...

随机推荐

  1. PAT-乙级-1030. 完美数列(25)

    1030. 完美数列(25) 时间限制 300 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CAO, Peng 给定一个正整数数列,和正整数p,设这 ...

  2. Android支付接入(三):电信爱游戏支付

    原地址:http://blog.csdn.net/simdanfeg/article/details/9011977 注意事项: 1.电信要求必须先启动电信的闪屏界面 2.非网络游戏不允许有Inter ...

  3. c++内存中字节对齐问题详解

    一.什么是字节对齐,为什么要对齐?    现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址 ...

  4. mysql 查询重复值命令

    积累备忘啊: ; 从t_maintenanceinfo表查询重复记录的mtiId 和ip字段,以及重复条数

  5. 【BZOJ 1046】 1046: [HAOI2007]上升序列

    1046: [HAOI2007]上升序列 Description 对于一个给定的S={a1,a2,a3,-,an},若有P={ax1,ax2,ax3,-,axm},满足(x1 < x2 < ...

  6. codeforces #309 div1 D

    求最小值最大显然是要二分 二分之后转换成了判定性问题 我们考虑哪些点一定不能选 显然是将所有可选点选中之后依然不满足条件的点不能选 那么我们不妨维护一个堆,每次取出堆顶看看是否满足条件 不满足条件就p ...

  7. 修改netbeans模版头部的说明

    以新建一个php类文件为例: 有两个地方需要修改, 1,工具->模版->默认许可证->在编辑器中打开 2,工具->模版->选择php类->在编辑器中打开 即可进行修 ...

  8. 安装Hadoop系列 — 新建MapReduce项目

    1.新建MR工程 依次点击 File → New → Ohter…  选择 “Map/Reduce Project”,然后输入项目名称:mrdemo,创建新项目:     2.(这步在以后的开发中可能 ...

  9. Java的类演进过程

    1.从面向过程到面向对象 在大家最熟悉的C语言中,如果要定义一个复杂的数据类型就用结构体(Struct)来实现,而为结构体的每个操作都定义一个函数,这个函数与结构体本身的定义没有任何关系.程序的重心集 ...

  10. DSPLIB for C6455+CCSv3.3

    问题描述: Hello everybody, I was looking for DSPLIB libraries optimized for C6455 processors. I found th ...