這幾天在博客園上看到好幾個寫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 492794628@qq.com
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#妥協進行字節序反轉操作~!
就不在添加調試和測試代碼和結果因為覺得沒多少意義~!
到此結束~!