首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 数据库 > 其他数据库 >

Socket编程之聊天程序 - 模拟Fins/ModBus协议通讯过程

2013-10-08 
Socket编程之聊天程序 -模拟Fins/ModBus协议通信过程 设备控制软件编程涉及到的基本通信方式主要有TCP/IP

Socket编程之聊天程序 - 模拟Fins/ModBus协议通信过程

 

设备控制软件编程涉及到的基本通信方式主要有TCP/IP与串口,用到的数据通信协议有Fins与ModBus。 更高级别的通信如.net中的Remoting与WCF在进行C/S架构软件开发时会采用。


本篇文章结合Fins/ModBus协议的指令帧结构与数据编码与解码过程,自定义了一套TcpChatter数据数据通信协议,编写了一个聊天程序,说明TCP/IP的在一个项目中应用。

本文涉及到的源代码工程项目为 - TcpChatter 后面附件提供源代码下载 ( OpenSource Code   软件版本:VS2008    语言:C#)


1 先普及几个基本概念





3  TcpChatter指令帧结构



Socket编程之聊天程序 -  模拟Fins/ModBus协议通讯过程


3.2 ModBus指令帧结构
(ModBus也具备响应帧结构)

Socket编程之聊天程序 -  模拟Fins/ModBus协议通讯过程

2.3 TcpChatter指令帧结构


TcpChatter指令 帧用于在客户端与服务端进行统一格式的数据通信。其基本构成为 : TcpChatter指令域 + TcpChatter数据域;

TcpChatter指令域构成:   命令头 + 命令请求模式+  发送者ID + 收发模式 + 收接都ID + 预留指令
TcpChatter指令域长度: 8bytes
TcpChatter数据域长度: 20kb

Socket编程之聊天程序 -  模拟Fins/ModBus协议通讯过程

3.4 TcpChatter 指令域结构 (该指令在通信过程中变换成了byte,可以进行位操作)


/// <summary> /// TcpChatter 数据通信命令格式定义 /// </summary> public struct LCmd { public int Head; // 有效命令开始标志(命令头) public int CmdMode; // 命令请求模式 public int SendID; // 发送者用户ID public int WR; // 发送或读写模式 public int RecvID; // 接收者用户ID public int Resv2; // 预留 public int Resv3; // 预留 public int Resv4; // 预留 }




 


3.5 TcpChatter 指令集


/// <summary> /// 应答请求命令 /// </summary> public enum CmdRequest { MinID = -1, Online = 0x01, // 在线请求 FixUser = 0x02, // 向固定用户发送消息请求 Flush = 0x03, // 向固定用户闪屏请求 FlushAll = 0x04, // 向所有用户闪屏请求 Broadcast = 0x05, // 广播消息请求 Offline = 0x06, // 离线请求 UpdateUsers = 0x07, // 用户列表更新请求 Success = 0x08, // 用户连接服务成功应答 InvalidUser = 0x09, // 非法用户名 - (预留) Failed = 0x0A, // 用户连接服务失败应答 InvalidCmd = 0xFF, // 非法命令包 - (预留) MaxID, }



 


4  ChatServer   -  TcpChatter服务端程序


private Dictionary<string, ClientContext> dicClientContext = new Dictionary<string, ClientContext>()


 

#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的独立客户端服务。


Socket编程之聊天程序 -  模拟Fins/ModBus协议通讯过程

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输入用户名

Socket编程之聊天程序 -  模拟Fins/ModBus协议通讯过程

ChatClient聊天界面


Socket编程之聊天程序 -  模拟Fins/ModBus协议通讯过程



附录:TcpChatter源代码下载        软件版本: VS2008    语言:C#




TcpChatter源代码.rar

热点排行