【飞秋】TCP粘包
?首先申明一下,寫的這個東西注重的是一個思想~,代碼只是參考,并不能直接運行.下面進入正題
這兩天在弄Silverlight版本的SOCKET網絡編程,參考了菩提樹下的楊過的例子,寫了一段程序
自己也遇到了一些問題,例如TCP協議的粘包,想了個解決方案,興沖沖的,GOOGLE了一下發現類似的思想很多,不過決定還是把代碼貼出來吧
?
/// <summary>
??????? /// 發送消息
??????? /// </summary>
??????? /// <param name="msgOrSql">消息類容</param>
??????? /// <returns></returns>
??????? public bool MsgTo(MsgOrSql msgOrSql)
??????? {
??????????? try
??????????? {
??????????????? SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
??????????????? MemoryStream ms = new MemoryStream();
??????????????? byte[] dataLen = new byte[sizeof(long)];
??????????????? ms.Write(dataLen, 0, dataLen.Length);//先占住前八位字符
??????????????? string jsonString = JsonConvert.SerializeObject(msgOrSql);//JSON的序列化方式,讀者也可以用其它的序列化方式
??????????????? byte[] msgByte = Encoding.UTF8.GetBytes(jsonString);//寫入需要的流
??????????????? ms.Write(msgByte,0,msgByte.Length);
??????????????? ms.Position = 0;//從頭寫
??????????????? dataLen = BitConverter.GetBytes(ms.Length - dataLen.Length);//有效長度
??????????????? ms.Write(dataLen,0,dataLen.Length);// 將有效長度寫入流中
??????????????? byte[] data = ms.ToArray();//需要發送的字節流
??????????????? ms.Close();????????????
??????????????? socketEventArg.SetBuffer(data,0,data.Length);
??????????????? socketEventArg.RemoteEndPoint = clickSocket.RemoteEndPoint;
??????????????? clickSocket.SendAsync(socketEventArg);
??????????? }
??????????? catch (Exception ee)
??????????? {
??????????????? Console.Write(ee);
??????????? }
??????????? return false;
??????? }
以上是發送代碼,下面貼一下接收方的代碼
?
? private int msgLength;//消息總長
??????? private int yishouLength;//已收消息長度
??????? byte[] lstReceiveBytes = new byte[] { };//已經接受的數據
? /// <summary>
??????? /// 接受服務端發來的數據-回調處理
??????? /// </summary>
??????? /// <param name="sender"></param>
??????? /// <param name="e"></param>
??????? private void OnSocketReceiveCompleted(object sender, SocketAsyncEventArgs e)
??????? {
??????????? if (e.Buffer == null)
??????????? {
??????????????? return;
??????????? }
??????????? MemoryStream ms = new MemoryStream();
??????????? if (lstReceiveBytes.Length == 0)//判斷緩存是否存在數據
??????????? {
//緩存中沒有數據
??????????????? msgLength = int.Parse(BitConverter.ToInt64(e.Buffer, 0).ToString());//獲得數據長度,也就是發送端寫入的字符長度
??????????????? ms.Write(e.Buffer, 8, e.BytesTransferred - 8);//將剩余的字符流寫入緩存,除了長度字符之外,所以是從第八位開始讀取數據流的
??????????? }
??????????? else
??????????? {
//緩存中有數據了
??????????????? ms.Write(lstReceiveBytes,0,lstReceiveBytes.Length);//將原有的數據寫入緩存
??????????????? ms.Write(e.Buffer, 0, e.BytesTransferred);//將當前接收數據寫入緩存
??????????? }
??????????? yishouLength += e.BytesTransferred;//已收數據長度+=當前收的數據長度
??????????? lstReceiveBytes = ms.ToArray();//緩存中的數據替換
??????????? ms.Close();
??????????? GetMsgByByte();//調用處理緩存數據的方法
??????????? try
??????????? {
??????????????? //繼續異步地從服務端 Socket 接收數據(類似長連接)
??????????????? if (clickSocket != null && clickSocket.Connected)
??????????????? {
??????????????????? clickSocket.ReceiveAsync(e);
??????????????? }
??????????????? else
??????????????? {
??????????????????? Console.Write("無法連接到服務器...請刷新后再試...");
??????????????? }
??????????? }
??????????? catch (Exception ex)
??????????? {
??????????????? Console.Write(ex.Message.ToString());
??????????? }
??????? }
??????? private void GetMsgByByte()//處理緩存數據的方法
??????? {
??????????? try
??????????? {
??????????????? if (lstReceiveBytes.Length >= msgLength) //如果已接受的數據長度大于等于定義的數據長度 也就是說可以處理一條消息了
??????????????? {
byte[] msgByte = new byte[] { };//當前需要處理的包信息
??????????????????? msgByte = lstReceiveBytes;//默認為緩存中的信息
??????????????????? if (lstReceiveBytes.Length > msgLength) //處于粘包狀態
??????????????????? {
//將字符流分為兩個部分,一部分為當前的一條消息,分離出來當前處理,另一部分為下一步操作的數據流,寫入緩存進行下一步操作,(或許寫的有點模糊)
MemoryStream m = new MemoryStream();
??????????????????????? m.Write(lstReceiveBytes,0,msgLength);//取字符流中的一條傳送完畢的消息
??????????????????????? msgByte = m.ToArray();
??????????????????????? m.Close();
??????????????????????? m = new MemoryStream();
??????????????????????? m.Write(lstReceiveBytes, msgLength, lstReceiveBytes.Length - msgLength);//根據當前的數據長度讀取一條數據,所屬的所有字符流
??????????????????????? byte[] bb = m.ToArray();
??????????????????????? msgLength = int.Parse(BitConverter.ToInt64(bb, 0).ToString());//獲得下一條信息的字符流的長度
??????????????????????? m.Close();
??????????????????????? m = new MemoryStream();
??????????????????????? m.Write(lstReceiveBytes, msgLength, lstReceiveBytes.Length - msgLength);//獲得下一條信息的字符流,(不包含長度的字符流)
??????????????????????? lstReceiveBytes = m.ToArray();//替換緩存中的數據
??????????????????????? m.Close();
???????? }
??????????????????? string jsonString = Encoding.UTF8.GetString(lstReceiveBytes.ToArray(), 0, msgLength);//JSON的序列化方式,大家可以不用理會
??????????????????? MsgOrSql msgOrSql = new MsgOrSql();
??????????????????? msgOrSql = JsonConvert.DeserializeObject<MsgOrSql>(jsonString);
??????????????????? if (msgOrSql.dbInfo != null)
??????????????????? {
??????????????????????? if (msgOrSql.dbInfo.FangFaMin != null)
??????????????????????? {
??????????????????????????? DBHelper.listDbInfo.Add(msgOrSql.dbInfo.FangFaMin, msgOrSql.dbInfo);
??????????????????????? }
??????????????????? }
??????????????????? if (msgOrSql.msgInfo != null)
??????????????????? {
??????????????????????? if (msgOrSql.msgInfo.Id != null)//發送者的ID不為空
??????????????????????? {
??????????????????????????? UserHelper.listMsg.Add(msgOrSql.msgInfo);
??????????????????????? }
??????????????????? }
??????????????????? if (lstReceiveBytes.Length > msgLength)
??????????????????? {
??????????????????????? GetMsgByByte();//遞歸調用處理字符流的方法
??????????????????? }else
{
return;
}
//字符流處理完畢,初始化接受消息的一些信息
??????????????????? msgLength = 0;
??????????????????? yishouLength = 0;
??????????????????? lstReceiveBytes=new byte[0];
??????????????? }
???????????????
??????????? }
??????????? catch (Exception ex)
??????????? {
??????????????? Console.Write(ex);
??????????? }
??????? }
兩個方法,一個是接受的回調函數,另外一個是處理字符流的方法
?
代碼寫的可能有些問題,COPY下去也運行不了,不過主要的思想應該是表現出來了,相信有些功底的人都能看得懂
發送消息之前,將消息打包,消息頭之前添加該消息的字符長度,接收方接受消息之后根據字符長度,判斷消息是否處理完畢,如果出現粘包,則繼續根據下一條消息的長度,處理下一條消息
?
希望對讀者有所啟發吧
關注技術文章飛秋:http://www.freeeim.com/,24小時專業轉載。
總結
- 上一篇: 『飞秋』WCF热门问题编程示例
- 下一篇: 『飞鸽传书』WindowsPhone支持