這幾天在博客園上看到好幾個寫Java和C#的socket通信的帖子。但是都為指出其中關鍵點。
C# socket通信組件有很多,在vs 使用nuget搜索socket組件有很多類似的。本人使用的是自己開發的一套組件。
Java socket通信的組件也有很多,常用的大多數都是用的mina或者netty。游戲行業使用也是居多。
關於socket的底層寫法,實在太多,我就不在BB。
這裡我想說,C#和C++或者叫VC++把是使用小端序作為字節序。而java使用的是大端序作為字節序。
也就是說比如一個int占用四個字節,java的字節序和c#的字節序是相反的,java的int四個字節第一個字節在數組的最後一個。C#是第一個。
也就是說如果java端正常發送一個int的字節序給C#,需要翻轉一次端緒。反之也是一樣的。一句話來概括的話就是高位在前還是低位在前的問題。
C#輸出數字 int 4 的字節序。為了保證c#下面絕對是是int所以加入了強制int轉化。默認的話可能是byte
java的默認輸出,這裡使用的是netty的默認框架。進行的int4的字節序輸出
高位和低位表示法完全不同。
java下面如果傳輸字符串,那麼必須要先把字符串轉化成byte數組,然後獲取數組長度,在字節序裡面壓入int表示的數組長度,然後在然如byte數組。不管你的字符串多長。
而C#也是相同做法。但是唯一不同的是數組的長度表示法不同。微軟經過了字節壓縮的。用字節的前7位表示長度。第8位表示下一個字節是否也是表示長度的字節,值需要與128位於。
從而減少字節的消耗。
現在一般如果我們在java和C#中無論是哪一個語言作為服務器。架設socket通信基准。其中另外一方都要妥協字節序反轉問題。
大多數情況下我們也許通信的要求不高,或許把一些類或者參數通過json格式化以後傳輸給對方。但是在這一條消息的傳輸中,一般會有兩個int需要字節序。最少也要一個字節序。
一個字節序int表示消息長度。另外一個字節序表示消息協議。
如果消息協議都放到json裡面沒有問題。但是消息長度是必不可少的。因為你需要知道在網絡環境中,消息壓棧,然後等待系統發出是有可能兩條消息一同發送的。也或者消息發送後由於網絡阻塞,前後相差好幾秒的消息同一時間達到。
這就是所謂的粘包。
我這裡就不表演了。
還有另外一種通信方式,就是通過protobuf進行字節序的序列化,和反序列,官方支持java,第三方支持C#。這個組件可以減少字節流。達到省流量,減少網絡資源消耗的問題。
例如一個long的類型值是1常規發送需要8個字節,64位。發送。如果改用protobuf的話只需要1字節8位就能發送。
同樣的問題,無論你使用哪一種序列化方式,都需要消息長度和消息協議號。
C#下面對int的反轉讀取。
1 /// <summary> 2 /// 讀取大端序的int 3 /// </summary> 4 /// <param name="value"></param> 5 public int ReadInt(byte[] intbytes) 6 { 7 Array.Reverse(intbytes); 8 return BitConverter.ToInt32(intbytes, 0); 9 } 10 11 /// <summary> 12 /// 寫入大端序的int 13 /// </summary> 14 /// <param name="value"></param> 15 public byte[] WriterInt(int value) 16 { 17 byte[] bs = BitConverter.GetBytes(value); 18 Array.Reverse(bs); 19 return bs; 20 }
粘包問題解決。
C#代碼
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 /** 9 * 10 * @author 失足程序員 11 * @Blog http://www.cnblogs.com/ty408/ 12 * @mail [email protected] 13 * @phone 13882122019 14 * 15 */ 16 namespace Sz.Network.SocketPool 17 { 18 public class MarshalEndian : IMarshalEndian 19 { 20 21 public enum JavaOrNet 22 { 23 Java, 24 Net, 25 } 26 27 public MarshalEndian() 28 { 29 30 } 31 32 public static JavaOrNet JN = JavaOrNet.Net; 33 34 /// <summary> 35 /// 讀取大端序的int 36 /// </summary> 37 /// <param name="value"></param> 38 public int ReadInt(byte[] intbytes) 39 { 40 Array.Reverse(intbytes); 41 return BitConverter.ToInt32(intbytes, 0); 42 } 43 44 /// <summary> 45 /// 寫入大端序的int 46 /// </summary> 47 /// <param name="value"></param> 48 public byte[] WriterInt(int value) 49 { 50 byte[] bs = BitConverter.GetBytes(value); 51 Array.Reverse(bs); 52 return bs; 53 } 54 55 //用於存儲剩余未解析的字節數 56 private List<byte> _LBuff = new List<byte>(2); 57 58 //字節數常量一個消息id4個字節 59 const long ConstLenght = 4L; 60 61 public void Dispose() 62 { 63 this.Dispose(true); 64 GC.SuppressFinalize(this); 65 } 66 67 protected virtual void Dispose(bool flag1) 68 { 69 if (flag1) 70 { 71 IDisposable disposable = this._LBuff as IDisposable; 72 if (disposable != null) { disposable.Dispose(); } 73 } 74 } 75 76 public byte[] Encoder(SocketMessage msg) 77 { 78 MemoryStream ms = new MemoryStream(); 79 BinaryWriter bw = new BinaryWriter(ms, UTF8Encoding.Default); 80 byte[] msgBuffer = msg.MsgBuffer; 81 82 if (msgBuffer != null) 83 { 84 switch (JN) 85 { 86 case JavaOrNet.Java: 87 bw.Write(WriterInt(msgBuffer.Length + 4)); 88 bw.Write(WriterInt(msg.MsgID)); 89 break; 90 case JavaOrNet.Net: 91 bw.Write((Int32)(msgBuffer.Length + 4)); 92 bw.Write(msg.MsgID); 93 break; 94 } 95 96 bw.Write(msgBuffer); 97 } 98 else 99 { 100 switch (JN) 101 { 102 case JavaOrNet.Java: 103 bw.Write(WriterInt(0)); 104 break; 105 case JavaOrNet.Net: 106 bw.Write((Int32)0); 107 break; 108 } 109 } 110 bw.Close(); 111 ms.Close(); 112 bw.Dispose(); 113 ms.Dispose(); 114 return ms.ToArray(); 115 } 116 117 public List<SocketMessage> Decoder(byte[] buff, int len) 118 { 119 //拷貝本次的有效字節 120 byte[] _b = new byte[len]; 121 Array.Copy(buff, 0, _b, 0, _b.Length); 122 buff = _b; 123 if (this._LBuff.Count > 0) 124 { 125 //拷貝之前遺留的字節 126 this._LBuff.AddRange(_b); 127 buff = this._LBuff.ToArray(); 128 this._LBuff.Clear(); 129 this._LBuff = new List<byte>(2); 130 } 131 List<SocketMessage> list = new List<SocketMessage>(); 132 MemoryStream ms = new MemoryStream(buff); 133 BinaryReader buffers = new BinaryReader(ms, UTF8Encoding.Default); 134 try 135 { 136 byte[] _buff; 137 Label_0073: 138 //判斷本次解析的字節是否滿足常量字節數 139 if ((buffers.BaseStream.Length - buffers.BaseStream.Position) < ConstLenght) 140 { 141 _buff = buffers.ReadBytes((int)(buffers.BaseStream.Length - buffers.BaseStream.Position)); 142 this._LBuff.AddRange(_buff); 143 } 144 else 145 { 146 long offset = 0; 147 switch (JN) 148 { 149 case JavaOrNet.Java: 150 offset = ReadInt(buffers.ReadBytes(4)); 151 break; 152 case JavaOrNet.Net: 153 offset = buffers.ReadInt32(); 154 break; 155 } 156 157 //剩余字節數大於本次需要讀取的字節數 158 if (offset <= (buffers.BaseStream.Length - buffers.BaseStream.Position)) 159 { 160 int msgID = 0; 161 switch (JN) 162 { 163 case JavaOrNet.Java: 164 msgID = ReadInt(buffers.ReadBytes(4)); 165 break; 166 case JavaOrNet.Net: 167 msgID = buffers.ReadInt32(); 168 break; 169 } 170 _buff = buffers.ReadBytes((int)(offset - 4)); 171 list.Add(new SocketMessage(msgID, _buff)); 172 goto Label_0073; 173 } 174 else 175 { 176 //剩余字節數剛好小於本次讀取的字節數 存起來,等待接受剩余字節數一起解析 177 buffers.BaseStream.Seek(ConstLenght, SeekOrigin.Current); 178 _buff = buffers.ReadBytes((int)(buffers.BaseStream.Length - buffers.BaseStream.Position)); 179 this._LBuff.AddRange(_buff); 180 } 181 } 182 } 183 catch { } 184 finally 185 { 186 buffers.Close(); 187 if (buffers != null) { buffers.Dispose(); } 188 ms.Close(); 189 if (ms != null) { ms.Dispose(); } 190 } 191 return list; 192 } 193 } 194 }
java netty
1 /* 2 * To change this license header, choose License Headers in Project Properties. 3 * To change this template file, choose Tools | Templates 4 * and open the template in the editor. 5 */ 6 package sz.network.socketpool.nettypool; 7 8 import io.netty.buffer.ByteBuf; 9 import io.netty.buffer.Unpooled; 10 import io.netty.channel.ChannelHandlerContext; 11 import io.netty.handler.codec.ByteToMessageDecoder; 12 import java.nio.ByteOrder; 13 import java.util.ArrayList; 14 import java.util.List; 15 import org.apache.log4j.Logger; 16 17 /** 18 * 解碼器 19 */ 20 class NettyDecoder extends ByteToMessageDecoder { 21 22 private static final Logger logger = Logger.getLogger(NettyDecoder.class); 23 24 private byte ZreoByteCount = 0; 25 private ByteBuf bytes; 26 private final ByteOrder endianOrder = ByteOrder.LITTLE_ENDIAN; 27 private long secondTime = 0; 28 private int reveCount = 0; 29 30 public NettyDecoder() { 31 32 } 33 34 ByteBuf bytesAction(ByteBuf inputBuf) { 35 ByteBuf bufferLen = Unpooled.buffer(); 36 if (bytes != null) { 37 bufferLen.writeBytes(bytes); 38 bytes = null; 39 } 40 bufferLen.writeBytes(inputBuf); 41 return bufferLen; 42 } 43 44 /** 45 * 留存無法讀取的byte等待下一次接受的數據包 46 * 47 * @param bs 數據包 48 * @param startI 起始位置 49 * @param lenI 結束位置 50 */ 51 void bytesAction(ByteBuf intputBuf, int startI, int lenI) { 52 if (lenI - startI > 0) { 53 bytes = Unpooled.buffer(); 54 bytes.writeBytes(intputBuf, startI, lenI); 55 } 56 } 57 58 @Override 59 protected void decode(ChannelHandlerContext chc, ByteBuf inputBuf, List<Object> outputMessage) { 60 if (System.currentTimeMillis() - secondTime < 1000L) { 61 reveCount++; 62 } else { 63 secondTime = System.currentTimeMillis(); 64 reveCount = 0; 65 } 66 67 if (reveCount > 50) { 68 logger.error("發送消息過於頻繁"); 69 chc.disconnect(); 70 return; 71 } 72 73 if (inputBuf.readableBytes() > 0) { 74 ZreoByteCount = 0; 75 //重新組裝字節數組 76 ByteBuf buffercontent = bytesAction(inputBuf); 77 List<NettyMessageBean> megsList = new ArrayList<>(0); 78 for (;;) { 79 //讀取 消息長度(short)和消息ID(int) 需要 8 個字節 80 if (buffercontent.readableBytes() >= 8) { 81 ///讀取消息長度 82 int len = buffercontent.readInt(); 83 if (buffercontent.readableBytes() >= len) { 84 int messageid = buffercontent.readInt();///讀取消息ID 85 ByteBuf buf = buffercontent.readBytes(len - 4);//讀取可用字節數; 86 megsList.add(new NettyMessageBean(chc, messageid, buf.array())); 87 //第二次重組 88 if (buffercontent.readableBytes() > 0) { 89 bytesAction(buffercontent, buffercontent.readerIndex(), buffercontent.readableBytes()); 90 buffercontent = Unpooled.buffer(); 91 buffercontent.writeBytes(bytes); 92 continue; 93 } else { 94 break; 95 } 96 } 97 ///重新設置讀取進度 98 buffercontent.setIndex(buffercontent.readableBytes() - 2, inputBuf.readableBytes()); 99 } 100 ///緩存預留的字節 101 bytesAction(buffercontent, buffercontent.readerIndex(), buffercontent.readableBytes()); 102 break; 103 } 104 outputMessage.addAll(megsList); 105 } else { 106 ZreoByteCount++; 107 if (ZreoByteCount >= 3) { 108 //todo 空包處理 考慮連續三次空包,斷開鏈接 109 logger.error("decode 空包處理 連續三次空包"); 110 chc.close(); 111 } 112 } 113 } 114 }
這是我封裝的部分代碼,因為現目前公司的開發組織架構為,java是服務器端。U3D 使用C#是客戶端開發。所以考慮性能問題,是C#妥協進行字節序反轉操作~!
就不在添加調試和測試代碼和結果因為覺得沒多少意義~!
到此結束~!