//Msg.h

#pragma once
#include <windows.h>#pragma comment(lib,"Ws2_32.lib")
//********************************************************************
//Part1:消息结构体定义
//取结构体某个字段的偏移量
//思路:将址址0x00000000开始的地址看作是TYPE结构体对象
//然后再取出指定的字段的地址,即是偏移量
#define OFFSET(Struct, Member) ((size_t) &((Struct*)0)->Member)#define CMD_LOGIN_REQUEST     0X10    //客户端请求登录
#define CMD_LOGIN_RESPONSE      0X20//服务器反馈登录
#define CMD_MSG_TO_SERVER       0X30//客户端上传聊天信息
#define CMD_MSG_TO_CLIENT       0X40//服务器分发聊天信息
#define CMD_LINK_CHECK              0X50//服务器隔一段时间进行链路检测typedef struct SMsgHdr{//数据包头部,所有的数据包都以 SMsgHdr 开头int iCmdType;//命令IDint iPkgLen;//整个数据包长度 = 数据包头部 + 数据包体
}SMSGHDR, *PSMSGHDR;typedef struct SMsgLogin{//登录数据包(客户端->服务器端)TCHAR szUser[32];//用户名TCHAR szPwd[32];//账号
}SMSGLOGIN, *PSMSGLOGIN;typedef struct SMsgResp{//登录回应数据包(服务器端->客户端)int iResult;//登录结构:1=成功,0=用户名或密码错误
}SMSGRESP, *PSMSGRESP;typedef struct SMsg2Server{//聊天语句(客户端->服务器端):不等长数据包int iContent;//后面内容字段的长度TCHAR szContent[256];//内容,长度由iContent指定
}SMSG2SERVER, *PSMSG2SERVER;typedef struct SMsg2Client{//聊天语句(服务器端->客户端):不等长数据包int iContent;//后面内容字段的长度TCHAR szSender[32];//消息发送者TCHAR szContent[256];//内容,不等长,长度由nLength指定
}SMSG2CLIENT, *PSMSG2CLIENT;typedef struct SMsgPkg{//数据包定义方式:每个数据包以MSGHEAD + MSGXXX组成SMSGHDR stMsgHdr;//Msg Headunion{SMSGLOGIN stMsgLogin;SMSGRESP stMsgResp;SMSG2SERVER stMsg2Server;SMSG2CLIENT stMsg2Client;};//Msg Body
}SMSGPKG, *PSMSGPKG;
//***********************************************************************************
//Part2:消息队列设计(先进先出)
#include <strsafe.h>   //使用到StringcbCopy等函数typedef struct SMsgEntity{//队列中单条消息的格式定义int iMsgId;//消息编号TCHAR szSender[32];//发送者TCHAR szContent[256];//聊天内容
}SMSGENTITY, *PSMSGENTITY;#define QUEUE_SIZE    100        //消息队列的长度void PutMsgIntoQueue(TCHAR* lpszSender, TCHAR* lpszContent);
int GetMsgFromQueue(int iMsgId, TCHAR* lpszSender, TCHAR* lpszContent);
//***********************************************************************************
//Part3:数据包的循环接收
int WaitSocket(SOCKET hSocket, DWORD dwTime);
int RecvDat(SOCKET hSocket, char* lpszBuff, int iBytes);
BOOL RecvPkg(SOCKET hSocket, char* lpszBuff, int iBytes);
//***********************************************************************************
//Part4:保存客户端的会话信息
//客户端会话信息
typedef struct SSession{TCHAR szUser[32];//用户名int iMsgId;//己经下发的消息编号DWORD dwLastTime;//链路最近一次活动的时间
}SSESSION, *PSSESSION;
//***********************************************************************************

//Msg.cpp

