标签:
上次写了一篇文章 基于networkcomms V3通信框架的c#服务器与java客户端进行通信之Protobuf探讨 其中没有解决粘包问题,抛砖引玉,文章得到了失足程序员 老师的点评,并给出了解决方案:[最多评论]java netty socket库和自定义C#socket库利用protobuf进行通信完整实例(10/591) »
于是马上开始学习,并把c#服务器端换成了我比较熟悉的networkcomms v3 c#通信框架(商业版,本文并不提供) ,以方便与已经存在的系统进行整合。
客户端没有改动,依旧使用 失足程序员 老师的netty客户端,proto的message文件也没有变化,具体可以参见上面的文章
服务器端
由于networkcomms支持与其他语言的通信,所以改动很少
修改networkcomms v3框架的源文件
《1》修改ConnectionIncomingData.cs文件:
topPacketHeader = new PacketHeader(Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.Unmanaged), packetBuilder.TotalBytesCached);
改为:
//java //指定包头大小为4个字节 packetHeaderSize = 4; if (packetBuilder.TotalBytesCached < packetHeaderSize) { if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Trace(" ... require " + packetHeaderSize + " bytes for packet header, only " + packetBuilder.TotalBytesCached + " bytes cached."); packetBuilder.TotalBytesExpected = packetHeaderSize; return; } if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Trace(" ... deserializing header using " + packetHeaderSize + " bytes, " + packetBuilder.TotalBytesCached + " bytes cached."); //根据前4个字节,获取包体的大小 int expectDateCount = packetBuilder.GetUnmangeHeaderByte(0, 4); //已经接收到足够的包头的大小 topPacketHeader = new PacketHeader(Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.Unmanaged), expectDateCount);
《2》修改PacketBuilder.cs文件
添加如下方法:
//返回消息头字节数据 public int GetUnmangeHeaderByte(int startIndex, int length) { lock (Locker) { byte[] returnArray = new byte[length]; int runningTotal = 0, writeTotal = 0; int startingPacketIndex; int firstPacketStartIndex = 0; //First find the correct starting packet for (startingPacketIndex = 0; startingPacketIndex < packets.Count; startingPacketIndex++) { if (startIndex - runningTotal <= packetActualBytes[startingPacketIndex]) { firstPacketStartIndex = startIndex - runningTotal; break; } else runningTotal += packetActualBytes[startingPacketIndex]; } //Copy the bytes of interest for (int i = startingPacketIndex; i < packets.Count; i++) { if (i == startingPacketIndex) { if (length > packetActualBytes[i] - firstPacketStartIndex) //If we want from some starting point to the end of the packet Buffer.BlockCopy(packets[i], firstPacketStartIndex, returnArray, writeTotal, packetActualBytes[i] - firstPacketStartIndex); else { //We only want part of the packet Buffer.BlockCopy(packets[i], firstPacketStartIndex, returnArray, writeTotal, length); writeTotal += length; break; } writeTotal = packetActualBytes[i] - firstPacketStartIndex; } else { //We are no longer on the first packet if (packetActualBytes[i] + writeTotal >= length) { //We have reached the last packet of interest Buffer.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) throw new Exception("Not enough data available in packetBuilder to complete request. Requested " + length.ToString() + " bytes but only " + writeTotal.ToString() + " bytes were copied."); //返回字节数组 //由于java返回的字序不同,需要进行反转 Array.Reverse(returnArray); return BitConverter.ToInt32(returnArray, 0); //双方统一编码 暂时不使用 //return bytesToInt(returnArray,0); } }
服务器端主要的处理方法是:
//array 收到字节 NetworkComms.AppendGlobalIncomingUnmanagedPacketHandler((header, connection, array) => { //接收的字节数组中,前四个字节是消息类型 后面的字节对应相应的类 MemoryStream ms1 = new MemoryStream(array); BinaryReader buffers = new BinaryReader(ms1, UTF8Encoding.Default); //消息类型 int msgID = ReadInt(buffers.ReadBytes(4)); byte[] body = buffers.ReadBytes(array.Length - 4); MemoryStream ms2 = new MemoryStream(); ms2.Write(body, 0, body.Length); ms2.Position = 0; if (msgID == (int)Sz.Test.ProtoMessage.TestMessage.Proto_Login.ReqLogin) { Sz.Test.ProtoMessage.TestMessage.ReqLoginMessage msg = Serializer.Deserialize<Sz.Test.ProtoMessage.TestMessage.ReqLoginMessage>(ms2); //在服务器上做一下记录 LogInfo.LogMessage("数据收到" + msg.userName, "客户端发来的数据123"); Sz.Test.ProtoMessage.TestMessage.ResTipMessage tip = new Sz.Test.ProtoMessage.TestMessage.ResTipMessage(); if (msg.userName == "admin" && msg.userPwd == "admin") { tip.msg = "服务器端返回消息 登录完成"; } else { tip.msg = "服务器端返回消息 用户名或者密码错误"; } //序列化要返回的ResTipMessage类为字节数组 byte[] buffer = Serialize(tip); //加上消息头和消息类型 byte[] resBuffer = Encoder(new Sz.Network.SocketPool.SocketMessage((int)Sz.Test.ProtoMessage.TestMessage.Proto_Login.ResTip, buffer)); //返回给客户端 connection.SendUnmanagedBytes(resBuffer); } else if (msgID == (int)Sz.Test.ProtoMessage.TestMessage.Proto_Login.ReqChat) { //构建消息 Sz.Test.ProtoMessage.TestMessage.ReqChatMessage chatMessage = Serializer.Deserialize<Sz.Test.ProtoMessage.TestMessage.ReqChatMessage>(ms2); //在服务器上做一下记录 LogInfo.LogMessage("聊天消息数据收到" + chatMessage.msg, "客户端发来的聊天消息"); Sz.Test.ProtoMessage.TestMessage.ResChatMessage chat = new Sz.Test.ProtoMessage.TestMessage.ResChatMessage(); chat.msg = "服务器发送的聊天消息"; //序列化要返回的ResTipMessage类为字节数组 byte[] buffer = Serialize(chat); //加上消息头和消息类型 byte[] resBuffer = Encoder(new Sz.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(); });
//由失足程序员老师提供 public byte[] Encoder(SocketMessage msg) { MemoryStream ms = new MemoryStream(); BinaryWriter bw = new BinaryWriter(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(); return ms.ToArray(); }
上面代码中使用的代码很多都是直接借用 失足程序员 老师的代码
服务器端界面:
为便于测试,收到的消息,写入了文本文件:
客户端界面:
java端代码 (由失足程序员老师开发,我下载下来的不能直接编译,把有一个命名空间大写换成小写就好了,这里把改动的发上来)
c#端代码(不包含通信框架)
NetworkComms c#通信框架与Java的Netty框架通信 解决粘包问题
标签:
原文地址:http://www.cnblogs.com/csdev/p/4731463.html