服务器发包到客户端
以登录包为例
SendCmd(s2c_login, &ret, sizeof(LoginEnum));
end_stat BaseChannel::SendCmd(int nCmd, void* pData, int nLen)
{Protocol Ptl;Ptl.cmd_type = nCmd;Ptl.content = pData;Ptl.size = nLen;void* tmpBuffer = g_SendCmdBuffer;int nBufferLen = 1024 * 100;int nSize = Ptl.to_buffer(tmpBuffer, 1024 * 100);//打包到g_SendCmdBufferif(-1 == nSize)return send_stat::send_parameter_error;DataPkt Pkt;Pkt.pData = m_pShare->PopPkt(nSize);//获取普通内存池中的一块内存
//    pkt.data = new char[n];Pkt.nSize = nSize;memcpy(Pkt.pData, tmpBuffer, nSize);m_queCmd.push(Pkt);//缓冲指令的地方,m_queCmd算是一个缓冲吧return SendCmdTry();
}send_stat BaseChannel::SendCmdTry()
{if(!m_queCmd.size())return send_stat::send_succeed;for(;;){if(m_queCmd.empty())break;DataPkt pkt = m_queCmd.front();{send_stat hr = m_pDataLayer->SendData(m_nChannelId, pkt.pData, pkt.nSize);if(hr != send_stat::send_succeed){//放入等待队列,让GS来发送m_pShare->PushGcWait(m_nChannelId);return hr;}}m_queCmd.pop();//delete[] pkt.data;m_pShare->PushPkt(pkt.pData, pkt.nSize);//释放内存池
    }return send_stat::send_succeed;
}send_stat DataLayer::SendData(int nChannelId, void* pData, int nSize)
{
#ifdef NET_MUL_PROCESSshareData tmpShareData;tmpShareData.channel_id = nChannelId;tmpShareData.data = pData;tmpShareData.is_data = true;tmpShareData.size = nSize;bool bRet = m_spShareMemInter->pushB(tmpShareData);//现在看这个就应该很好理解了,其实就是从内存中取内存然后放到共享内存队列中if(bRet)return send_stat::send_succeed;return send_stat::send_buffer_full;
#elsereturn m_spTCPServer->send_data(nChannelId, pData, nSize);
#endif
}在net主线程
for (;;)
{bool bRet = FromMem2Net();//相当于死循环从共享内存中取数据到net中if(!bRet){boost::this_thread::interruptible_wait(1);}
}bool NetProcSvr::FromMem2Net()
{shareData SharePkt;SharePkt.data = m_pSendBuffer;if(m_spShareMemInter->popA(SharePkt) == false)//从共享队列中取包到m_pSendBuffer发送缓冲中
    {return false;}if(!SharePkt.is_data){//主动断开客户端连接if(SharePkt.size == (int)send_stat::send_disconnected)//现在只是同一个用户登录用户下的两个角色
        {return m_spTCPServer->close(SharePkt.channel_id);        }}auto eState = m_spTCPServer->send_data(SharePkt.channel_id, SharePkt.data, SharePkt.size);if(eState != send_stat::send_succeed){if(eState == send_stat::send_disconnected)return true;else if(eState == send_stat::send_buffer_full)//对于libevnet的发送缓冲满了,貌似这个很少吧,不知有多少字节的数据,发送量有多大才会满
        {std::string str = boost::lexical_cast<std::string>(SharePkt.channel_id);str += ": send_buffer_full, return ret: ";char buf[10] = {0};itoa((int)eState, buf, sizeof(eState));str += buf;MessageBoxA(nullptr, str.c_str(), "send_data警告!", MB_ICONWARNING);std::cout << str << std::endl;}}return true;
}//close_channel
bool LibEvtServer::close_channel(int channel_id)
{auto c2 = m_allChannels[channel_id];if(!c2->channel || c2->channel->m_is_disconnected)return false;auto c = c2->channel;LibEvtServer::OffChannel2 offc2 = { GetTickCount(), c->m_bev->ev_read.ev_fd, c2};{std::lock_guard<std::mutex> lock(m_offline_mtx);m_offline_que.push(offc2);//他这个下线只是将其放到下线队列中,等下看看他是何时吧socket关闭的
#ifdef MUL_LIBEVENT_THREAD}{//防止多个libevent thread 线程同时访问下面公共变量std::lock_guard<std::mutex> lock(m_lts_mtx);
#endifm_allChannels[c->m_id] = NULL;m_ids->freeId(c->m_id);//[L]将id归还。
    }return true;
}//对于这个函数有两个疑问
//1.他到底是如何保证这个套接字有效的
//2.BUFFEREVENT_WRITE到底还是主线程在发送数据,这个和四个子线程什么关系
send_stat LibEvtServer::send_data(int channel_id, void* data, int len) //返回send_stat
{#ifndef NET_MUL_ThrSend //多进程模式只有一个线程进此函数std::lock_guard<std::mutex> lock(m_mtx);
#endifif(NULL == data || len <= 0)return send_stat::send_parameter_error;if(++m_send_100count >= 100)//当发送超过100个包是执行一次下线,看来这个踢下线还是在主线程做的
    {m_send_100count = 0;free_one_link();}{//std::lock_guard<std::mutex> lock(m_offline_mtx);auto c2 = m_allChannels[channel_id];if(!c2 || c2->channel->m_is_disconnected)return send_stat::send_disconnected;auto c = c2->channel;*(int*)m_send_buffer = len;memcpy(m_send_buffer + 4, data, len);//此时他在包当前添了四个字节表示整个包的长度//auto cur_fd = c->m_bev->ev_write.ev_fd;/***    [说明]:因为是异步读写,bufferevent_write将写入事件丢进工作线程的事件列表中*        可能存在此主线程在调用bufferevent_free的时候,将套接字关闭(即:主线程立马关闭套接字),*        而下一瞬间工作线程从激活队列中取出此处的写入事件发给客户端时,这个时候,发现此套接字无效,*        select的时候发现对无效套接字进行操作,立刻报错!所以需要保证操作的套接字有效*/if(0 != bufferevent_write(c->m_bev, m_send_buffer, len+4))    //就发送了,客户端的libevent就能收到通知了
        {return send_stat::send_buffer_full;}}return send_stat::send_succeed;
}bool LibEvtServer::free_one_link()
{/***①让主线程来决定连接,为了保证bufferevent_write的第一个参数绝对有效;*②一次处理一个,保证实时性。*/if(m_offline_que.size()){OffChannel2* offc2 = NULL;{std::lock_guard<std::mutex> lock(m_offline_mtx);offc2 = &(m_offline_que.front());//注意需要用成员的引用或地址
        }if(offc2->c2){delete offc2->c2->channel;//bufferevent_free(...)delete offc2->c2;offc2->c2 = nullptr;}if((GetTickCount() - offc2->offtime) > 60*1000)//一分钟让子线程操作完相关套接字,不然无效套接字select报错
        {evutil_closesocket(offc2->sockfd);    //关闭套接字,相当于clost(socket)std::lock_guard<std::mutex> lock(m_offline_mtx);m_offline_que.pop();}}return false;
}//看看客户端接收到包是如何处理的
如果服务器bufferevent_write的时候客户端的读事件就会被调用,然后他会把它放到流里面,然后从流中读包
bool on_receive_data(int channel_id, void* data, int len)
{auto link = g_TcpLinkExs[channel_id];//g_TcpLinkExs保存的是所有的连接,通过channel_id就能够确定是那个客户端连接的if(!link)return true;//m_event是I_NetEvent类型的,我们这边有好多都是这样做的,别的类保存一个虚基类指针,然后用子类类型进行初始化,到时就拿这个基类指针进行回调//当时这个过程不是太懂,给我造成了不少麻烦,关键是不太清楚这种用法,看了好久才看出来,而m_event是用NGP初始化的,到时肯定回调NGP里面的相关函数link->m_event->OnRec(data, len);return true;
}bool NGP::OnRec(void* pBuffer, int nSize)
{date_pkt pkt;pkt.len = nSize;pkt.date = new char[nSize];memcpy(pkt.date, pBuffer, nSize);{std::lock_guard<std::mutex> lock(m_PktMutex);m_quePktQueue.push(pkt);//然后这边分配内存,放到这个队列中
    }return true;
}
//然后就是runonce调用不同的function,或者本来就传递一个function过来,看来服务器发送一个包过来也是很麻烦的

转载于:https://www.cnblogs.com/zzyoucan/p/4098365.html

一个包从服务器到达客户端相关推荐

  1. 抓包之服务器校验客户端证书

    挂上vpn,打开charles抓包可以看到没网络,但是可以抓到包,怀疑是服务器校验客户端证书了 这里推荐一下肉老师的文章 https://www.anquanke.com/post/id/197657 ...

  2. 用C++写一个简单的服务器和客户端

    我们将创建一个服务器节点add_two_ints_server,它将会收到两个整数,并且返回它们的和.切换目录到之前建立的beginner_tutorials包下: cd ~/catkin_ws/sr ...

  3. python udp客户端 服务器实现方式_python3实现UDP协议的简单服务器和客户端

    利用python中的socket模块中的来实现UDP协议,这里写一个简单的服务器和客户端.为了说明网络编程中UDP的应用,这里就不写图形化了,在两台电脑上分别打开UDP的客户端和服务端就可以了. UD ...

  4. 灵活使用手机之-手机服务器和客户端

    灵活使用手机之-手机服务器和客户端 2020-12-15 近期玩手机少了,更多的精力用来学习未来必将大放光明的linux系统,一个redhat系列,一个ubuntu系列,把自己搞的晕头晕脑,虽然了解的 ...

  5. 【TCP服务器和客户端的简单编写】

    TCP服务器和客户端的简单编写 TCP简介 TCP是什么 传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的.可靠的.基于字节流的传输层通信协议,由IE ...

  6. Socket心跳包异常检测的C语言实现,服务器与客户端代码案例

    在Socket心跳机制中,心跳包可以由服务器发送给客户端,也可以由客户端发送给服务器,不过比较起来,前者开销可能较大.本文实现的是由客户端给服务器发送心跳包,服务器不必返回应答包,而是通过判断客户在线 ...

  7. java做服务器端给客户端传数据包_java 服务器怎样给客户端传输数据

    展开全部 服务器端源码: import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundExceptio ...

  8. 使用go的ssh包快速打造一个本地命令行ssh客户端

    2019独角兽企业重金招聘Python工程师标准>>> 热身运动? 在开始之前,先来个热身运动.虽然标题党写着快速打造一个ssh客户端,但是和跑步一样,在运动前还是需要先热身一下,不 ...

  9. RED5流媒体服务器作为客户端转发流至另一个RED5服务器

    RED5流媒体服务器,是Java开源的实现RTMP协议的服务器.有关RTMP协议,网上有很多的介绍.现在着重介绍一下,客户端连接RED5服务器的开发流程. RED5有一个client包red-clie ...

最新文章

  1. WCF-学习笔记概述之计算服务(1)
  2. Mysql 8.0 安装教程 Linux Centos7
  3. elasticsearch java api查询
  4. 京东总部4号楼即将投入使用:建面超过13万平方米 耗时644天
  5. 优先队列-二叉堆-堆排序原理-Java相关API
  6. 阿里云部署java web
  7. SPSS Sobel检验(图文+数据集)【SPSS 044期】
  8. ubuntu运行c/c++语言
  9. quartz定时任务时间设置
  10. iPhoneX、iPhoneXS、iPhoneXR、iPhoneXSMax屏幕适配
  11. blender python编程入门
  12. zynq-7000系列基于zynq-zed的vivado初步设计之linux下控制PL扩展的GPIO
  13. 如何在PC,Mac或iPhone上启用iTunes家长限制
  14. Cadence Allegro 鼠标轨迹快捷键的设置图文教程及视频演示
  15. eversync safari_值得推荐的chrome书签同步插件汇总
  16. 支付宝沙箱环境对接流程
  17. 什么是LTV,舔狗的LTV可以乘以N吗?
  18. 词根词缀学单词【3】
  19. 干支纪年、干支纪月、干支纪日、干支纪时
  20. WKWebView高级使用

热门文章

  1. 企业网络推广专员浅析有利于企业网络推广的页面制作技巧有哪些?
  2. 网站内容页面如何优化才利于排名提升?
  3. 网站关键词优化有哪些技巧?
  4. 复杂查询练习_数据分析之路——复杂查询(4)
  5. 天线巴伦制作和原理_一种基于LTCC技术的新型Marchand巴伦滤波器
  6. mysql 网络io_MySQL的各种网络IO超时的用法和实现
  7. 递归循环子组件_算法一看就懂之「 递归 」
  8. 开发日记-20190701 关键词 读书笔记《Linux 系统管理技术手册(第二版)》DAY 7
  9. (转载)聊聊Git原理
  10. HTTP metadata数据