#include "Msg.h"SMSGENTITY arrMsgQueue[QUEUE_SIZE];//先进先出消息队列
CRITICAL_SECTION  csMsgQueue;//线程间互斥访问消息队列
int iMsgCnt = 0;//队列中当前消息的数量
int iMsgSeq = 0;//消息的序号//在队列中加入一条消息
//——如果队列己满,则将整个队列前移一个位置,相当于最早的消息被覆盖,然后在队列尾部空出的位置加入新消息
//——如果队列未满,则在队列的最后加入新消息
//——消息编号从1开始递增,这样保证队列中的各消息的编号是连续的
//pszSender指两只发送者字符串的指针,pszContent指向聊天语句内容的字符串指针
void PutMsgIntoQueue(TCHAR* lpszSender, TCHAR* lpszContent)
{EnterCriticalSection(&csMsgQueue);//新消息覆盖最早的那一条消息,消息总数不变if(iMsgCnt >= QUEUE_SIZE)           CopyMemory(&arrMsgQueue[0], &arrMsgQueue[1], sizeof(SMSGENTITY)*(QUEUE_SIZE-1));else                                                    ++iMsgCnt;PSMSGENTITY pMsgEntity = &arrMsgQueue[0]; pMsgEntity += (iMsgCnt-1); //将消息添加到队列尾部StringCchCopy((TCHAR*)(&pMsgEntity->szSender), lstrlen(lpszSender)+1, lpszSender);StringCchCopy((TCHAR*)(&pMsgEntity->szContent), lstrlen(lpszContent)+1, lpszContent);pMsgEntity->iMsgId = ++iMsgSeq;//消息的序号,从1开始LeaveCriticalSection(&csMsgQueue);
}
/*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>从队列获取指定编号的消息-- 如果指定编号的消息已经被清除出消息队列,则返回编号最小的一条消息当向连接速度过慢的客户端发消息的速度比不上消息被清除的速度,则中间的消息等于被忽略,这样可以保证慢速链路不会影响快速链路-- 如果队列中的所有消息的编号都比指定编号小(意味着这些消息以前都被获取过)那么不返回任何消息参数:
in nMessageId = 需要获取的消息编号
out pszSender = 用于返回消息中发送者字符串的缓冲区指针
out pszSender = 用于返回消息中聊天内容字符串的缓冲区指针
返回: 0 (队列为空,或者队列中没有小于等于指定编号的消息)
返回:非0(已经获取指定消息号)
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
int GetMsgFromQueue(int iCurr, TCHAR* lpszSender, TCHAR* lpszContent)
{if(iMsgCnt <= 0)           return 0;EnterCriticalSection(&csMsgQueue);int iMin = arrMsgQueue[0].iMsgId, iMax = iMin + iMsgCnt - 1;PSMSGENTITY pMsgEntity = NULL; //获取指定编号的消息if(iCurr < iMin)                pMsgEntity = &arrMsgQueue[0];else if(iCurr <= iMax)    pMsgEntity = &arrMsgQueue[iCurr-iMin];if(pMsgEntity != NULL){StringCbCopy(lpszSender, sizeof(pMsgEntity->szSender), pMsgEntity->szSender);StringCbCopy(lpszContent, sizeof(pMsgEntity->szContent), pMsgEntity->szContent);}LeaveCriticalSection(&csMsgQueue);if(pMsgEntity != NULL)          return pMsgEntity->iMsgId;return 0;
}//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// 在规定的时间内等待数据到达
// 输入:dwTime = 需要等待的时间(微秒)
// 返回值:超时而返回(0);出错而返回(SOCKET_ERROR);就绪的套接字数量(n)
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
int WaitSocket(SOCKET hSocket, DWORD dwTime)
{FD_SET  stFd;stFd.fd_count = 1; stFd.fd_array[0] = hSocket;TIMEVAL  stTv; stTv.tv_sec = 0;stTv.tv_usec = dwTime;return select(0, &stFd, NULL, NULL, &stTv);
}//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// 接收规定字节的数据,如果缓冲区中的数据不够则等待
// 返回:连接中断或发生错误(FALSE);成功(TRUE)
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
int RecvDat(SOCKET hSocket, char* lpszBuff, int iBytes)
{int iBeginTime = GetTickCount();int iRecv = 0;while((GetTickCount()-iBeginTime) < 10*1000){//10s超时int iRet = WaitSocket(hSocket, 100*1000);//等待数据100msif(iRet == SOCKET_ERROR)         return FALSE;//连接错误if(iRet == 0)                                      break;//超时do{//接收数据,直至收完指定的字节数iRecv += recv(hSocket, lpszBuff+iRecv, iBytes - iRecv, 0);if(iRecv == SOCKET_ERROR || iRecv == 0)           return FALSE;if(iRecv == iBytes)          return TRUE;}while(iRecv < iBytes);}return TRUE;
}//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//接收一个符合规范的数据包
//参数: pBuffer用来接收数据的缓冲区          nBytes 数据区最大的空间
//返回:失败(FALSE);成功(TRUE)
//注意:这里的nBytes不要指要接收的字节数,只是用来判断缓冲区是否只够大
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
BOOL RecvPkg(SOCKET hSocket, char* lpszBuff, int iBytes)
{PSMSGPKG pMsg = (PSMSGPKG)lpszBuff;//接收数据包头部并检测数据是否正常int iRet = RecvDat(hSocket, lpszBuff, sizeof(SMSGHDR));if(iRet){//如果成功接收数据if(pMsg->stMsgHdr.iPkgLen <= sizeof(SMSGHDR) || pMsg->stMsgHdr.iPkgLen > iBytes)     return FALSE;//接收余下的数据iRet = RecvDat(hSocket, lpszBuff+sizeof(SMSGHDR), pMsg->stMsgHdr.iPkgLen-sizeof(SMSGHDR));}return iRet;
}

TCP聊天室02 通信协议数据包的设计相关推荐

  1. 【Java网络编程(四)】手写TCP聊天室——控制台版

    版本1:群聊功能 使用TCP 运行方式:先开服务端,再开任意个数客户端.在控制台可以实现群聊功能. 效果 代码 服务端 package cn.hanquan.groupchat;import java ...

  2. TCP 聊天室v2 实现多人匿名聊天 C++,linux系统下

    使用select IO复用实现多人匿名聊天室(linux系统下) 功能:用户可发送消息,并同时能看到其他用户发的消息,用户输入over退出 实现思路: 服务端仅作为消息的中转方,使用select管理所 ...

  3. 基于JavaEE的聊天室实现显示表情包图片

    一.写在前面 这学期新开的JavaEE,使用socket做了一个聊天室,想着加一点自己的功能.传输文件流有点麻烦,于是就使用客户端直接的约定,实现显示图片的功能,说明:图片文件并未在socket中传输 ...

  4. java仿聊天室项目总结_Java团队课程设计-socket聊天室(个人总结)

    Java团队课程设计-socket聊天室(个人总结) 一.团队课程设计博客链接 二.本人负责模块或任务说明 任务1 服务端对socket线程的接受以及对客户端的数据转发操作 任务2 数据库的查找,添加 ...

  5. java仿聊天室项目总结_Java团队课程设计-socket聊天室(Day4总结篇)

    Java团队课程设计-socket聊天室(Day4总结篇) 团队名称: ChatRoom 项目git地址: git提交记录(仅截取部分): 面向对象设计包图.类图 包图 UML类图 总结: 首先总结一 ...

  6. 基于webrtc的视频聊天室(六)之客户端设计

    客户端用 Vue 框架写的,分了8个自定义组件: container.vue 是其它组件的父组件,其中 WebRtcPeerSendRecv.vue 是共用组件,UserOpt 和OneToOneBo ...

  7. 基于webrtc的视频聊天室(四)之用户设计

    用户的设计主要是明确其生命周期及其需要持有的对象. 用户的生命周期 创建 websocket连接时,建立普通用户对象: 申请通话时,等待对方响应: 对方接受,如果为群组聊天,则将其更改为 GroupU ...

  8. QT学习:基于TCP的网络聊天室程序

    TCP与UDP的差别如图: 一.TCP工作原理 如下图所示,TCP能够为应用程序提供可靠的通信连接,使一台计算机发出的字节流无差错 地送达网络上的其他计算机.因此,对可靠性要求高的数据通信系统往往使用 ...

  9. 用udp协议通讯时怎样得知目标机是否获得了数据包?_和相亲对象聊天,你属于UDP还是CDP?...

    有人说 和相亲对象聊天就像ping服务器 每发一条消息 就像发出一条Ping命令 等待对方回复从而得到响应速度结果 但是难受的是 这个响应速度永远无法做到秒级 少点几分钟 多则几十分钟 甚至几十个小时 ...

最新文章

  1. PHP遇到json解决的两个办法,转为数组,直接取值
  2. https refused 解决方法
  3. DataTable筛选符合条件的DataRow
  4. [持续收集]中国好注入-语句
  5. Android中最详细的焦点问题,从概念出发带你一点点分享(1)
  6. 8086汇编求一组正整数{0x1223,0x1234,0x1434,0x2345,0x3412,0x1712}中的最大数并存在变量MAX中
  7. echo显示文字后不换行(sep /p 和 echo 命令的一些细节)
  8. css background 一半_CSS---阴阳图
  9. MySQL 阿里巴巴JAVA开发手册-MySQL相关
  10. 【S交换机技术连载帖】交换机在江湖系列-序言
  11. 2022年给正在创作的程序员的实用工具
  12. 【Android 逆向】Android 中常用的 so 动态库 ( libm.so 数学函数动态库 | liblog.so 日志模块动态库 | libselinux.so 安全模块动态库 )
  13. 传统项目管理和敏捷项目管理的区别是什么?
  14. win7计算机用户名在哪改,win7系统怎么更改用户账户名称|win7修改用户名的方法...
  15. 慧都MES系统怎么实施?有哪些注意事项?
  16. Centos7安装源地址
  17. G - The Tourist Guide UVA - 10099
  18. 浅学socket及iOS中的AsyncSocket框架
  19. python挖掘B站猛男手游公主连结的另类操作!
  20. 2020web前端学习路线(附全套前端视频教程+教学大纲

热门文章

  1. 免费下载roboware studio 1.2 中文使用说明书
  2. 教你百分百实用监控安装摄像头的方法与技巧
  3. 指针学习十三——指针动态分配内存
  4. 手把手教程Atlas安装与使用
  5. MATLAB多核并行计算使用方法
  6. C语言qsort函数的实现
  7. IDE在控制台打印覆盖上次打印
  8. 智慧社区解决方案的服务形式有哪些
  9. 基于51单片机的智能家居的设计(一)
  10. PHP RSA密文过长加密解密 越过1024的解决代码