Socket編程 (異步通訊,解決Tcp粘包) - Part3


Socket編程 (異步通訊,解決Tcp粘包)

  從上一章的通訊中,我們發現如果使用Tcp連續發送消息會出現消息一起發送過來的情況,這樣給我們編程造成一定的問題,給我們的信息解析造成一定的問題。那么這篇文章就將針對以上問題給出解決方案......

 

問題一般會出現的情況如下,假設我們連續發送兩條兩天記錄("我是liger_zql"):

模擬發送示例:

 #region 測試消息發送,並匹配協議
  TcpClient client = new TcpClient();
  client.AsynConnect();
  Console.WriteLine("下面將連續發送2條測試消息...");
  Console.ReadKey();
  MessageProtocol msgPro;
  for (int i = 0; i < 2; i++)
  {
    msgPro = new MessageProtocol("我是liger_zql");
    Console.WriteLine("第{0}條:{1}", i + 1, msgPro.MessageInfo.Content);
    client.AsynSend(msgPro);
  }
  #endregion

接收端接受兩條信息會出現如下三種情況:

  1.(1)我是liger_zql(2)我是liger_zql

  2.(1)我是liger_zql我是(2)liger_zql

  3.(1)我是liger_zql我是liger_zql

通過以上三種情況,顯然2、3都不是我們想要的結果。那么如何處理這中情況呢?

解決方案:通過自定義協議...

我們可以以將信息以xml的格式發送出去,列入<protocol>content</protocol>通過正則匹配信息是否完整,如果不完整,我們可以先將本次接受信息緩存接受下一次信息,再次匹配得到相應的結果。

將信息對象轉換成一定格式的xml字符串:

   /// <summary>
    /// 文本信息
    /// </summary>
    public class MessageInfo
    {
        public string Content { get; set; }//信息內容
        public MessageInfo(string content)
        {
            this.Content = content;
        }
        public override string ToString()
        {
            return String.Format("<message Content=\"{0}\" />", this.Content);
        }
    }

    /// <summary>
    /// 文件信息
    /// </summary>
    public class RequestFile
    {
        public string Address { get; set; }//發送端Ip
        public int Port { get; set; }//端口號
        public RequestMode Mode { get; set; }//請求類
        public FileObject FileObject { get; set; }//文件詳細參數
        public RequestFile() { }
        public RequestFile(RequestMode mode, FileObject fileobject)
        {
            this.Mode = mode;
            this.FileObject = fileobject;
        }
        public RequestFile(string address, int port, RequestMode mode, FileObject fileobject)
        {
            this.Address = address;
            this.Port = port;
            this.Mode = mode;
            this.FileObject = fileobject;
        }
        public RequestFile(string address, int port, RequestMode mode, string filename, long filelength, int packetsize, long packetcount)
        {
            this.Address = address;
            this.Port = port;
            this.Mode = mode;
            this.FileObject = new FileObject(filename, filelength, packetsize, packetcount);
        }
        public override string ToString()
        {
            StringBuilder sbString = new StringBuilder();
            sbString.Append("<message ");
            sbString.Append(String.Format("Address=\"{0}\" ", Address));
            sbString.Append(String.Format("Port=\"{0}\" ", Port));
            sbString.Append(String.Format("Mode=\"{0}\" ", Mode));
            sbString.Append(String.Format("FileName=\"{0}\" ", FileObject.FileName));
            sbString.Append(String.Format("FileLength=\"{0}\" ", FileObject.FileLength));
            sbString.Append(String.Format("PacketSize=\"{0}\" ", FileObject.PacketSize));
            sbString.Append(String.Format("PacketCount=\"{0}\" ", FileObject.PacketCount));
            sbString.Append("/>");
            return sbString.ToString();
        }
    }
    /// <summary>
    /// 訂立信息協議
    /// </summary>
    public class MessageProtocol
    {
        public MessageType MessageType { get; set; }
        public MessageInfo MessageInfo { get; set; }
        public RequestFile RequestFile { get; set; }
        public MessageProtocol() { }
        public MessageProtocol(string msg)
        {
            MessageType = MessageType.text;
            MessageInfo = new MessageInfo(msg);
        }
        public MessageProtocol(RequestMode mode, FileObject fileobject)
        {
            MessageType = MessageType.file;
            RequestFile = new RequestFile(mode, fileobject);
        }
        public MessageProtocol(string address, int port, RequestMode mode, FileObject fileobject)
        {
            MessageType = MessageType.file;
            RequestFile = new RequestFile(address, port, mode, fileobject);
        }
        public override string ToString()
        {
            StringBuilder sbString = new StringBuilder();
            sbString.Append(String.Format("<protocol Type=\"{0}\">", MessageType));
            if (MessageType == MessageType.text)
            {
                sbString.Append(MessageInfo.ToString());
            }
            else
            {
                sbString.Append(RequestFile.ToString());
            }
            sbString.Append("</protocol>");
            return sbString.ToString();
        }
        public byte[] ToBytes()
        {
            return Encoding.UTF8.GetBytes(this.ToString());
        }
    }

