小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Socket編程之聊天程序

 herowuking 2015-04-26

 

設(shè)備控制軟件編程涉及到的基本通信方式主要有TCP/IP與串口,用到的數(shù)據(jù)通信協(xié)議有Fins與ModBus。 更高級別的通信如.net中的Remoting與WCF在進行C/S架構(gòu)軟件開發(fā)時會采用。


本篇文章結(jié)合Fins/ModBus協(xié)議的指令幀結(jié)構(gòu)與數(shù)據(jù)編碼與解碼過程,自定義了一套TcpChatter數(shù)據(jù)數(shù)據(jù)通信協(xié)議,編寫了一個聊天程序,說明TCP/IP的在一個項目中應(yīng)用。


本文涉及到的源代碼工程項目為 - TcpChatter 后面附件提供源代碼下載 ( OpenSource Code   軟件版本:VS2008    語言:C#)


1 先普及幾個基本概念



Socket

接觸C/C++的人都知道,編寫網(wǎng)絡(luò)程序會用到Socket,對于Socket編程,其基本編程思想就是使用listen,accept,connect,send與write等幾個操作來實現(xiàn)客戶端與服務(wù)端的通信。
對于使用C#的程序員,.net為我們提供了Socket類來編寫服務(wù)程序,提供了TcpClient來編寫客戶端程序。我們只需要知道如何使用listen,accept,connect,send與write操作就能編寫我們需要的網(wǎng)絡(luò)程序了。

簡單的說:
Socket是支持TCP/IP協(xié)議的網(wǎng)絡(luò)通信的基本操作單元,它是建立在TCP/IP協(xié)議上的一組編程接口,是我們編寫代碼,使用TCP/IP進行數(shù)據(jù)通信的入口,它是對TCP/IP協(xié)議棧的抽像,等效于是TCP/IP協(xié)議棧提供的對外編程接口,在.net中,只是說這個編程接口的實現(xiàn)由Micosoft為我們完成,我們做的唯一工作只是使用這些接口就能在我們的應(yīng)用程序間進行TCP/IP通信了


TCP/IP - Transmission Control Protocol/Internet Protocol

傳輸控制協(xié)議/因特網(wǎng)互聯(lián)協(xié)議,是Internet互聯(lián)網(wǎng)絡(luò)的基礎(chǔ),由傳輸層的TCP協(xié)議與網(wǎng)絡(luò)層的IP協(xié)議構(gòu)成。網(wǎng)絡(luò)層負責(zé)在節(jié)點與節(jié)點之間傳送數(shù)據(jù)包(IP數(shù)據(jù)包),該IP數(shù)據(jù)包由TCP協(xié)議來進行組裝,IP數(shù)據(jù)包再通過它的下層協(xié)議以太網(wǎng)協(xié)議 (IEEE802)在光纖上進行傳輸,從而將不同的信息從一臺計算機傳送到了另一臺計算機。

對于程序員,我們編寫的程序要在不同的計算機間進行數(shù)據(jù)通信,可以通過Socket編程來使用TCP/IP,從而將我們的數(shù)據(jù)從一臺計算機傳到了另一臺計算機。


PLC - Programmable Logic Controller
可編程邏輯控制器,一種數(shù)字運算操作的電子系統(tǒng),專為工業(yè)環(huán)境應(yīng)用而設(shè)計,與計算機一樣,可以把它看成是一種用于工業(yè)控制的計算機,它也有自己的編程語言 - T形圖,可以通過T形圖編程來實現(xiàn)各種設(shè)備的控制。

Fins - Factory Interface Network Service
Fins協(xié)議是歐姆龍開發(fā)的用于工業(yè)自動化控制網(wǎng)絡(luò)的指令/響應(yīng)通信協(xié)議,它借助TCP/IP協(xié)議與串口通信協(xié)議,通過發(fā)送Fins指令實現(xiàn)在各種網(wǎng)絡(luò)間的無縫通信,這里主工是PC與PLC的通信,我們PC可以通過發(fā)送Fins指令與PLC進行通信。

ModBus
Modbus是由Modicon公司于1979年發(fā)明,是全球第一個真正用于工業(yè)現(xiàn)場的總線協(xié)議。在工業(yè)控制系統(tǒng)中,目前ModBus已經(jīng)成為一通用工業(yè)標(biāo)準(zhǔn).
Modbus主要應(yīng)用于電子控制器上的一種通用數(shù)據(jù)協(xié)議,借助TCP/IP協(xié)議與串口通信協(xié)議,通過發(fā)送Modbus指令實現(xiàn)在各種設(shè)備之間的通信。目前公司的溫控表與PC間的通信采用ModBus。


2 TcpChatter消息傳輸結(jié)構(gòu)



2.1 TcpChatter軟件架構(gòu)

與傳統(tǒng)的軟件系統(tǒng)一樣,TcpChatter采用C/S架構(gòu),即客戶端/服務(wù)端架構(gòu),Client參與通信會話,Server不參與通信會話,只負責(zé)將Client的消息通過Server進行轉(zhuǎn)發(fā),從而實現(xiàn)Client-Client間的通信。
整個TcpChatter的代碼結(jié)構(gòu)由 ChatServer + ChatClient構(gòu)成

2.2 TcpChatter消息處理

如下圖所示TcpChatterMessageTransaction,為TcpChatter系統(tǒng)中使用的消息處理結(jié)構(gòu)。下圖異展示了從 客戶端A←→客戶端B 的消息傳遞過程。

2.3 TcpChatter消息處理原理

在不同的客戶端進行通信,客戶端通過將消息封裝為TcpChatter指定的數(shù)據(jù)格式 [TcpChater指令 + 數(shù)據(jù)]  然后發(fā)送給服務(wù)端程序ChatServer,服務(wù)端程序再將消息轉(zhuǎn)發(fā)給指定的客戶端,客戶端收到消息后解析TcpChater數(shù)據(jù)包然后做其它的處理。




3  TcpChatter指令幀結(jié)構(gòu)



目前工業(yè)控制中的溫控主流采用串口通信,使用數(shù)據(jù)通信協(xié)議為ModBus協(xié)議,而與底層PLC通信則多采用Fins協(xié)議。下面分別解釋ModBus協(xié)議與Fins協(xié)議的指令幀結(jié)構(gòu)與TcpChatter指令幀結(jié)構(gòu)。


TcpChatter指令 幀用于在客戶端與服務(wù)端進行統(tǒng)一格式的數(shù)據(jù)通信。其基本構(gòu)成為 : TcpChatter指令域 + TcpChatter數(shù)據(jù)域;

Fins協(xié)議與ModBus協(xié)議原理基本一樣,其各自的指令幀結(jié)構(gòu) 基本有2部分構(gòu)成: 指令域 + 數(shù)據(jù)域
指令域為Fins協(xié)議與ModBus協(xié)議定義的數(shù)據(jù)通信格式,指令域字節(jié)長度也不一樣.比如Fins指令有效指令域(Fin頭 + Fin指令域)為12個節(jié)字,數(shù)據(jù)域長度能到2000字節(jié),而ModBus協(xié)議有效指令域(地址 + 功能碼 + CRC校驗碼)為4個字節(jié),數(shù)據(jù)域為(256-4)字節(jié)或(260-4)字節(jié)。
數(shù)據(jù)域為發(fā)送的真正數(shù)據(jù)。由于受限于硬件設(shè)備通信的數(shù)據(jù)速率, 在串口與TCP/IP通信中, 指令域 + 數(shù)據(jù)域的總長度是有限制的。我們通過PC與設(shè)備進行通信,實際上是在反復(fù)的發(fā)送這些 數(shù)據(jù)包與解析這些數(shù)據(jù)包,從而達到PC與設(shè)備信息交互的目的。

熟悉嵌入式的人都知道,我們編寫代碼跟設(shè)備進行通信,基本是在通過操作設(shè)備的寄存器對寄存器進行讀寫從而達到控制設(shè)備狀態(tài)與獲取設(shè)備狀態(tài)的目的。寄存器普通的有8位與16位,我們最常見的溫控表是8位寄存器,正好一個byte,每一位都可以看成是硬件上的一個I/O,我們通過操作這些位從而操作了對應(yīng)的硬件的I/O狀態(tài) (0/1),設(shè)備跟據(jù)這些I/O狀態(tài)做出相應(yīng)的動作。



3.1 Fins指令幀結(jié)構(gòu)
(
Fins響應(yīng)幀(應(yīng)答幀結(jié)構(gòu))結(jié)構(gòu)與Fins指令幀結(jié)構(gòu)類似)





3.2 ModBus指令幀結(jié)構(gòu)
(ModBus也具備響應(yīng)幀結(jié)構(gòu))



2.3 TcpChatter指令幀結(jié)構(gòu)


TcpChatter指令 幀用于在客戶端與服務(wù)端進行統(tǒng)一格式的數(shù)據(jù)通信。其基本構(gòu)成為 : TcpChatter指令域 + TcpChatter數(shù)據(jù)域;

TcpChatter指令域構(gòu)成:   命令頭 + 命令請求模式+  發(fā)送者ID + 收發(fā)模式 + 收接都ID + 預(yù)留指令
TcpChatter指令域長度: 8bytes
TcpChatter數(shù)據(jù)域長度: 20kb



3.4 TcpChatter 指令域結(jié)構(gòu) (該指令在通信過程中變換成了byte,可以進行位操作)


 

  1. /// <summary>  
  2.     /// TcpChatter 數(shù)據(jù)通信命令格式定義  
  3.     /// </summary>  
  4.     public struct LCmd  
  5.     {  
  6.         public int Head;            // 有效命令開始標(biāo)志(命令頭)  
  7.         public int CmdMode;         // 命令請求模式  
  8.         public int SendID;          // 發(fā)送者用戶ID  
  9.         public int WR;              // 發(fā)送或讀寫模式  
  10.         public int RecvID;          // 接收者用戶ID  
  11.         public int Resv2;           // 預(yù)留  
  12.         public int Resv3;           // 預(yù)留  
  13.         public int Resv4;           // 預(yù)留  
  14.     }  




 


3.5 TcpChatter 指令集



 

  1. /// <summary>  
  2.    /// 應(yīng)答請求命令  
  3.    /// </summary>  
  4.    public enum CmdRequest  
  5.    {  
  6.        MinID = -1,               
  7.        Online      = 0x01,     // 在線請求  
  8.        FixUser     = 0x02,     // 向固定用戶發(fā)送消息請求  
  9.        Flush       = 0x03,     // 向固定用戶閃屏請求  
  10.        FlushAll    = 0x04,     // 向所有用戶閃屏請求  
  11.        Broadcast   = 0x05,     // 廣播消息請求  
  12.        Offline     = 0x06,     // 離線請求  
  13.        UpdateUsers = 0x07,     // 用戶列表更新請求  
  14.        Success     = 0x08,     // 用戶連接服務(wù)成功應(yīng)答  
  15.        InvalidUser = 0x09,     // 非法用戶名 - (預(yù)留)  
  16.        Failed      = 0x0A,     // 用戶連接服務(wù)失敗應(yīng)答  
  17.        InvalidCmd  = 0xFF,     // 非法命令包 - (預(yù)留)  
  18.        MaxID,  
  19.    }  



 


4  ChatServer   -  TcpChatter服務(wù)端程序



4.1 ChatAgent服務(wù)端  獲取客戶端獨立的Socket連接請求

在TcpChatter項目中,通過TcpListener創(chuàng)建一個監(jiān)聽端口獲取Socket連接請求,不同的客戶端連接請求(TcpClient的Connect),服務(wù)端會創(chuàng)建客戶端各自獨立的Socket對象,在ChatAgent中通過ClientContext管理了所有連接客戶端的Socket,消息的轉(zhuǎn)發(fā)通過各自不同的Socket進行。


4.1.1 ClientContext

ChatServer服務(wù)端通過dicClientContext 表保存了所有連接客戶端的信息,當(dāng)客戶端異?;螂x線,其客戶端資源會被從Server端移除。

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


 

  1. #region InnerClass - Client Instance Context  
  2.   
  3.         class ClientContext  
  4.         {  
  5.             internal ClientContext()  
  6.             {  
  7.             }  
  8.             internal byte[] Buf { get; set; }  
  9.             internal byte[] HeadBuf { get; set; }  
  10.             internal byte[] DataBuf { get; set; }  
  11.             internal int UserID { get; set; }  
  12.             internal string UserName { get; set; }  
  13.             internal Thread MsgHandle { get; set; }  
  14.             internal Socket Skt { get; set; }  
  15.         }  
  16.         #endregion  




 

 

4.2 監(jiān)聽連接請求與消息監(jiān)聽流程圖

如下圖所示,ChatServer啟動了一個監(jiān)聽端口,當(dāng)有新的連接請求達到,會生成新的Socket對象,同時啟動Socket服務(wù)消息監(jiān)聽線程:

服務(wù)監(jiān)聽線程:客戶端連接請求線程,有新的客戶端成功連接服務(wù)端時會生成新的Socket對象。該線程為所有客戶端服務(wù)。

Socket服務(wù)線程:服務(wù)監(jiān)聽線程的子線程,用于處理服務(wù)端使用Socket轉(zhuǎn)發(fā)的消息。為指定Socket的獨立客戶端服務(wù)。




4.3  IChatAgent服務(wù)代理接口

TcpChatter的服務(wù)端接口含2個屬性與2個接口

Name : 服務(wù)器名稱
IsAlive:服務(wù)器激活狀態(tài)
StartChatServer: 啟動服務(wù)接口
StopChatServer:關(guān)閉服務(wù)接口


  1. public interface IChatAgent  
  2.    {  
  3.        string Name { get;}  
  4.        bool IsAlive { get; }  
  5.        bool StartChatServer();  
  6.        bool StopChatServer();  
  7.    }  




4.4 服務(wù)監(jiān)聽線程

  1. /// <summary>  
  2.         /// 客戶端 消息處理主線程  
  3.         /// </summary>  
  4.         private void MessageProcessThread()  
  5.         {  
  6.             ClientContext client = null;  
  7.             while (IsAlive)  
  8.             {  
  9.                 try  
  10.                 {  
  11.                     byte[] useNameBuf = new byte[MAXBUFSIZE];  
  12.   
  13.                     // 監(jiān)聽連接請求對像  
  14.                     Socket msgSkt = tcpListener.AcceptSocket();  
  15.   
  16.                     // 等待上線請求  
  17.                     int actualLens = msgSkt.Receive(useNameBuf);          
  18.   
  19.                     // 獲取實際數(shù)據(jù)長度  
  20.                     byte[] buf = this.CopyArrayFrom(useNameBuf, actualLens);  
  21.   
  22.                     byte[] header = null;  
  23.                     byte[] dataBuf = null;  
  24.   
  25.                     // 解析上線請求命令包 : 上線請求 + 用戶名  
  26.                     LErrorCode error = this.ResolveDataPackage(buf, out header, out dataBuf);  
  27.                     if (error != LErrorCode.Success)  
  28.                     {  
  29.                         Console.Error.WriteLine("ResolveDataPackage failed! LErrorCode = {0}", error);  
  30.                         continue;  
  31.                     }  
  32.   
  33.                     // 校驗命令頭  
  34.                     if (header[0] != ProtocolMsg.LCML)  
  35.                     {  
  36.                         Console.Error.WriteLine("Invalid cmmand head = {0}", header[0]);  
  37.                         continue;  
  38.                     }  
  39.                     // 是否是上線請求   -  第 1 個命令必須是: 上線請求命令包 + 用戶名  
  40.                     CmdRequest request = (CmdRequest)header[1];  
  41.                     if (request != CmdRequest.Online)  
  42.                     {  
  43.                         Console.Error.WriteLine("Invalid request command! Cmd = {0}", request);  
  44.                         continue;  
  45.                     }  
  46.   
  47.                     // 校驗用戶名的合法性  
  48.                     string user = this.GetStringFrom(dataBuf);  
  49.                     if (!CheckUserInvalid(user))  
  50.                     {  
  51.                         string msg = "User name " + user + " has been existed in TcpChatter system! User tried to join chatting failed!";  
  52.   
  53.                         this.currentRequest = CmdRequest.Failed;  
  54.                         this.currentRight = LProtocolRight.WR;  
  55.   
  56.                         msgSkt.Send(CurrentCmd);  
  57.                         Console.Error.WriteLine(msg);  
  58.                         continue;  
  59.                     }  
  60.   
  61.   
  62.                     // 服務(wù)端生成用戶信息 并動態(tài)分配獨立用戶ID  
  63.                     client = new ClientContext();  
  64.   
  65.                     client.UserID = ChatAgent.ActiveID;  
  66.                     client.UserName = user;  
  67.                     client.Skt = msgSkt;  
  68.                     dicClientContext.Add(user, client);  
  69.   
  70.                     this.currentRequest = CmdRequest.Success;  
  71.                     this.currentRight = LProtocolRight.WR;  
  72.                     this.senderID = client.UserID;  
  73.   
  74.                     // 發(fā)送登陸成功命令  
  75.                     msgSkt.Send(CurrentCmd);     
  76.   
  77.                     string sysmsg = string.Format("[系統(tǒng)消息]\n新用戶 {0} 在[{1}] 已成功連接服務(wù)器[當(dāng)前在線人數(shù): {2}]\r\n\r\n",   
  78.                         user, DateTime.Now, dicClientContext.Count);  
  79.                     Console.WriteLine(SysInfo.Timestamp + sysmsg);  
  80.   
  81.                     Thread.Sleep(1000);         // Sleep 1s  
  82.   
  83.                     Thread handle = new Thread(() =>  
  84.                         {  
  85.                             if (PreMessageProcess(client, sysmsg))  
  86.                             {  
  87.                                 // 啟用用戶 消息監(jiān)聽線程  
  88.                                 SubMsgProcessThread(client, sysmsg);  
  89.                             }  
  90.                         });  
  91.                     handle.Start();  
  92.   
  93.                     dicClientContext[user].MsgHandle = handle;  
  94.                 }  
  95.                 catch (SocketException se)  
  96.                 {  
  97.                     Innerlog.Error(dcrlringType, "SocketException Current user =  " + client.UserName + " was offline!", se);  
  98.                 }  
  99.                 catch (Exception ex)  
  100.                 {  
  101.                     Innerlog.Error(dcrlringType, "Exception Current user =  " + client.UserName + " was offline!", ex);  
  102.                 }  
  103.             }  
  104.         }  


 


4.5 Socket服務(wù)消息線程

 

  1. /// <summary>  
  2.        /// 用戶消息監(jiān)聽線程  
  3.        /// </summary>  
  4.        /// <param name="client"></param>  
  5.        private void SubMsgProcessThread(ClientContext clientx, string message)  
  6.        {  
  7.            ClientContext client = clientx;  
  8.            while (true)  
  9.            {  
  10.                try  
  11.                {  
  12.                    byte[] msgBuf = new byte[MAXBUFSIZE];  
  13.                    // 監(jiān)聽 并接收數(shù)據(jù)  
  14.                    int actualLens = client.Skt.Receive(msgBuf);  
  15.                    byte[] totalBuf = this.CopyArrayFrom(msgBuf, actualLens);  
  16.   
  17.                    byte[] headBuf = null;  
  18.                    byte[] dataBuf = null;  
  19.   
  20.                    // 解析命令包  
  21.                    LErrorCode error = this.ResolveDataPackage(totalBuf, out headBuf, out dataBuf);  
  22.   
  23.                    client.HeadBuf = headBuf;  
  24.                    client.DataBuf = dataBuf;  
  25.                    client.Buf = totalBuf;  
  26.   
  27.                    if (error != LErrorCode.Success) continue;  
  28.   
  29.                    // 是否是有效命令  
  30.                    if (headBuf[0] != ProtocolMsg.LCML) continue;  
  31.   
  32.                      
  33.                    CmdRequest cmdHead = (CmdRequest)headBuf[1];  
  34.                    if (cmdHead == CmdRequest.InvalidCmd ||  
  35.                        cmdHead == CmdRequest.MaxID ||  
  36.                        cmdHead == CmdRequest.MinID)  
  37.                    {  
  38.                        Console.Error.WriteLine("Invalid Send Message!");  
  39.                        continue;  
  40.                    }  
  41.                    else  
  42.                    {  
  43.                        // 用戶消息轉(zhuǎn)發(fā)  
  44.                        UserMessageProcess(client);  
  45.                    }  
  46.                }  
  47.                catch (Exception ex)  
  48.                {  
  49.                    ClientOfflineProcess(client);  
  50.                    //Innerlog.Error(dcrlringType, "Current user =  " + client.UserName + " was offline!", ex);  
  51.                    Thread.CurrentThread.Abort();  
  52.                }  
  53.            }  
  54.        }  


 

 

4.6  序列化

序列化與反序列化在TcpChatter中被用于消息的編碼與解碼。編碼與解碼過程可以詳細的參看ChatAgent代碼內(nèi)部實現(xiàn)。

序列化描述了持久化一個對像對流的過程,反序列化則與此過程相反,表示從流到對象的重建過程。在.net中,消息傳遞,數(shù)據(jù)存儲都大量的用到了序列化與反序列化的操作。


由于客戶端與服務(wù)端消息傳輸以byte字節(jié)流的方式進行傳輸,當(dāng)在客戶端之前傳遞對象時需要對對象進行序列化。如傳遞客戶端在線列表,該列表是一個Dictionary,在客戶端與服務(wù)端進行Dictionary傳遞需用到序列化與反序列化。

見 TcpChatter CHTCommon中的SysInfo.cs

 

  1. public static byte[] SerializeGraph<T>(T graph)  


 
4.7  反序列化
見 TcpChatter CHTCommon中的SysInfo.cs

  1. public static T DeserializeGraph<T>(byte[] bytes)  

 


4.8 ChatClient



4.9  ChatServer 服務(wù)端程序

  1. class Program  
  2.     {  
  3.         static void Main(string[] args)  
  4.         {  
  5.             int beginner = Win32Manager.TickCounter;  
  6.             Console.WriteLine("\r\n-----------------------------------------------------------------------");  
  7.             Console.WriteLine(SysInfo.Timestamp + "ChatServer is starting........\r\n");  
  8.   
  9.             IChatAgent agent = new ChatAgent(null);  
  10.             int linkCounter = 0;  
  11.             bool isStarted = agent.StartChatServer();  
  12.             while (!agent.IsAlive)  
  13.             {  
  14.                 if (linkCounter++ > 10)  
  15.                 {  
  16.                     Console.WriteLine(SysInfo.Timestamp + "ChatServer start failed! Try LinkCounter = {0}",linkCounter);  
  17.                     break;  
  18.                 }  
  19.                 Thread.Sleep(100);  
  20.             }  
  21.   
  22.             Console.WriteLine(SysInfo.Timestamp + "Total ElapsedTime = {0}ms", (Win32Manager.TickCounter - beginner));  
  23.             if (linkCounter < 10) Console.WriteLine(SysInfo.Timestamp + "ChatServer is running........");  
  24.   
  25.             Console.WriteLine("-----------------------------------------------------------------------\r\n");  
  26.             Application.Run();  
  27.         }  
  28.     }  


 


5  TcpChatter運行測試



運行 ChatServer,如圖1所示,輸入端口服務(wù)啟動
運行ChatClient,輸入用戶名就可以聊天了。當(dāng)有新用戶上線或新用戶離線時,ChatServer控制臺會顯示當(dāng)前用在線用戶的情況。



運行ChatClient輸入用戶名



ChatClient聊天界面






附錄:TcpChatter源代碼下載        軟件版本: VS2008    語言:C#




TcpChatter源代碼.rar

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多