Socket编程之聊天程序 - 模拟Fins/ModBus协议通信过程
设备控制软件编程涉及到的基本通信方式主要有TCP/IP与串口,用到的数据通信协议有Fins与ModBus。 更高级别的通信如.net中的Remoting与WCF在进行C/S架构软件开发时会采用。
本篇文章结合Fins/ModBus协议的指令帧结构与数据编码与解码过程,自定义了一套TcpChatter数据数据通信协议,编写了一个聊天程序,说明TCP/IP的在一个项目中应用。
本文涉及到的源代码工程项目为 - TcpChatter 后面附件提供源代码下载 ( OpenSource Code 软件版本:VS2008 语言:C#)
1 先普及几个基本概念
3.2 ModBus指令帧结构
(ModBus也具备响应帧结构)
2.3 TcpChatter指令帧结构
TcpChatter指令 帧用于在客户端与服务端进行统一格式的数据通信。其基本构成为 : TcpChatter指令域 + TcpChatter数据域;
TcpChatter指令域构成: 命令头 + 命令请求模式+ 发送者ID + 收发模式 + 收接都ID + 预留指令
TcpChatter指令域长度: 8bytes
TcpChatter数据域长度: 20kb
3.4 TcpChatter 指令域结构 (该指令在通信过程中变换成了byte,可以进行位操作)
3.5 TcpChatter 指令集
4 ChatServer - TcpChatter服务端程序
#region InnerClass - Client Instance Context class ClientContext { internal ClientContext() { } internal byte[] Buf { get; set; } internal byte[] HeadBuf { get; set; } internal byte[] DataBuf { get; set; } internal int UserID { get; set; } internal string UserName { get; set; } internal Thread MsgHandle { get; set; } internal Socket Skt { get; set; } } #endregion
4.2 监听连接请求与消息监听流程图
如下图所示,ChatServer启动了一个监听端口,当有新的连接请求达到,会生成新的Socket对象,同时启动Socket服务消息监听线程:
服务监听线程:客户端连接请求线程,有新的客户端成功连接服务端时会生成新的Socket对象。该线程为所有客户端服务。
Socket服务线程:服务监听线程的子线程,用于处理服务端使用Socket转发的消息。为指定Socket的独立客户端服务。
4.3 IChatAgent服务代理接口
TcpChatter的服务端接口含2个属性与2个接口
Name : 服务器名称
IsAlive:服务器激活状态
StartChatServer: 启动服务接口
StopChatServer:关闭服务接口
public interface IChatAgent { string Name { get;} bool IsAlive { get; } bool StartChatServer(); bool StopChatServer(); }
4.4 服务监听线程
/// <summary> /// 客户端 消息处理主线程 /// </summary> private void MessageProcessThread() { ClientContext client = null; while (IsAlive) { try { byte[] useNameBuf = new byte[MAXBUFSIZE]; // 监听连接请求对像 Socket msgSkt = tcpListener.AcceptSocket(); // 等待上线请求 int actualLens = msgSkt.Receive(useNameBuf); // 获取实际数据长度 byte[] buf = this.CopyArrayFrom(useNameBuf, actualLens); byte[] header = null; byte[] dataBuf = null; // 解析上线请求命令包 : 上线请求 + 用户名 LErrorCode error = this.ResolveDataPackage(buf, out header, out dataBuf); if (error != LErrorCode.Success) { Console.Error.WriteLine("ResolveDataPackage failed! LErrorCode = {0}", error); continue; } // 校验命令头 if (header[0] != ProtocolMsg.LCML) { Console.Error.WriteLine("Invalid cmmand head = {0}", header[0]); continue; } // 是否是上线请求 - 第 1 个命令必须是: 上线请求命令包 + 用户名 CmdRequest request = (CmdRequest)header[1]; if (request != CmdRequest.Online) { Console.Error.WriteLine("Invalid request command! Cmd = {0}", request); continue; } // 校验用户名的合法性 string user = this.GetStringFrom(dataBuf); if (!CheckUserInvalid(user)) { string msg = "User name " + user + " has been existed in TcpChatter system! User tried to join chatting failed!"; this.currentRequest = CmdRequest.Failed; this.currentRight = LProtocolRight.WR; msgSkt.Send(CurrentCmd); Console.Error.WriteLine(msg); continue; } // 服务端生成用户信息 并动态分配独立用户ID client = new ClientContext(); client.UserID = ChatAgent.ActiveID; client.UserName = user; client.Skt = msgSkt; dicClientContext.Add(user, client); this.currentRequest = CmdRequest.Success; this.currentRight = LProtocolRight.WR; this.senderID = client.UserID; // 发送登陆成功命令 msgSkt.Send(CurrentCmd); string sysmsg = string.Format("[系统消息]\n新用户 {0} 在[{1}] 已成功连接服务器[当前在线人数: {2}]\r\n\r\n", user, DateTime.Now, dicClientContext.Count); Console.WriteLine(SysInfo.Timestamp + sysmsg); Thread.Sleep(1000); // Sleep 1s Thread handle = new Thread(() => { if (PreMessageProcess(client, sysmsg)) { // 启用用户 消息监听线程 SubMsgProcessThread(client, sysmsg); } }); handle.Start(); dicClientContext[user].MsgHandle = handle; } catch (SocketException se) { Innerlog.Error(dcrlringType, "SocketException Current user = " + client.UserName + " was offline!", se); } catch (Exception ex) { Innerlog.Error(dcrlringType, "Exception Current user = " + client.UserName + " was offline!", ex); } } }
4.5 Socket服务消息线程
/// <summary> /// 用户消息监听线程 /// </summary> /// <param name="client"></param> private void SubMsgProcessThread(ClientContext clientx, string message) { ClientContext client = clientx; while (true) { try { byte[] msgBuf = new byte[MAXBUFSIZE]; // 监听 并接收数据 int actualLens = client.Skt.Receive(msgBuf); byte[] totalBuf = this.CopyArrayFrom(msgBuf, actualLens); byte[] headBuf = null; byte[] dataBuf = null; // 解析命令包 LErrorCode error = this.ResolveDataPackage(totalBuf, out headBuf, out dataBuf); client.HeadBuf = headBuf; client.DataBuf = dataBuf; client.Buf = totalBuf; if (error != LErrorCode.Success) continue; // 是否是有效命令 if (headBuf[0] != ProtocolMsg.LCML) continue; CmdRequest cmdHead = (CmdRequest)headBuf[1]; if (cmdHead == CmdRequest.InvalidCmd || cmdHead == CmdRequest.MaxID || cmdHead == CmdRequest.MinID) { Console.Error.WriteLine("Invalid Send Message!"); continue; } else { // 用户消息转发 UserMessageProcess(client); } } catch (Exception ex) { ClientOfflineProcess(client); //Innerlog.Error(dcrlringType, "Current user = " + client.UserName + " was offline!", ex); Thread.CurrentThread.Abort(); } } }
4.6 序列化
序列化与反序列化在TcpChatter中被用于消息的编码与解码。编码与解码过程可以详细的参看ChatAgent代码内部实现。
序列化描述了持久化一个对像对流的过程,反序列化则与此过程相反,表示从流到对象的重建过程。在.net中,消息传递,数据存储都大量的用到了序列化与反序列化的操作。
由于客户端与服务端消息传输以byte字节流的方式进行传输,当在客户端之前传递对象时需要对对象进行序列化。如传递客户端在线列表,该列表是一个Dictionary,在客户端与服务端进行Dictionary传递需用到序列化与反序列化。
见 TcpChatter CHTCommon中的SysInfo.cs
public static byte[] SerializeGraph<T>(T graph)
4.7 反序列化
见 TcpChatter CHTCommon中的SysInfo.cs
public static T DeserializeGraph<T>(byte[] bytes)
4.8 ChatClient
略
4.9 ChatServer 服务端程序
class Program { static void Main(string[] args) { int beginner = Win32Manager.TickCounter; Console.WriteLine("\r\n-----------------------------------"); Console.WriteLine(SysInfo.Timestamp + "ChatServer is starting........\r\n"); IChatAgent agent = new ChatAgent(null); int linkCounter = 0; bool isStarted = agent.StartChatServer(); while (!agent.IsAlive) { if (linkCounter++ > 10) { Console.WriteLine(SysInfo.Timestamp + "ChatServer start failed! Try LinkCounter = {0}",linkCounter); break; } Thread.Sleep(100); } Console.WriteLine(SysInfo.Timestamp + "Total ElapsedTime = {0}ms", (Win32Manager.TickCounter - beginner)); if (linkCounter < 10) Console.WriteLine(SysInfo.Timestamp + "ChatServer is running........"); Console.WriteLine("-----------------------------------\r\n"); Application.Run(); } }
5 TcpChatter运行测试
ChatClient聊天界面
附录:TcpChatter源代码下载 软件版本: VS2008 语言:C#