對接收的信息通過正則進行匹配處理:

     //臨時緩存
        public string temp = string.Empty;
        //匹配協議獲取信息
        public List<MessageProtocol> HandlerString(string msg)
        {
            List<MessageProtocol> msgProList = new List<MessageProtocol>();
            if (!String.IsNullOrEmpty(temp))
            {
                msg = temp + msg;
            }
            string pattern = "(^<protocol Type=.*?>.*?</protocol>)";
            if (Regex.IsMatch(msg, pattern))
            {
                //匹配協議內容
                string match = Regex.Match(msg, pattern).Groups[0].Value;
                //將匹配的內容添加到集合
                msgProList.Add(HandlerObject(match));
                temp = string.Empty;
                //截取未匹配字符串,進行下一次匹配
                msg = msg.Substring(match.Length);
                if (!String.IsNullOrEmpty(msg))
                {
                    msgProList.AddRange(HandlerString(msg));
                }
            }
            else
            {
                temp = msg;
            }
            return msgProList;
        }

然后將該定義的協議換換成信息對象,通過對象獲取自己想要的信息。

        //將已轉成協議信息轉成對象信息
        public MessageProtocol HandlerObject(string protocol)
        {
            XmlDocument xmldoc = new XmlDocument();
            xmldoc.LoadXml(protocol);
            XmlNode root = xmldoc.DocumentElement;
            XmlNode msgnode = root.SelectSingleNode("message");
            MessageProtocol msgPro = new MessageProtocol();
            if (root.Attributes["Type"].Value == MessageType.text.ToString())
            {
                msgPro.MessageType = MessageType.text;
                msgPro.MessageInfo = new MessageInfo(msgnode.Attributes["Content"].Value);
            }
            else
            {
                msgPro.MessageType = MessageType.file;
                RequestMode mode = (RequestMode)Enum.Parse(typeof(RequestMode), msgnode.Attributes["Mode"].Value);
                FileObject fileobject = new FileObject();
                fileobject.FileName = msgnode.Attributes["FileName"].Value;
                fileobject.FileLength = Convert.ToInt64(msgnode.Attributes["FileLength"].Value);
                fileobject.PacketSize = Convert.ToInt32(msgnode.Attributes["PacketSize"].Value);
                fileobject.PacketCount = Convert.ToInt64(msgnode.Attributes["PacketCount"].Value);
                msgPro.RequestFile = new RequestFile(
                    msgnode.Attributes["Address"].Value,
                    Convert.ToInt32(msgnode.Attributes["Port"].Value),
                    mode, fileobject);
            }
            return msgPro;
        }        

最后運行結果如下:

好了Tcp粘包的問題我們解決了。下一章我們將解決Udp丟包的個人解決方案!

附上源碼:SocketProQuests.zip

作者:曾慶雷
出處:http://www.cnblogs.com/zengqinglei
本頁版權歸作者和博客園所有,歡迎轉載,但未經作者同意必須保留此段聲明, 且在文章頁面明顯位置給出原文鏈接,否則保留追究法律責任的權利


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM