• 我看不下去鸟。。。。Java和C#的socket通信真的简单吗?


    这几天在博客园上看到好几个写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#妥协进行字节序反转操作~!

    就不在添加调试和测试代码和结果因为觉得没多少意义~!

    到此结束~!

  • 相关阅读:
    es之零停机重新索引数据
    es之索引的别名操作
    es索引基本操作(2)之 索引映射(mappings)管理和索引库配置管理(settings)
    进程管理(八)-进程控制
    进程管理(七)-进程状态与转换
    进程管理(六)-进程的描述
    numpy数组转置与轴变换
    numpy数组的索引和切片
    numpy数组的运算
    numpy库中数组的数据类型
  • 原文地址:https://www.cnblogs.com/shizuchengxuyuan/p/4699272.html
Copyright © 2020-2023  润新知