问题描述
上次写了一篇文章其中没有解决粘包问题,抛砖引玉,文章得到了失足程序员老师的点评,并给出了解决方案:»于是马上开始学习,并把c#服务器端换成了我比较熟悉的networkcommsv3c#通信框架(商业版,本文并不提供),以方便与已经存在的系统进行整合。客户端没有改动,依旧使用失足程序员老师的netty客户端,proto的message文件也没有变化,具体可以参见上面的文章服务器端由于networkcomms支持与其他语言的通信,所以改动很少修改networkcommsv3框架的源文件《1》修改ConnectionIncomingData.cs文件:topPacketHeader=newPacketHeader(Enum.GetName(typeof(ReservedPacketType),ReservedPacketType.Unmanaged),packetBuilder.TotalBytesCached);
改为://java//指定包头大小为4个字节packetHeaderSize=4;if(packetBuilder.TotalBytesCached<packetHeaderSize){if(NetworkComms.LoggingEnabled)NetworkComms.Logger.Trace("...require"+packetHeaderSize+"bytesforpacketheader,only"+packetBuilder.TotalBytesCached+"bytescached.");packetBuilder.TotalBytesExpected=packetHeaderSize;return;}if(NetworkComms.LoggingEnabled)NetworkComms.Logger.Trace("...deserializingheaderusing"+packetHeaderSize+"bytes,"+packetBuilder.TotalBytesCached+"bytescached.");//根据前4个字节,获取包体的大小intexpectDateCount=packetBuilder.GetUnmangeHeaderByte(0,4);//已经接收到足够的包头的大小topPacketHeader=newPacketHeader(Enum.GetName(typeof(ReservedPacketType),ReservedPacketType.Unmanaged),expectDateCount);
《2》修改PacketBuilder.cs文件添加如下方法://返回消息头字节数据publicintGetUnmangeHeaderByte(intstartIndex,intlength){lock(Locker){byte[]returnArray=newbyte[length];intrunningTotal=0,writeTotal=0;intstartingPacketIndex;intfirstPacketStartIndex=0;//Firstfindthecorrectstartingpacketfor(startingPacketIndex=0;startingPacketIndex<packets.Count;startingPacketIndex++){if(startIndex-runningTotal<=packetActualBytes[startingPacketIndex]){firstPacketStartIndex=startIndex-runningTotal;break;}elserunningTotal+=packetActualBytes[startingPacketIndex];}//Copythebytesofinterestfor(inti=startingPacketIndex;i<packets.Count;i++){if(i==startingPacketIndex){if(length>packetActualBytes[i]-firstPacketStartIndex)//IfwewantfromsomestartingpointtotheendofthepacketBuffer.BlockCopy(packets[i],firstPacketStartIndex,returnArray,writeTotal,packetActualBytes[i]-firstPacketStartIndex);else{//WeonlywantpartofthepacketBuffer.BlockCopy(packets[i],firstPacketStartIndex,returnArray,writeTotal,length);writeTotal+=length;break;}writeTotal=packetActualBytes[i]-firstPacketStartIndex;}else{//Wearenolongeronthefirstpacketif(packetActualBytes[i]+writeTotal>=length){//WehavereachedthelastpacketofinterestBuffer.BlockCopy(packets[i],0,returnArray,writeTotal,length-writeTotal);writeTotal+=length-writeTotal;break;}else{Buffer.BlockCopy(packets[i],0,returnArray,writeTotal,packetActualBytes[i]);writeTotal+=packetActualBytes[i];}}}if(writeTotal!=length)thrownewException("NotenoughdataavailableinpacketBuildertocompleterequest.Requested"+length.ToString()+"bytesbutonly"+writeTotal.ToString()+"byteswerecopied.");//返回字节数组//由于java返回的字序不同,需要进行反转Array.Reverse(returnArray);returnBitConverter.ToInt32(returnArray,0);//双方统一编码暂时不使用//returnbytesToInt(returnArray,0);}}
服务器端主要的处理方法是://array收到字节NetworkComms.AppendGlobalIncomingUnmanagedPacketHandler((header,connection,array)=>{//接收的字节数组中,前四个字节是消息类型后面的字节对应相应的类MemoryStreamms1=newMemoryStream(array);BinaryReaderbuffers=newBinaryReader(ms1,UTF8Encoding.Default);//消息类型intmsgID=ReadInt(buffers.ReadBytes(4));byte[]body=buffers.ReadBytes(array.Length-4);MemoryStreamms2=newMemoryStream();ms2.Write(body,0,body.Length);ms2.Position=0;if(msgID==(int)Sz.Test.ProtoMessage.TestMessage.Proto_Login.ReqLogin){Sz.Test.ProtoMessage.TestMessage.ReqLoginMessagemsg=Serializer.Deserialize<Sz.Test.ProtoMessage.TestMessage.ReqLoginMessage>(ms2);//在服务器上做一下记录LogInfo.LogMessage("数据收到"+msg.userName,"客户端发来的数据123");Sz.Test.ProtoMessage.TestMessage.ResTipMessagetip=newSz.Test.ProtoMessage.TestMessage.ResTipMessage();if(msg.userName=="admin"&&msg.userPwd=="admin"){tip.msg="服务器端返回消息登录完成";}else{tip.msg="服务器端返回消息用户名或者密码错误";}//序列化要返回的ResTipMessage类为字节数组byte[]buffer=Serialize(tip);//加上消息头和消息类型byte[]resBuffer=Encoder(newSz.Network.SocketPool.SocketMessage((int)Sz.Test.ProtoMessage.TestMessage.Proto_Login.ResTip,buffer));//返回给客户端connection.SendUnmanagedBytes(resBuffer);}elseif(msgID==(int)Sz.Test.ProtoMessage.TestMessage.Proto_Login.ReqChat){//构建消息Sz.Test.ProtoMessage.TestMessage.ReqChatMessagechatMessage=Serializer.Deserialize<Sz.Test.ProtoMessage.TestMessage.ReqChatMessage>(ms2);//在服务器上做一下记录LogInfo.LogMessage("聊天消息数据收到"+chatMessage.msg,"客户端发来的聊天消息");Sz.Test.ProtoMessage.TestMessage.ResChatMessagechat=newSz.Test.ProtoMessage.TestMessage.ResChatMessage();chat.msg="服务器发送的聊天消息";//序列化要返回的ResTipMessage类为字节数组byte[]buffer=Serialize(chat);//加上消息头和消息类型byte[]resBuffer=Encoder(newSz.Network.SocketPool.SocketMessage((int)Sz.Test.ProtoMessage.TestMessage.Proto_Login.ResChat,buffer));//返回给客户端connection.SendUnmanagedBytes(resBuffer);}ms1.Close();ms2.Close();buffers.Close();ms1.Dispose();ms2.Dispose();buffers.Dispose();});
//由失足程序员老师提供publicbyte[]Encoder(SocketMessagemsg){MemoryStreamms=newMemoryStream();BinaryWriterbw=newBinaryWriter(ms,UTF8Encoding.Default);byte[]msgBuffer=msg.MsgBuffer;if(msgBuffer!=null){//数据长度转为java可以识别bw.Write(WriterInt(msgBuffer.Length+4));//消息类型转为java可以识别bw.Write(WriterInt(msg.MsgID));bw.Write(msgBuffer);}else{bw.Write(WriterInt(0));}bw.Close();ms.Close();bw.Dispose();ms.Dispose();returnms.ToArray();}
上面代码中使用的代码很多都是直接借用 失足程序员 老师的代码服务器端界面:为便于测试,收到的消息,写入了文本文件:客户端界面:(由失足程序员老师开发,我下载下来的不能直接编译,把有一个命名空间大写换成小写就好了,这里把改动的发上来)(不包含通信框架)