导航

  两者之间使用socket连接,采用tcp协议,传输字节流消息,双方约定统一的通讯消息格式。

  通过一个简单的聊天程序来介绍此种通讯。服务器端程序用MFC编写,负责管理用户连接,转发用户聊天消息。客户端程序分别采用silverlight和flex4技术编写,负责用户连接登陆服务器、聊天内容接收和发送等功能。 

  客户端与服务器端的通讯关键是客户端如何解析socket传递的字节流消息。

  下面分别介绍通讯消息格式、客户端解析字节流消息、服务器端程序、客户端程序。

一、通讯消息格式

  此聊天程序双方共使用三种消息:聊天内容消息、用户事件消息、用户信息列表消息。

  双方约定一个完整的消息包由包头和包体组成(消息包 = 包头 + 包体)。

  包头的组成在服务器端程序中定义如下:

typedef struct _NET_MSG_HEAD 
{
	UINT nSize;		//!< 	包大小(包括包头)
	UINT unType;		//!< 	包类型
	UINT	 unEndFlag;	//!< 	网络消息结束标志位,参见EM_NET_MSG_END_FLAG
	UINT	 unMsgNumber;	//!< 网络消息序号,回送给客户端的消息要和发送到服务器的消息的序号相同,广播消息序号为0
}NET_MSG_HEAD;

  nSize 为完整消息包的大小。

  unType 为消息包的类型。

  unEndFlag 标识此消息是否为同一批次最后一个消息包。

  unMsgNumber 客户端发送消息时给定的唯一编号,便于确认收到的消息是由哪个消息请求而来。

  整个包头由4个INT组成,大小为16个字节。

  

一个完整的消息包在服务器端程序中定义如下:

typedef struct _ST_MSG_CHAT_INFO
{
	NET_MSG_HEAD head;		//!< 	包头
	int				nChatType;//!< 	聊天信息类型
	char				szFromName[65];//!< 	发送者名称
	char				szToName[65];//!< 	接收者名称
	char				szContent[300];	//!< 	聊天内容
}ST_MSG_CHAT_INFO;

  这是一个聊天内容消息。“NET_MSG_HEAD head”为包头,head下面就是这个包的包体。整个消息大小为450字节(16+4+65+65+300)。

  

用户事件消息结构:

typedef struct _ST_MSG_ROLE_EVENT
{
	NET_MSG_HEAD head;		//!< 	包头
	int				nType;//!< 	事件类型
	char				szRoleName[CONST_ROLE_NAME_LENGTH];//!< 	用户名称
	int				nRoleIndex;//!< 索引
}ST_MSG_ROLE_EVENT;

 “nType事件类型”包括:用户登陆、验证用户登陆、请求用户列表、用户上线、用户离线

  

用户信息列表消息结构:

/// \brief  用户信息
typedef struct _ST_ROLE_INFO
{
	int	nRoleIndex;//!<索引
	char	szRoleName[65];//!< 发送者名称
}ST_ROLE_INFO;

/// \brief  用户信息列表
typedef struct _ST_ROLE_INFO_LIST
{
	NET_MSG_HEAD	head;	//!< 	包头
	int		nCount;	//!< 	列表数量
	ST_ROLE_INFO	info[1];	//!< 	用户信息列表
}ST_ROLE_INFO_LIST;

ST_ROLE_INFO_LIST用户信息列表消息结构,里面包含ST_ROLE_INFO用户信息结构,nCount为ST_ROLE_INFO用户信息结构的数量。客户端的消息解析类约定列表数量“nCount”一定要紧跟在用户信息列表“info[1]”前。

二、客户端解析字节流消息

  客户端将消息结构记录在MSGConfig.xml文件中。

  建立一个解析类(如NetMsg),将字节流解析成键值对或将键值对转换成字节流。

  1、字节流解析成键值对:将收到的消息字节流按照MSGConfig.xml文件中定义的消息结构,解析成键(字段名)值(字段值)对的形式。

  2、键值对转换成字节流:将解析类中的键值对按照MSGConfig.xml文件中定义的消息结构转换成字节流。

三、服务器端程序

  服务器端程序由三个线程完成主要功能:监听线程、工作线程、银光策略文件监听线程。

  1、监听线程

    监听线程负责监听客户端的socket连接,如果有客户端连接,则创建一个“ConnectItem类”,然后将此类交给工作线程处理数据收发。“ConnectItem类”包含连接的socket、数据缓冲、发送和接收用重叠结构。

   2、工作线程

    检查完成端口是否有完成的接收或发送操作,有完成的接收操作则进行相应的消息逻辑处理,有完成的发送操作则将发送重叠结构还到空闲发送重叠结构队列中。

   3、银光策略文件监听线程

    银光程序创建socket连接时会访问相同IP的943端口取得策略文件,此线程就是接受银光客户端943端口的socket连接请求,建立连接后则将连接交给工作线程进行策略文件的收发。

    服务器建立连接后的消息逻辑处理流程:

      1.接收客户端登陆消息,验证客户端登陆消息并回传验证消息(将用户加入“在线用户”队列,登陆完成),向在线用户群发某用户上线通知消息。

      2.接收客户端请求用户列表消息,回传“在线用户”信息列表消息。

      3.接收客户端聊天内容消息,向所有在线用户转发聊天内容。

      4.群发用户上线或下线事件消息。

四、客户端程序

  客户端程序接收到服务器端传来的字节流数据后将数据存入数据缓冲,读取数据缓冲中的头4个字节确认消息长度,如果缓冲中的数据长度大于等于消息长度,则截取与消息长度等长的字节流,将字节流放入NetMsg的Parse()方法中构建消息。

  FLEX客户端发送NetMsg消息时则使用NetMsg类中的GetByteArray()方法取得此消息的字节流进行发送。

  

FLEX客户端取得NetMsg消息中字段值的代码如下:

var nRoleIndex1:int = netMsg.GetIntValue("nRoleIndex");
var strRoleName1:String = netMsg.GetStringValue("szRoleName");

  

FLEX客户端组成NetMsg消息的代码如下:

private function SendRoleName():void
			{
				var msg:NetMsg = _socketCon.GetNetMsgProcessor.GetSendNetMsg(EnumType.NetMsg_RoleEvent_Login);
				
				if (msg == null)
					return;
				
				msg.SetValue("nType", EnumType.NetMsg_RoleEvent_Login);
				msg.SetValue("szRoleName", _strRoleName);
				_socketCon.Send(msg);
			}

  

silverlight客户端取得NetMsg消息中字段值的代码如下:

                        int nRoleIndex = pNetMsg["nRoleIndex"].IntValue;
                        string strRoleName = pNetMsg["szRoleName"].StrValue;

  

silverlight客户端组成NetMsg消息的代码如下:

NetMsg pNetMsg = _netSocket.NetMsgProcessor.GetSendMsg((int)EN_NETMSG_TYPE.ROLE_EVENT);
                        pNetMsg["nType"].IntValue = (int)EN_NETMSG_ROLEEVENT.LOGIN;
                        pNetMsg["szRoleName"].StrValue = _strRoleName;

   

    客户端建立连接后的消息逻辑处理流程:

      1.向服务器端发送登陆消息,接收服务器端回发的登陆验证消息(登陆完成)。

      2.发送请求用户列表消息,接收用户信息列表消息(更新本地用户列表)。

      3.接收用户上下线消息。      

      4.接收和发送聊天内容消息。

  

MFC服务器端程序

FLEX客户端程序

silverlight客户端程序