【飞秋】TCP粘包
?首先申明一下,寫的這個東西注重的是一個思想~,代碼只是參考,并不能直接運行.下面進(jìn)入正題
這兩天在弄Silverlight版本的SOCKET網(wǎng)絡(luò)編程,參考了菩提樹下的楊過的例子,寫了一段程序
自己也遇到了一些問題,例如TCP協(xié)議的粘包,想了個解決方案,興沖沖的,GOOGLE了一下發(fā)現(xiàn)類似的思想很多,不過決定還是把代碼貼出來吧
?
/// <summary>
??????? /// 發(fā)送消息
??????? /// </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();//需要發(fā)送的字節(jié)流
??????????????? ms.Close();????????????
??????????????? socketEventArg.SetBuffer(data,0,data.Length);
??????????????? socketEventArg.RemoteEndPoint = clickSocket.RemoteEndPoint;
??????????????? clickSocket.SendAsync(socketEventArg);
??????????? }
??????????? catch (Exception ee)
??????????? {
??????????????? Console.Write(ee);
??????????? }
??????????? return false;
??????? }
以上是發(fā)送代碼,下面貼一下接收方的代碼
?
? private int msgLength;//消息總長
??????? private int yishouLength;//已收消息長度
??????? byte[] lstReceiveBytes = new byte[] { };//已經(jīng)接受的數(shù)據(jù)
? /// <summary>
??????? /// 接受服務(wù)端發(fā)來的數(shù)據(jù)-回調(diào)處理
??????? /// </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)//判斷緩存是否存在數(shù)據(jù)
??????????? {
//緩存中沒有數(shù)據(jù)
??????????????? msgLength = int.Parse(BitConverter.ToInt64(e.Buffer, 0).ToString());//獲得數(shù)據(jù)長度,也就是發(fā)送端寫入的字符長度
??????????????? ms.Write(e.Buffer, 8, e.BytesTransferred - 8);//將剩余的字符流寫入緩存,除了長度字符之外,所以是從第八位開始讀取數(shù)據(jù)流的
??????????? }
??????????? else
??????????? {
//緩存中有數(shù)據(jù)了
??????????????? ms.Write(lstReceiveBytes,0,lstReceiveBytes.Length);//將原有的數(shù)據(jù)寫入緩存
??????????????? ms.Write(e.Buffer, 0, e.BytesTransferred);//將當(dāng)前接收數(shù)據(jù)寫入緩存
??????????? }
??????????? yishouLength += e.BytesTransferred;//已收數(shù)據(jù)長度+=當(dāng)前收的數(shù)據(jù)長度
??????????? lstReceiveBytes = ms.ToArray();//緩存中的數(shù)據(jù)替換
??????????? ms.Close();
??????????? GetMsgByByte();//調(diào)用處理緩存數(shù)據(jù)的方法
??????????? try
??????????? {
??????????????? //繼續(xù)異步地從服務(wù)端 Socket 接收數(shù)據(jù)(類似長連接)
??????????????? if (clickSocket != null && clickSocket.Connected)
??????????????? {
??????????????????? clickSocket.ReceiveAsync(e);
??????????????? }
??????????????? else
??????????????? {
??????????????????? Console.Write("無法連接到服務(wù)器...請刷新后再試...");
??????????????? }
??????????? }
??????????? catch (Exception ex)
??????????? {
??????????????? Console.Write(ex.Message.ToString());
??????????? }
??????? }
??????? private void GetMsgByByte()//處理緩存數(shù)據(jù)的方法
??????? {
??????????? try
??????????? {
??????????????? if (lstReceiveBytes.Length >= msgLength) //如果已接受的數(shù)據(jù)長度大于等于定義的數(shù)據(jù)長度 也就是說可以處理一條消息了
??????????????? {
byte[] msgByte = new byte[] { };//當(dāng)前需要處理的包信息
??????????????????? msgByte = lstReceiveBytes;//默認(rèn)為緩存中的信息
??????????????????? if (lstReceiveBytes.Length > msgLength) //處于粘包狀態(tài)
??????????????????? {
//將字符流分為兩個部分,一部分為當(dāng)前的一條消息,分離出來當(dāng)前處理,另一部分為下一步操作的數(shù)據(jù)流,寫入緩存進(jìn)行下一步操作,(或許寫的有點模糊)
MemoryStream m = new MemoryStream();
??????????????????????? m.Write(lstReceiveBytes,0,msgLength);//取字符流中的一條傳送完畢的消息
??????????????????????? msgByte = m.ToArray();
??????????????????????? m.Close();
??????????????????????? m = new MemoryStream();
??????????????????????? m.Write(lstReceiveBytes, msgLength, lstReceiveBytes.Length - msgLength);//根據(jù)當(dāng)前的數(shù)據(jù)長度讀取一條數(shù)據(jù),所屬的所有字符流
??????????????????????? 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();//替換緩存中的數(shù)據(jù)
??????????????????????? 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)//發(fā)送者的ID不為空
??????????????????????? {
??????????????????????????? UserHelper.listMsg.Add(msgOrSql.msgInfo);
??????????????????????? }
??????????????????? }
??????????????????? if (lstReceiveBytes.Length > msgLength)
??????????????????? {
??????????????????????? GetMsgByByte();//遞歸調(diào)用處理字符流的方法
??????????????????? }else
{
return;
}
//字符流處理完畢,初始化接受消息的一些信息
??????????????????? msgLength = 0;
??????????????????? yishouLength = 0;
??????????????????? lstReceiveBytes=new byte[0];
??????????????? }
???????????????
??????????? }
??????????? catch (Exception ex)
??????????? {
??????????????? Console.Write(ex);
??????????? }
??????? }
兩個方法,一個是接受的回調(diào)函數(shù),另外一個是處理字符流的方法
?
代碼寫的可能有些問題,COPY下去也運行不了,不過主要的思想應(yīng)該是表現(xiàn)出來了,相信有些功底的人都能看得懂
發(fā)送消息之前,將消息打包,消息頭之前添加該消息的字符長度,接收方接受消息之后根據(jù)字符長度,判斷消息是否處理完畢,如果出現(xiàn)粘包,則繼續(xù)根據(jù)下一條消息的長度,處理下一條消息
?
希望對讀者有所啟發(fā)吧
關(guān)注技術(shù)文章飛秋:http://www.freeeim.com/,24小時專業(yè)轉(zhuǎn)載。
總結(jié)
- 上一篇: 『飞秋』WCF热门问题编程示例
- 下一篇: Unable to generate a