一个简单的聊天客户端,主要介绍如何与c++服务器端传递消息、解析消息。使用vs2010和silverlight4.0库开发。
客户端提供连接服务器,发送和接收通讯消息(发送登陆消息,发送用户列表请求消息等)。
客户端运行后的界面:

解决方案:

一、客户端主逻辑
客户端的主逻辑都在MainPage.xaml.cs文件中
MainPage.xaml
<UserControl x:Class="ChatClientSL.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Height="300" Width="570" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<Grid x:Name="LayoutRoot" Background="White">
<Button Content="连接到" Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="btCon" VerticalAlignment="Top" Width="75" Click="btCon_Click" />
<Button Content="发送" Height="23" HorizontalAlignment="Right" Margin="0,265,190,0" Name="btSend" VerticalAlignment="Top" Width="80" Click="btSend_Click" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="10,265,0,0" Name="textBoxSend" VerticalAlignment="Top" Width="284" KeyUp="textBoxSend_KeyUp" />
<ScrollViewer x:Name ="scrollChat" VerticalScrollBarVisibility="Auto" Background="White" Margin="10,41,190,41">
<TextBlock x:Name="textBlockShow" Text="" TextWrapping ="Wrap" Width="360" />
</ScrollViewer>
<TextBox Height="23" HorizontalAlignment="Left" Margin="93,12,0,0" Name="tbIP" VerticalAlignment="Top" Width="118" Text="127.0.0.1" />
<TextBox Height="23" HorizontalAlignment="Right" Margin="0,12,307,0" Name="tbPort" Text="4503" VerticalAlignment="Top" Width="46" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="322,12,0,0" Name="tbName" Text="SL" VerticalAlignment="Top" Width="58" />
<ListBox Height="247" Margin="0,41,12,0" Name="listBox" VerticalAlignment="Top" HorizontalAlignment="Right" Width="172" />
<sdk:Label Height="28" HorizontalAlignment="Left" Margin="275,13,0,0" Name="label1" VerticalAlignment="Top" Width="47" Content="昵称:" FontSize="14" />
<sdk:Label Content="参与聊天的人" FontSize="14" Height="28" HorizontalAlignment="Left" Margin="422,13,0,0" Name="label2" VerticalAlignment="Top" Width="95" />
</Grid>
</UserControl>
MainPage.xaml.cs
/********************************************************************
author: 田园小蛙 ourtree@live.cn
purpose:
*********************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Text;
using System.Collections;
using System.Threading;
using System.Collections.ObjectModel;
namespace ChatClientSL
{
//消息类型
public enum EN_NETMSG_TYPE
{
ROLE_EVENT = 1,//用户事件消息
CHAT_INFO = 2,//聊天内容消息
ROLE_LIST = 3//用户信息列表消息
}
public enum EN_NETMSG_ROLEEVENT
{
LOGIN = 1,//用户登陆
VALIDE_LOGIN = 2,//验证用户登陆
REQ_ROLE_LIST = 3,//请求用户列表
ROLE_IN = 4,//连接
ROLE_OUT = 5//退出
}
//聊天类型
public enum EN_CHAT_TYPE
{
ROLE_COMMUNITY = 1, //用户群聊
SYSTEM = 2//系统广播
}
//消息结束标识
public enum EN_NETMSG_ENDFLAG
{
Normal = 0,//普通消息
End = 1//结束消息
}
public partial class MainPage : UserControl
{
NetSocket _netSocket;//连接
string _strRoleName = "";//用户名
int _nRoleIndex = -1;//用户索引
ObservableCollection<string> _listRole = new ObservableCollection<string>();// 绑定的用户列表
Dictionary<int, string> _dictRole = new Dictionary<int, string>();//用户列表
string _strIP = "";//服务器IP
int _nPort = 4503;//服务器端口
public MainPage()
{
InitializeComponent();
Init();
}
private void Init()
{
_netSocket = new NetSocket();//初始化网络连接
listBox.ItemsSource = _listRole;
//收到消息
_netSocket.OnReceiveEvent += new NetSocket.ReceiveEventDelegate(_netSocket_OnReceiveEvent);
//网络连接情况
_netSocket.OnConnectEvent += new NetSocket.ConnectEventDelegate(_netSocket_OnConnectEvent);
}
/// <summary>
/// 刷新用户列表
/// </summary>
private void ReflashRoleList()
{
this.Dispatcher.BeginInvoke(
delegate
{
_listRole.Clear();
Dictionary<int, string>.ValueCollection vc = _dictRole.Values;
foreach (string str in vc)
{
_listRole.Add(str);
}
}
);
}
/// <summary>
/// 处理连接事件
/// </summary>
/// <param name="enEvent">事件类型</param>
void _netSocket_OnConnectEvent(EN_CONNECT_EVENT_TYPE enEvent)
{
switch (enEvent)
{
case EN_CONNECT_EVENT_TYPE.CONNECT_OK:
{
string strMsg = "成功连接 " + _strIP;
ShowText(strMsg);
//发送登陆消息
NetMsg pNetMsg = _netSocket.NetMsgProcessor.GetSendMsg((int)EN_NETMSG_TYPE.ROLE_EVENT);
pNetMsg["nType"].IntValue = (int)EN_NETMSG_ROLEEVENT.LOGIN;
pNetMsg["szRoleName"].StrValue = _strRoleName;
_netSocket.SendMsg(pNetMsg.GetSendBuffer(), pNetMsg.BufferSize);
}
break;
case EN_CONNECT_EVENT_TYPE.CONNECT_FAILED:
{
string strMsg = "连接失败 " + _strIP;
ShowText(strMsg);
}
break;
default:
break;
}
}
/// <summary>
/// 处理接收到的消息
/// </summary>
/// <param name="pNetMsg"></param>
void _netSocket_OnReceiveEvent(NetMsg pNetMsg)
{
//在这里处理收到的消息
switch (pNetMsg.Type)
{
case (int)EN_NETMSG_TYPE.ROLE_EVENT:
{
int nRoleIndex = pNetMsg["nRoleIndex"].IntValue;
string strRoleName = pNetMsg["szRoleName"].StrValue;
switch (pNetMsg["nType"].IntValue)
{
case (int)EN_NETMSG_ROLEEVENT.VALIDE_LOGIN:
{
//处理登陆验证消息
if (strRoleName != _strRoleName)
{
_strRoleName = strRoleName;
ShowName(_strRoleName);
_nRoleIndex = nRoleIndex;
}
//发送用户列表请求消息
NetMsg pNetMsgReq = _netSocket.NetMsgProcessor.GetSendMsg((int)EN_NETMSG_TYPE.ROLE_EVENT);
pNetMsgReq["nType"].IntValue = (int)EN_NETMSG_ROLEEVENT.REQ_ROLE_LIST;
pNetMsgReq["szRoleName"].StrValue = _strRoleName;
pNetMsgReq["nRoleIndex"].IntValue = _nRoleIndex;
_netSocket.SendMsg(pNetMsgReq.GetSendBuffer(), pNetMsgReq.BufferSize);
_dictRole.Clear();
}
break;
case (int)EN_NETMSG_ROLEEVENT.ROLE_IN:
{
//处理用户上线消息
//加上线用户信息加到用户列表中
_dictRole.Add(nRoleIndex, strRoleName);
ReflashRoleList();
string str = strRoleName + " 上线了";
ShowText(str);
}
break;
case (int)EN_NETMSG_ROLEEVENT.ROLE_OUT:
{
//处理用户下线消息
//将用户从用户列表中移除
if (_dictRole.ContainsKey(nRoleIndex))
{
_dictRole.Remove(nRoleIndex);
ReflashRoleList();
string str = strRoleName + " 下线了";
ShowText(str);
}
}
break;
default:
break;
}
}
break;
case (int)EN_NETMSG_TYPE.CHAT_INFO:
{
//处理收到的聊天内容
switch (pNetMsg["nChatType"].IntValue)
{
case (int)EN_CHAT_TYPE.ROLE_COMMUNITY:
case (int)EN_CHAT_TYPE.SYSTEM:
{
//显示聊天内容
string strMsg = pNetMsg["szFromName"].StrValue + " 说: " + pNetMsg["szContent"].StrValue;
ShowText(strMsg);
}
break;
default:
break;
}
}
break;
case (int)EN_NETMSG_TYPE.ROLE_LIST:
{
//处理收到的用户信息列表消息
//提取消息的列表内容,更新用户列表
NetMsgSectionCollection[] secs = pNetMsg["info"].Sections;
for (int i = 0; i < secs.Length; i++)
{
NetMsgSectionCollection sec = secs[i];
int nIndex = sec.StrCollection["nRoleIndex"].IntValue;
string strName = sec.StrCollection["szRoleName"].StrValue;
_dictRole[nIndex] = strName;
}
if (pNetMsg.Head.endFlag.IntValue == (int)EN_NETMSG_ENDFLAG.End)
{
//收到最后一个包就更新显示列表
ReflashRoleList();
}
}
break;
default:
break;
}
}
private void btCon_Click(object sender, RoutedEventArgs e)
{
//连接服务器
try
{
_strRoleName = tbName.Text;
_strIP = tbIP.Text;
_nPort = Convert.ToInt32(tbPort.Text);
//开始连接服务器
_netSocket.Connect(_strIP, _nPort);
}
catch (System.Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btSend_Click(object sender, RoutedEventArgs e)
{
//发送聊天内容
SendMsg();
}
private void textBoxSend_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
e.Handled = true;
SendMsg();
}
}
/// <summary>
/// 发送聊天内容
/// </summary>
private void SendMsg()
{
string strSend = textBoxSend.Text.Trim();
if (_netSocket.bConnected && strSend.Length > 0)
{
//组建一个聊天内容消息包
NetMsg pNetMsg = _netSocket.NetMsgProcessor.GetSendMsg((int)EN_NETMSG_TYPE.CHAT_INFO);
pNetMsg["nChatType"].IntValue = (int)EN_CHAT_TYPE.ROLE_COMMUNITY;
pNetMsg["szFromName"].StrValue = _strRoleName;
pNetMsg["szToName"].StrValue = "大家";
pNetMsg["szContent"].StrValue = strSend;
_netSocket.SendMsg(pNetMsg.GetSendBuffer(), pNetMsg.BufferSize);
}
}
/// <summary>
/// 显示聊天信息
/// </summary>
/// <param name="strText"></param>
private void ShowText(string strText)
{
this.Dispatcher.BeginInvoke(
delegate
{
textBlockShow.Text += strText + "\r\n";
scrollChat.ScrollToVerticalOffset(textBlockShow.ActualHeight);
}
);
}
/// <summary>
/// 显示验证后的用户名
/// </summary>
/// <param name="strName"></param>
private void ShowName(string strName)
{
this.Dispatcher.BeginInvoke(
delegate
{
tbName.Text = strName;
}
);
}
}
}
MainPage.xaml.cs在加载时会初始化一个NetSocket类,此类提供网络连接,消息包的处理。客户端的逻辑处理见MainPage.xaml.cs代码中的注释。
二、客户端消息
客户端的消息结构定义在MSGConfig.xml文件中,同服务器端一样只有三种消息结构。
MSGConfig.xml文件:
MSGConfig.xml
<?xml version="1.0" encoding="utf-8"?>
<Config>
<Mapping type="1" name="RoleEvent">
<Int name="nType"/>
<String name="szRoleName" length="65" charset="utf-8"/>
<Int name="nRoleIndex"/>
</Mapping>
<Mapping type="2" name="ChatInfo" comment = " 聊天">
<Int name="nChatType"/>
<String name="szFromName" length="35" charset="utf-8" comment = " 聊天发送者名称"/>
<String name="szToName" length="35" charset="utf-8" comment = " 聊天接收者名称"/>
<String name="szContent" length="300" charset="utf-8" comment = " 聊天内容"/>
</Mapping>
<Mapping type="3" name="RoleInfoList" comment = " 用户列表">
<Int name="nCount" comment = " 总数量"/>
<Array name="info">
<Item>
<Int name="nRoleIndex" comment = "帐号索引"/>
<String name="szRoleName" length="65" charset="utf-8" comment = " 发送人名称"/>
</Item>
</Array>
</Mapping>
</Config>
RoleInfoList用户信息列表消息结构,里面包含info用户信息结构,nCount为用户信息结构数组的数量。客户端的消息解析类约定列表数量“nCount”一定要紧跟在用户信息列表“info”前。(经几个游戏的开发总结,发现可以取消“nCount”,因为消息数组肯定是排在消息最后,且消息数组内的消息不会再包含消息列表)
NetMsg类负责解析消息,将字节流解析成键值对或将键值对转换成字节流,如下:
1、字节流解析成键值对(NetMsg类中的Parse()方法):将收到的消息字节流按照MSGConfig.xml文件中定义的消息结构,解析成键(字段名)值(字段值)对的形式。
2、键值对转换成字节流(NetMsg类中的GetSendBuffer()方法):将解析类中的键值对按照MSGConfig.xml文件中定义的消息结构转换成字节流。
“NetMsg.cs”文件:
/********************************************************************
author: 田园小蛙 ourtree@live.cn
purpose: 消息解析类
*********************************************************************/
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections;
using System.Collections.Generic;
using System.Xml.Linq;
using System.IO;
namespace ChatClientSL
{
/// <summary>
/// 消息元素类型
/// </summary>
public enum NetMsgSectionType
{
INT,
STRING,
ARRAY,
}
/// <summary>
/// 消息内的元素
/// </summary>
public class NetMsgSection
{
public static readonly int EF_NORMAL = 0;
public static readonly int EF_FIRST = 1;
public static readonly int EF_LAST = 2;
public static int INT_SIZE = 4;
private NetMsgSectionType _type;
private int _length;
private string _name;
private int _intValue;
private string _strValue;
private NetMsgSectionCollection _sectionsDefault;
private NetMsgSectionCollection[] _sections;
public NetMsgSectionType Type { get { return _type; } }
/// <summary>
/// 类型为INT 时,为4,类型为STRING时,为字节数,类型为ARRAY时,为对象数
/// </summary>
public int Length { get { return _length; } }
public string Name { get { return _name; } }
public int IntValue
{
get
{
if (_type == NetMsgSectionType.INT)
return _intValue;
else
throw new Exception("wrong net msg section type");
}
set
{
if (_type == NetMsgSectionType.INT)
_intValue = value;
else
throw new Exception("wrong net msg section type");
}
}
public string StrValue
{
get
{
if (_type == NetMsgSectionType.STRING)
return _strValue;
else
throw new Exception("wrong net msg section type");
}
set
{
if(_type == NetMsgSectionType.STRING)
_strValue = value;
else
throw new Exception("wrong net msg section type");
}
}
/// <summary>
/// by ourtree 20101014
/// </summary>
public NetMsgSectionCollection[] Sections
{
get
{
if (_type == NetMsgSectionType.ARRAY)
return _sections;
else
throw new Exception("wrong net msg section type");
}
}
public static implicit operator int(NetMsgSection section)
{
return section.IntValue;
}
public NetMsgSection()
{
_sectionsDefault = new NetMsgSectionCollection();
}
public NetMsgSection(NetMsgSection section)
{
_type = section._type;
_name = section._name;
_strValue = section._strValue;
_intValue = section._intValue;
_length = section._length;
_sectionsDefault = new NetMsgSectionCollection(section._sectionsDefault);
}
public void Init(string name, NetMsgSectionType type, int length)
{
_type = type;
_length = length;
_name = name;
}
public int Init(XElement el)
{
_name = el.Attribute("name").Value;
switch (el.Name.LocalName)
{
case "String":
{
_type = NetMsgSectionType.STRING;
_length = Convert.ToInt32(el.Attribute("length").Value);
return _length;
}
case "Int":
{
_type = NetMsgSectionType.INT;
_length = 4;
return _length;
}
case "Array":
{
_type = NetMsgSectionType.ARRAY;
_length = 1;
int index = 0;
XElement elItem = el.Element("Item");
int sectionSize = INT_SIZE; // 数量字段
IEnumerable<System.Xml.Linq.XElement> elementsSection = elItem.Elements();
foreach (XElement elSection in elementsSection)
{
NetMsgSection section = new NetMsgSection();
int size = section.Init(elSection);
if (size == 0)
throw new Exception("Init netmsg section get size 0");
_sectionsDefault.Add(index, section.Name, section);
index++;
sectionSize += size;
}
return sectionSize;
}
}
return 0;
}
/// <summary>
/// 解析
/// </summary>
/// <param name="buffer">缓冲</param>
/// <param name="offset">偏移量</param>
/// <param name="length">从偏移量算起,还剩的长度</param>
/// <returns>解析了多少字节</returns>
public int Parse(byte[] buffer, int offset, int length)
{
int nParseCount = 0;
switch(_type)
{
case NetMsgSectionType.INT:
{
if (length < _length)
return 0;
_intValue = BitConverter.ToInt32(buffer, offset);
return _length;
}
case NetMsgSectionType.STRING:
{
if (length < _length)
return 0;
if(buffer[offset] == 0)
{
_strValue = "";
return _length;
}
else
{
_strValue = System.Text.Encoding.UTF8.GetString(buffer, offset, _length);
int index = _strValue.IndexOf('\0');
if(index != -1 && index < _length)
{
_strValue = _strValue.Substring(0, index);
}
return _length;
}
}
case NetMsgSectionType.ARRAY:
{
if (length < INT_SIZE)
return 0;
_length = BitConverter.ToInt32(buffer, offset - 4);
if(_length > 200)
return 0;
nParseCount = 0;
_sections = new NetMsgSectionCollection[_length];
for(int i = 0; i < _length; i++)
{
_sections[i] = new NetMsgSectionCollection(_sectionsDefault);
int nSectionParseCount =_sections[i].Parse(buffer, offset + nParseCount, length - nParseCount);
if(nSectionParseCount == 0)
return 0;
nParseCount += nSectionParseCount;
}
return nParseCount;
}
}
return nParseCount;
}
public int GetSendBuffer(byte[] buffer, int offset, int length)
{
switch(_type)
{
case NetMsgSectionType.INT:
{
if (length < INT_SIZE)
return 0;
byte[] temp = BitConverter.GetBytes(_intValue);
if (temp == null)
return 0;
Array.Copy(temp, 0, buffer, offset, INT_SIZE);
return INT_SIZE;
}
case NetMsgSectionType.STRING:
{
if (length < _length)
return 0;
if (_strValue == null)
_strValue = "";
byte[] temp = System.Text.Encoding.UTF8.GetBytes(_strValue);
if (temp == null)
return 0;
int nCopyLength = temp.Length;
if (nCopyLength > _length)
nCopyLength = _length;
Array.Copy(temp, 0, buffer, offset, nCopyLength);
return _length;
}
case NetMsgSectionType.ARRAY:
{
if (_sections == null)
return 0;
int sectionsSize = 0;
for (int i = 0; i < _sections.Length; i++ )
{
int size = _sections[i].GetSendBuffer(buffer, offset + sectionsSize, length - sectionsSize);
if (size == 0)
return 0;
sectionsSize += size;
}
return sectionsSize;
}
}
return 0;
}
}
/// <summary>
/// 消息头
/// </summary>
public class NetMsgHead
{
public NetMsgHead()
{
size = new NetMsgSection();
type = new NetMsgSection();
endFlag = new NetMsgSection();
number = new NetMsgSection();
size.Init("size", NetMsgSectionType.INT, NetMsgSection.INT_SIZE);
type.Init("type", NetMsgSectionType.INT, NetMsgSection.INT_SIZE);
endFlag.Init("endFlag", NetMsgSectionType.INT, NetMsgSection.INT_SIZE);
number.Init("number", NetMsgSectionType.INT, NetMsgSection.INT_SIZE);
}
public NetMsgHead(NetMsgHead netMsgHead)
{
size = new NetMsgSection(netMsgHead.size);
type = new NetMsgSection(netMsgHead.type);
endFlag = new NetMsgSection(netMsgHead.endFlag);
number = new NetMsgSection(netMsgHead.number);
}
public NetMsgSection size;
public NetMsgSection type;
public NetMsgSection endFlag;
public NetMsgSection number;
public static int SIZE = NetMsgSection.INT_SIZE * 4;
public int GetSendBuffer(byte[] buffer, int offset, int length)
{
if (buffer == null)
return 0;
if (buffer.Length < length)
return 0;
if (buffer.Length < SIZE)
return 0;
byte[] temp = BitConverter.GetBytes(size);
Array.Copy(temp, 0, buffer, offset, NetMsgSection.INT_SIZE);
temp = BitConverter.GetBytes(type);
Array.Copy(temp, 0, buffer, offset + NetMsgSection.INT_SIZE, NetMsgSection.INT_SIZE);
temp = BitConverter.GetBytes(endFlag);
Array.Copy(temp, 0, buffer, offset + NetMsgSection.INT_SIZE * 2, NetMsgSection.INT_SIZE);
temp = BitConverter.GetBytes(number);
Array.Copy(temp, 0, buffer, offset + NetMsgSection.INT_SIZE * 3, NetMsgSection.INT_SIZE);
return SIZE;
}
}
/// <summary>
/// 消息内的元素集合
/// </summary>
public class NetMsgSectionCollection
{
private Dictionary<int, NetMsgSection> _msgSection;
private Dictionary<string, NetMsgSection> _msgSectionName;
public NetMsgSectionCollection()
{
_msgSection = new Dictionary<int, NetMsgSection>();
_msgSectionName = new Dictionary<string, NetMsgSection>();
}
public NetMsgSectionCollection(NetMsgSectionCollection sections)
{
_msgSection = new Dictionary<int, NetMsgSection>();
_msgSectionName = new Dictionary<string, NetMsgSection>();
for(int i = 0; i < sections._msgSection.Count; i++)
{
NetMsgSection section = new NetMsgSection(sections._msgSection[i]);
_msgSectionName.Add(section.Name, section);
_msgSection.Add(i, section);
}
}
public Dictionary<int, NetMsgSection> IntCollection
{
get{return _msgSection;}
}
public Dictionary<string, NetMsgSection> StrCollection
{
get{return _msgSectionName;}
}
public NetMsgSection this[int index]
{
get{return _msgSection[index];}
}
public NetMsgSection this[string name]
{
get{return _msgSectionName[name];}
}
public void Add(int index, string name, NetMsgSection section)
{
_msgSection.Add(index, section);
_msgSectionName.Add(name, section);
}
public int Parse(byte[] buffer, int offset, int length)
{
int nParseCount = 0;
for(int i = 0; i < _msgSection.Count; i++)
{
NetMsgSection section = _msgSection[i];
int nSectionParseCount = section.Parse(buffer, offset + nParseCount, length - nParseCount);
if (nSectionParseCount == 0)
return 0;
nParseCount += nSectionParseCount;
}
return nParseCount;
}
public int GetSendBuffer(byte[] buffer, int offset, int length)
{
int readSize = 0;
for (int i = 0; i < IntCollection.Count; i++)
{
NetMsgSection section = IntCollection[i];
int size = section.GetSendBuffer(buffer, offset + readSize, length - readSize);
if (size == 0)
return 0;
readSize += size;
}
return readSize;
}
}
/// <summary>
/// 消息
/// </summary>
public class NetMsg
{
private static int _idUniverse = 0;
private int _id;
private NetMsgSectionCollection _msgSections;
private NetMsgHead _msgHead;
private int _bufferSize;
private byte[] _buffer;
public int BufferSize { get { return _bufferSize; } }
public int Type { get { return _msgHead.type; } }
public NetMsgHead Head
{
get { return _msgHead; }
}
public NetMsgSectionCollection Sections
{
get{return _msgSections;}
}
public NetMsgSection this[string sectionName]
{
get { return Sections.StrCollection[sectionName]; }
}
public NetMsg()
{
_msgSections = new NetMsgSectionCollection();
_msgHead = new NetMsgHead();
}
public NetMsg(NetMsg pNetMsg)
{
if (pNetMsg == null)
throw new Exception("NetMsg is Null");
_id = _idUniverse++;
//复制HEAD
_msgSections = new NetMsgSectionCollection();
_msgHead = new NetMsgHead(pNetMsg._msgHead);
//复制NetMsgSectionCollection
for (int i = 0; i < pNetMsg.Sections.IntCollection.Count; i++)
{
NetMsgSection pNetMsgSection = new NetMsgSection(pNetMsg.Sections.IntCollection[i]);
NetMsgSection pNetMsgSectionNew = new NetMsgSection(pNetMsgSection);
this._msgSections.Add(i, pNetMsgSectionNew.Name, pNetMsgSectionNew);
}
//复制公共项
this._bufferSize = pNetMsg.BufferSize;
}
public void Init(XElement el)
{
_msgHead.type.IntValue = Convert.ToInt32(el.Attribute("type").Value);
try
{
IEnumerable<System.Xml.Linq.XElement> elementsSection = el.Elements();
int index = 0;
_bufferSize = NetMsgHead.SIZE;
foreach (XElement elSection in elementsSection)
{
NetMsgSection section = new NetMsgSection();
int size = section.Init(elSection);
if (size == 0)
throw new Exception("NetMsg init failed because get a section 0 size");
_bufferSize += size;
_msgSections.Add(index, section.Name, section);
index++;
}
}
catch (System.Exception ex)
{
throw new Exception(string.Format("NetMsg.Init type{0}, exception:{1}", _msgHead.type.IntValue, ex.Message));
}
}
/// <summary>
/// 解析消息
/// </summary>
/// <param name="buffer">缓冲区</param>
/// <param name="offset">缓冲区偏移量</param>
/// <param name="length">从偏移量算起,还有多少个字节的有效数据</param>
/// <returns>解析了多少字节,0为失败</returns>
public int Parse(byte[] buffer, int offset, int length)
{
int nParseCount = 0;
try
{
if (buffer.Length < NetMsgHead.SIZE)
return nParseCount;
//解析消息头
_msgHead.size.IntValue = BitConverter.ToInt32(buffer, offset);
_msgHead.type.IntValue = BitConverter.ToInt32(buffer, offset + NetMsgSection.INT_SIZE);
_msgHead.endFlag.IntValue = BitConverter.ToInt32(buffer, offset + NetMsgSection.INT_SIZE * 2);
_msgHead.number.IntValue = BitConverter.ToInt32(buffer, offset + NetMsgSection.INT_SIZE * 3);
if (buffer.Length < _msgHead.size)
return nParseCount;
if (buffer.Length < length)
return nParseCount;
if (length < _msgHead.size)
return nParseCount;
//解析消息体
nParseCount = NetMsgSection.INT_SIZE * 4;
for(int i = 0; i < _msgSections.IntCollection.Count; i++)
{
NetMsgSection section = _msgSections.IntCollection[i];
int nSectionParseCount = section.Parse(buffer, offset + nParseCount, length - nParseCount);
if (nSectionParseCount == 0)
return 0;
nParseCount += nSectionParseCount;
}
}
catch (System.Exception ex)
{
MessageBox.Show(ex.InnerException.Message);
}
_bufferSize = _msgHead.size;
return nParseCount;
}
/// <summary>
/// 得消息字节流
/// </summary>
/// <returns></returns>
public byte[] GetSendBuffer()
{
if (_buffer == null)
_buffer = new byte[_bufferSize];
else if (_buffer.Length != _bufferSize)
_buffer = new byte[_bufferSize];
Head.size.IntValue = _bufferSize;
Head.GetSendBuffer(_buffer, 0, _bufferSize);
int size = _msgSections.GetSendBuffer(_buffer, NetMsgHead.SIZE, _bufferSize - NetMsgHead.SIZE);
return _buffer;
}
}
/// <summary>
/// 消息管理器,创建消息
/// </summary>
public class NetMsgProcessor
{
private Dictionary<string, NetMsg> _msgDictName;
private Dictionary<int, NetMsg> _msgDictID;
private static int nNumber = 0;
public NetMsg GetSendMsg(int type)
{
NetMsg pNetMsg = new NetMsg(_msgDictID[type]);
pNetMsg.Head.number.IntValue = nNumber++;
return pNetMsg;
}
public void Send()
{
}
public NetMsgProcessor()
{
_msgDictName = new Dictionary<string, NetMsg>();
_msgDictID = new Dictionary<int, NetMsg>();
}
/// <summary>
/// 载入消息结构配置文件
/// </summary>
/// <param name="strMSGConfigPath"></param>
public void Init(string strMSGConfigPath)
{
XElement elementRoot = XElement.Load(strMSGConfigPath);
if (elementRoot != null)
{
System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement> elements = elementRoot.Elements("Mapping");
foreach (XElement el in elements)
{
NetMsg netMsg = new NetMsg();
netMsg.Init(el);
_msgDictID.Add(netMsg.Type, netMsg);
}
}
}
/// <summary>
/// 解析缓冲到消息
/// </summary>
/// <param name="buffer"></param>
/// <param name="length">缓冲区有效数据</param>
/// <returns></returns>
public NetMsg BuildNetMsgFromBuffer(byte[] buffer, int length)
{
if (buffer == null)
return null;
if (buffer.Length < NetMsgHead.SIZE)
return null;
if (buffer.Length < length)
return null;
if (length < NetMsgHead.SIZE)
return null;
int size = BitConverter.ToInt32(buffer, 0);
if(buffer.Length >= size)
{
// 有消息了
int type = BitConverter.ToInt32(buffer, NetMsgSection.INT_SIZE);
if (!_msgDictID.ContainsKey(type))
throw new Exception("NetMsg.BuildNetMsgFromBuffer upsupport msg");
//取得消息结构
NetMsg msgTemplate = _msgDictID[type];
//根据字节流生成消息
NetMsg netMsg = new NetMsg(msgTemplate);
if (netMsg.Parse(buffer, 0, length) > 0)
return netMsg;
}
return null;
}
}
/// <summary>
/// 消息事件
/// </summary>
public class NetMsgEventArgs : System.EventArgs
{
public NetMsgEventArgs(NetMsg netMsg)
{
_netMsg = netMsg;
}
private NetMsg _netMsg;
public NetMsg NetMsg
{
get { return _netMsg; }
}
}
/// <summary>
/// 消息队列类
/// </summary>
public class NetMsgQueue
{
private Queue<NetMsg> _queue;
public EventHandler<NetMsgEventArgs> AllNewNetMsgEvent;
private Dictionary<int, EventHandler<NetMsgEventArgs>> _netMsgEvent;
public EventHandler<NetMsgEventArgs> this[int type]
{
get{
return _netMsgEvent[type];
}
}
public int Size { get { lock (_queue) { return _queue.Count; } } }
public NetMsgQueue()
{
_queue = new Queue<NetMsg>();
_netMsgEvent = new Dictionary<int, EventHandler<NetMsgEventArgs>>();
}
public void PushNetMsg(NetMsg netMsg)
{
lock (_queue)
{
_queue.Enqueue(netMsg);
}
}
public void ProcessNetMsg(int maxCount)
{
lock (_queue)
{
while (_queue.Count > 0 && maxCount > 0)
{
NetMsg netMsg = null;
netMsg = _queue.Dequeue();
if (netMsg != null)
{
if (AllNewNetMsgEvent != null)
AllNewNetMsgEvent.Invoke(this, new NetMsgEventArgs(netMsg));
int type = netMsg.Type;
if (_netMsgEvent.ContainsKey(type))
_netMsgEvent[type].Invoke(this, new NetMsgEventArgs(netMsg));
}
maxCount--;
}
}
}
}
}
“NetMsg.cs”文件包含下面这些类:
NetMsgSection类:消息元素(元素名对应元素值)
NetMsgHead类:消息头(16字节)
NetMsgSectionCollection类:消息元素集合
NetMsg类:消息类
NetMsgProcessor:消息辅助类
这些类的功能见代码注释。
三、客户通讯连接
“NetSocket.cs”文件:
NetSocket.cs
/********************************************************************
author: 田园小蛙 ourtree@live.cn
purpose: 网络连接
*********************************************************************/
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Net.Sockets;
using System.Threading;
namespace ChatClientSL
{
/// <summary>
/// 连接事件类型
/// </summary>
public enum EN_CONNECT_EVENT_TYPE
{
NONE = 0,
CONNECT_OK = 1,//连接成功
CONNECT_FAILED = 2//连接失败
}
public class NetSocket
{
//分发消息的委托
public delegate void ReceiveEventDelegate(NetMsg netMsg);
public event ReceiveEventDelegate OnReceiveEvent;
private void RaiseReceiveEvent(NetMsg netMsg)
{
if (OnReceiveEvent != null)
{
OnReceiveEvent(netMsg);
}
}
//连接事件委托
public delegate void ConnectEventDelegate(EN_CONNECT_EVENT_TYPE enEvent);
public event ConnectEventDelegate OnConnectEvent;
private void ConnectEvent(EN_CONNECT_EVENT_TYPE enEvent)
{
if (OnConnectEvent != null)
{
OnConnectEvent(enEvent);
}
}
public const int CN_RECV_BUFFER_SIZE = 1024 * 20;//接收缓冲区大小
static ManualResetEvent clientDone = new ManualResetEvent(false);
SocketAsyncEventArgs socketEventArgSend = null;
Socket netsock = null;
protected int m_nRecvLen = 0;
protected byte[] m_bufRecvMsg = new byte[CN_RECV_BUFFER_SIZE];//接收缓冲区
private NetMsgProcessor _processor;
public bool bConnected
{
get
{
if (netsock == null)
{
return false;
}
else
{
return netsock.Connected;
}
}
}
public NetMsgProcessor NetMsgProcessor
{
get { return _processor; }
}
/// <summary>
/// 初始化
/// </summary>
public NetSocket()
{
_processor = new NetMsgProcessor();
_processor.Init("MSGConfig.xml");//加载消息格式配置文件
}
/// <summary>
/// 关闭连接
/// </summary>
public void Close()
{
if (netsock != null)
{
netsock.Shutdown(SocketShutdown.Send);
netsock.Close();
clientDone.Set();
}
}
/// <summary>
/// 连接服务器
/// </summary>
/// <param name="strIP"></param>
/// <param name="nPort"></param>
public void Connect(string strIP, int nPort)
{
try
{
if (bConnected)
return;
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
DnsEndPoint hostEntry = new DnsEndPoint(strIP, nPort);
netsock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(socketEventArg_Completed);
socketEventArg.RemoteEndPoint = hostEntry;
socketEventArg.UserToken = netsock;
netsock.ConnectAsync(socketEventArg);
}
catch(Exception ex)
{
throw ex;
}
}
void socketEventArg_Completed(object sender, SocketAsyncEventArgs e)
{
switch (e.LastOperation)
{
case SocketAsyncOperation.Connect:
{
ProcessConnect(e);
}
break;
case SocketAsyncOperation.Receive:
{
ProcessReceive(e);
}
break;
default:
throw new Exception("Invalid operation completed");
}
}
/// <summary>
/// 连接完成
/// </summary>
/// <param name="e"></param>
private void ProcessConnect(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
Socket sock = e.UserToken as Socket;
socketEventArgSend = new SocketAsyncEventArgs();
socketEventArgSend.RemoteEndPoint = e.RemoteEndPoint;
socketEventArgSend.UserToken = sock;
socketEventArgSend.Completed += new EventHandler<SocketAsyncEventArgs>(socketEventArgSend_Completed);
e.SetBuffer(m_bufRecvMsg, m_nRecvLen, CN_RECV_BUFFER_SIZE - m_nRecvLen);
sock.ReceiveAsync(e);
ConnectEvent(EN_CONNECT_EVENT_TYPE.CONNECT_OK);
}
else
{
ConnectEvent(EN_CONNECT_EVENT_TYPE.CONNECT_FAILED);
}
}
/// <summary>
/// 发送完成
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void socketEventArgSend_Completed(object sender, SocketAsyncEventArgs e)
{
switch (e.LastOperation)
{
case SocketAsyncOperation.Send:
{
ProcessSend(e);
}
break;
default:
throw new Exception("Invalid Send operation completed");
}
}
/// <summary>
/// 接收到数据
/// </summary>
/// <param name="e"></param>
private void ProcessReceive(SocketAsyncEventArgs e)
{
Socket sock = e.UserToken as Socket;
if (e.BytesTransferred == 0 && e.SocketError == SocketError.Shutdown)
{
return;
}
m_nRecvLen += e.BytesTransferred;
int size = BitConverter.ToInt32(m_bufRecvMsg, 0);
while (m_nRecvLen >= size && m_nRecvLen > 0)
{
// 分发消息
NetMsg netMsg = _processor.BuildNetMsgFromBuffer(m_bufRecvMsg, m_nRecvLen);
if (netMsg != null)
{
// 清理缓存
m_nRecvLen -= netMsg.BufferSize;
System.Buffer.BlockCopy(m_bufRecvMsg, netMsg.BufferSize, m_bufRecvMsg, 0, m_nRecvLen);
RaiseReceiveEvent(netMsg);
}
}
e.SetBuffer(m_bufRecvMsg, m_nRecvLen, CN_RECV_BUFFER_SIZE - m_nRecvLen);
netsock.ReceiveAsync(e);
}
private void ProcessSend(SocketAsyncEventArgs e)
{
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="sendBuffer">字节流</param>
/// <param name="lenght">字节流长度</param>
public void SendMsg(byte[] sendBuffer, int lenght)
{
socketEventArgSend.SetBuffer(sendBuffer, 0, lenght);
netsock.SendAsync(socketEventArgSend);
}
}
}
封装的socket连接类,接收和发送字节流消息,将收到消息将通知界面处理。
ProcessReceive(SocketAsyncEventArgs e)方法处理收到的字节流,分析字节流是否够组成一个消息包,如果够就组成一个消息包并通知界面,不够就将收到的数据缓存起来。
以上就是整个客户端代码。(完)

浙公网安备 33010602011771号