之前的文章讲述了socket通信的一些基本知识,已经本人自定义的C#版本的socket、和java netty 库的二次封装,但是没有真正的发表测试用例。

本文只是为了讲解利用protobuf 进行C# 和 java的通信。以及完整的实例代码

java 代码 svn 地址,本人开发工具是NetBeans 8.0.2 使用 maven 项目编译

http://code.taobao.org/svn/flynetwork_csharp/trunk/BlogTest

c# 代码 svn 地址 使用的是 vs 2013 .net 4.5

http://code.taobao.org/svn/flynetwork_csharp/trunk/Flynetwork/BlogTest

编译工具下载

http://files.cnblogs.com/files/ty408/Sz.ExcelTools.zip

本文着重以C# socket作为服务器端,java netty作为socket的客户端进行访问通信

首先附上proto的message文件

package Sz.Test.ProtoMessage;

//登陆消息
message TestMessage {

   //消息枚举
    enum Proto_Login {

        ResTip                          = 101201;//服务器推送提示

        ReqLogin                        = 101102;//客户端申请登陆

        ReqChat                        = 101103;//客户端申请聊天消息
        ResChat                        = 101203;//服务器推送聊天消息

    }

    //服务器推送提示 ResTip
    message ResTipMessage {
        required string msg                     = 1;//提示内容
    }

    //客户端申请登陆 ReqLogin
    message ReqLoginMessage {

        required string userName                = 1;//登陆用户名
        required string userPwd                 = 2;//登陆密码

    } 

    //客户端申请登陆 ReqChat
    message ReqChatMessage {
        required string msg                     = 1;//提示内容
    }

    //客户端申请登陆 ResChat
    message ResChatMessage {
        required string msg                     = 1;//提示内容
    }
}

本人编译工具自带生产消息,和对应的handler

先把proto文件编译生产后,放到哪里,然后创建服务器监听代码

上一篇文章讲到由于java和C#默认网络端绪不一样,java是标准端绪大端序,C#使用的小端序。

             MarshalEndian.JN = MarshalEndian.JavaOrNet.Java;
             Sz.Network.SocketPool.ListenersBox.Instance.SetParams(new MessagePool(), typeof(MarshalEndian));
             Sz.Network.SocketPool.ListenersBox.Instance.Start("tcp:*:9527");

所以在我开启服务器监听的时候设置解码器和编码器的解析风格为java

然后建立一个文件chat文件夹用于存放handler文件就是刚才工具生成 目录下的 ExcelSource\protobuf\net\Handler

这一系列文件

 if (message.MsgID == (int)Sz.Test.ProtoMessage.TestMessage.Proto_Login.ReqLogin)
             {
                 //构建消息
                 Sz.Test.ProtoMessage.TestMessage.ReqLoginMessage loginmessage = new Test.ProtoMessage.TestMessage.ReqLoginMessage();
                 object msg = DeSerialize(message.MsgBuffer, loginmessage);
                 //构建handler
                 Test.ProtoMessage.ReqLoginHandler handler = new Test.ProtoMessage.ReqLoginHandler();
                 handler.Session = client;
                 handler.Message = loginmessage;
                 //把handler交给 登录 线程处理
                 ThreadManager.Instance.AddTask(ServerManager.LoginThreadID, handler);
             }
             else if (message.MsgID == (int)Sz.Test.ProtoMessage.TestMessage.Proto_Login.ReqChat)
             {
                 //构建消息
                 Sz.Test.ProtoMessage.TestMessage.ReqChatMessage loginmessage = new Test.ProtoMessage.TestMessage.ReqChatMessage();
                 object msg = DeSerialize(message.MsgBuffer, loginmessage);
                 //构建handler
                 Test.ProtoMessage.ReqChatHandler handler = new Test.ProtoMessage.ReqChatHandler();
                 handler.Session = client;
                 handler.Message = loginmessage;
                 //把handler交给 聊天 线程处理
                 ThreadManager.Instance.AddTask(ServerManager.ChatThreadID, handler);
             }

收到消息后的处理判断传入的消息id是什么类型的,然后对应反序列化byte[]数组为消息

最后把消息和生成handler移交到对应的线程处理

登录的消息全部交给 LoginThread 线程 去处理 ,这样在真实的运行环境下,能保证单点登录问题;

聊天消息全部交给 ChatThread 线程 去处理 这样的好处是,聊天与登录无关;

收到登录消息的处理

     public class ReqLoginHandler : TcpHandler
     {
         public override void Run()
         {

             var message = (Sz.Test.ProtoMessage.TestMessage.ReqLoginMessage)this.Message;
             Sz.Test.ProtoMessage.TestMessage.ResTipMessage tip = new TestMessage.ResTipMessage();
             if (message.userName == "admin" && message.userPwd == "admin")
             {
                 Logger.Debug("收到登录消息 登录完成");
                 tip.msg = "登录完成";
             }
             else
             {
                 Logger.Debug("收到登录消息 用户名或者密码错误");
                 tip.msg = "用户名或者密码错误";
             }
             byte[] buffer = MessagePool.Serialize(tip);
             this.Session.SendMsg(new Network.SocketPool.SocketMessage((int)Sz.Test.ProtoMessage.TestMessage.Proto_Login.ResTip, buffer));
         }
     }

收到聊天消息的处理

     public class ReqChatHandler : TcpHandler
     {
         public override void Run()
         {
             var message = (Sz.Test.ProtoMessage.TestMessage.ReqChatMessage)this.Message;
             Logger.Debug("收到来自客户端聊天消息:" + message.msg);
             Sz.Test.ProtoMessage.TestMessage.ResChatMessage chat = new TestMessage.ResChatMessage();
             chat.msg = "服务器广播:" + message.msg;
             byte[] buffer = MessagePool.Serialize(chat);
             this.Session.SendMsg(new Network.SocketPool.SocketMessage((int)Sz.Test.ProtoMessage.TestMessage.Proto_Login.ResChat, buffer));
         }
     }

接下来我们构建

java版本基于netty 二次封装的socket客户端

 package sz.network.socketpool.nettypool;

 import Sz.Test.ProtoMessage.Test.TestMessage;
 import com.google.protobuf.InvalidProtocolBufferException;
 import io.netty.channel.ChannelHandlerContext;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.util.logging.Level;
 import org.apache.log4j.Logger;

 /**
  *
  * @author Administrator
  */
 public class TestClient {

     static final Logger log = Logger.getLogger(TestClient.class);
     static NettyTcpClient client = null;

     public static void main(String[] args) {
         client = new NettyTcpClient("127.0.0.1", 9527, true, new NettyMessageHandler() {

             @Override
             public void channelActive(ChannelHandlerContext session) {
                 log.info("连接服务器成功:");
                 //构建错误的登录消息
                 TestMessage.ReqLoginMessage.Builder newBuilder = TestMessage.ReqLoginMessage.newBuilder();
                 newBuilder.setUserName("a");
                 newBuilder.setUserPwd("a");
                 //发送消息
                 TestClient.client.sendMsg(new NettyMessageBean(TestMessage.Proto_Login.ReqLogin_VALUE, newBuilder.build().toByteArray()));

                 //构建正确的登录消息
                 TestMessage.ReqLoginMessage.Builder newBuilder1 = TestMessage.ReqLoginMessage.newBuilder();
                 newBuilder1.setUserName("admin");
                 newBuilder1.setUserPwd("admin");
                 TestClient.client.sendMsg(new NettyMessageBean(TestMessage.Proto_Login.ReqLogin_VALUE, newBuilder1.build().toByteArray()));
             }

             @Override
             public void readMessage(NettyMessageBean msg) {
                 try {
                     if (msg.getMsgid() == TestMessage.Proto_Login.ResTip_VALUE) {
                         TestMessage.ResTipMessage tipmessage = TestMessage.ResTipMessage.parseFrom(msg.getMsgbuffer());
                         log.info("收到提示信息:" + tipmessage.getMsg());
                     } else if (msg.getMsgid() == TestMessage.Proto_Login.ResChat_VALUE) {
                         TestMessage.ResChatMessage tipmessage = TestMessage.ResChatMessage.parseFrom(msg.getMsgbuffer());
                         log.info("收到聊天消息:" + tipmessage.getMsg());
                     }
                 } catch (InvalidProtocolBufferException ex) {
                     log.error("收到消息:" + msg.getMsgid() + " 解析出错:" + ex);
                 }
             }

             @Override
             public void closeSession(ChannelHandlerContext session) {
                 log.info("连接关闭或者连接不成功:");
             }

             @Override
             public void exceptionCaught(ChannelHandlerContext session, Throwable cause) {
                 log.info("错误:" + cause.toString());
             }
         });
         client.Connect();

         BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
         while (true) {
             try {
                 String str = strin.readLine();
                 //构建聊天消息
                 TestMessage.ReqChatMessage.Builder chatmessage = TestMessage.ReqChatMessage.newBuilder();
                 chatmessage.setMsg(str);
                 TestClient.client.sendMsg(new NettyMessageBean(TestMessage.Proto_Login.ReqChat_VALUE, chatmessage.build().toByteArray()));
             } catch (IOException ex) {
             }
         }

     }

 }

接下来我们看看效果

我设置了断线重连功能,我们来测试一下,把服务器关闭

可以看到没3秒向服务器发起一次请求;

知道服务器再次开启链接成功

完整的通信示例演示就完了;

代码我不在上传了,请各位使用svn下载好么????

需要注意的是,消息的解码器和编码器,一定要双方都遵守你自己的契约。比如我在编码消息格式的时候先写入消息包的长度,然后跟上消息的id,再是消息的内容

所以解码的时候,先读取一个消息长度,在读取一个消息id,如果本次收到的消息字节数不够长度那么留存起来以用于下一次收到字节数组追加后再一起解析。

这样就能解决粘包的问题。

附上C#版本的解析器

 using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;

 /**
  *
  * @author 失足程序员
  * @Blog http://www.cnblogs.com/ty408/
  * @mail 492794628@qq.com
  * @phone 13882122019
  *
  */
 namespace Sz.Network.SocketPool
 {
     public class MarshalEndian : IMarshalEndian
     {

         public enum JavaOrNet
         {
             Java,
             Net,
         }

         public MarshalEndian()
         {

         }

         public static JavaOrNet JN = JavaOrNet.Net;

         /// <summary>
         /// 读取大端序的int
         /// </summary>
         /// <param name="value"></param>
         public int ReadInt(byte[] intbytes)
         {
             Array.Reverse(intbytes);
             );
         }

         /// <summary>
         /// 写入大端序的int
         /// </summary>
         /// <param name="value"></param>
         public byte[] WriterInt(int value)
         {
             byte[] bs = BitConverter.GetBytes(value);
             Array.Reverse(bs);
             return bs;
         }

         //用于存储剩余未解析的字节数
         );

         //字节数常量一个消息id4个字节
         const long ConstLenght = 4L;

         public void Dispose()
         {
             this.Dispose(true);
             GC.SuppressFinalize(this);
         }

         protected virtual void Dispose(bool flag1)
         {
             if (flag1)
             {
                 IDisposable disposable = this._LBuff as IDisposable;
                 if (disposable != null) { disposable.Dispose(); }
             }
         }

         public byte[] Encoder(SocketMessage msg)
         {
             MemoryStream ms = new MemoryStream();
             BinaryWriter bw = new BinaryWriter(ms, UTF8Encoding.Default);
             byte[] msgBuffer = msg.MsgBuffer;

             if (msgBuffer != null)
             {
                 switch (JN)
                 {
                     case JavaOrNet.Java:
                         bw.Write(WriterInt(msgBuffer.Length + ));
                         bw.Write(WriterInt(msg.MsgID));
                         break;
                     case JavaOrNet.Net:
                         bw.Write((Int32)(msgBuffer.Length + ));
                         bw.Write(msg.MsgID);
                         break;
                 }

                 bw.Write(msgBuffer);
             }
             else
             {
                 switch (JN)
                 {
                     case JavaOrNet.Java:
                         bw.Write(WriterInt());
                         break;
                     case JavaOrNet.Net:
                         bw.Write((Int32));
                         break;
                 }
             }
             bw.Close();
             ms.Close();
             bw.Dispose();
             ms.Dispose();
             return ms.ToArray();
         }

         public List<SocketMessage> Decoder(byte[] buff, int len)
         {
             //拷贝本次的有效字节
             byte[] _b = new byte[len];
             Array.Copy(buff, , _b, , _b.Length);
             buff = _b;
             )
             {
                 //拷贝之前遗留的字节
                 this._LBuff.AddRange(_b);
                 buff = this._LBuff.ToArray();
                 this._LBuff.Clear();
                 );
             }
             List<SocketMessage> list = new List<SocketMessage>();
             MemoryStream ms = new MemoryStream(buff);
             BinaryReader buffers = new BinaryReader(ms, UTF8Encoding.Default);
             try
             {
                 byte[] _buff;
             Label_0073:
                 //判断本次解析的字节是否满足常量字节数
                 if ((buffers.BaseStream.Length - buffers.BaseStream.Position) < ConstLenght)
                 {
                     _buff = buffers.ReadBytes((int)(buffers.BaseStream.Length - buffers.BaseStream.Position));
                     this._LBuff.AddRange(_buff);
                 }
                 else
                 {
                     ;
                     switch (JN)
                     {
                         case JavaOrNet.Java:
                             offset = ReadInt(buffers.ReadBytes());
                             break;
                         case JavaOrNet.Net:
                             offset = buffers.ReadInt32();
                             break;
                     }

                     //剩余字节数大于本次需要读取的字节数
                     if (offset <= (buffers.BaseStream.Length - buffers.BaseStream.Position))
                     {
                         ;
                         switch (JN)
                         {
                             case JavaOrNet.Java:
                                 msgID = ReadInt(buffers.ReadBytes());
                                 break;
                             case JavaOrNet.Net:
                                 msgID = buffers.ReadInt32();
                                 break;
                         }
                         _buff = buffers.ReadBytes(());
                         list.Add(new SocketMessage(msgID, _buff));
                         goto Label_0073;
                     }
                     else
                     {
                         //剩余字节数刚好小于本次读取的字节数 存起来,等待接受剩余字节数一起解析
                         buffers.BaseStream.Seek(ConstLenght, SeekOrigin.Current);
                         _buff = buffers.ReadBytes((int)(buffers.BaseStream.Length - buffers.BaseStream.Position));
                         this._LBuff.AddRange(_buff);
                     }
                 }
             }
             catch { }
             finally
             {
                 buffers.Close();
                 if (buffers != null) { buffers.Dispose(); }
                 ms.Close();
                 if (ms != null) { ms.Dispose(); }
             }
             return list;
         }
     }
 }

谢谢观赏~!

java netty socket库和自定义C#socket库利用protobuf进行通信完整实例的更多相关文章

  1. Java 网络IO编程总结(BIO、NIO、AIO均含完整实例代码)

    本文会从传统的BIO到NIO再到AIO自浅至深介绍,并附上完整的代码讲解. 下面代码中会使用这样一个例子:客户端发送一段算式的字符串到服务器,服务器计算后返回结果到客户端. 代码的所有说明,都直接作为 ...

  2. (转)Java 网络IO编程总结(BIO、NIO、AIO均含完整实例代码)

    原文出自:http://blog.csdn.net/anxpp/article/details/51512200 1.BIO编程 1.1.传统的BIO编程 网络编程的基本模型是C/S模型,即两个进程间 ...

  3. ClientAbortException: java.net.SocketException: Software caused connection abort: socket write erro

    1.错误描述 ClientAbortException: java.net.SocketException: Software caused connection abort: socket writ ...

  4. [经验] Java 服务端 和 C# 客户端 实现 Socket 通信

    由于项目需要, 我需要通过 Java 开发的服务端对 C# 作为脚本语言开发的 unity 项目实现控制 话不多说, 直接上代码 首先, 我们先来构建服务端的代码, 服务端我们使用 Java 语言 i ...

  5. java.net.SocketException: Software caused connection abort: socket write error

    用Java客户端程序访问Java Web服务器时出错: java.net.SocketException: Software caused connection abort: socket write ...

  6. Java从零开始学四十五(Socket编程基础)

    一.网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可 ...

  7. JAVA与网络开发(TCP:Socket、ServerSocket;UDP:DatagramSocket、DatagramPacket;多线程的C/S通讯、RMI开发概述)

    通过TCP建立可靠通讯信道 1)为了对应TCP协议里的客户端和服务器端,Socket包提供了Socket类和ServerSocket类. 2)Socket类构造函数及相关方法 Public Socke ...

  8. testNG java.net.SocketException: Software caused connection abort: socket write error

    执行用例报错,提示 java.net.SocketException: Software caused connection abort: socket write error java.net.So ...

  9. Caused by: java.net.SocketException: Software caused connection abort: socket write error

    1.错误描述 [ERROR:]2015-05-06 10:54:18,967 [异常拦截] ClientAbortException: java.net.SocketException: Softwa ...

随机推荐

  1. PC分配盘符的时候发现==》RPC盘符不可用

    服务器汇总:http://www.cnblogs.com/dunitian/p/4822808.html#iis 服务器异常: http://www.cnblogs.com/dunitian/p/45 ...

  2. 【WCF】自定义错误处理(IErrorHandler接口的用法)

    当被调用的服务操作发生异常时,可以直接把异常的原始内容传回给客户端.在WCF中,服务器传回客户端的异常,通常会使用 FaultException,该异常由这么几个东东组成: 1.Action:在服务调 ...

  3. Bringing Whoops Back to Laravel 5

    You might be missing the "prettier" Whoops error handler from Laravel 4. If so, here's how ...

  4. [.NET] C# 知识回顾 - 事件入门

    C# 知识回顾 - 事件入门 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6057301.html 序 之前通过<C# 知识回顾 - 委托 de ...

  5. CSS知识总结(七)

    CSS常用样式 5.背景样式 1)背景颜色 background-color : transparent | color 常用值:①英文单词,②十六进制,③RGB或RGBA 另外,还有一种是 渐变色彩 ...

  6. “老坛泡新菜”:SOD MVVM框架,让WinForms焕发新春

    火热的MVVM框架 最近几年最热门的技术之一就是前端技术了,各种前端框架,前端标准和前端设计风格层出不穷,而在众多前端框架中具有MVC,MVVM功能的框架成为耀眼新星,比如GitHub关注度很高的Vu ...

  7. SAP CRM 树视图(TREE VIEW)

    树视图可以用于表示数据的层次. 例如:SAP CRM中的组织结构数据可以表示为树视图. 在SAP CRM Web UI的术语当中,没有像表视图(table view)或者表单视图(form view) ...

  8. Oracle创建表空间

    1.创建表空间 导出Oracle数据的指令:/orcl file=C:\jds.dmp owner=jds 导入Oracle数据的指令:imp zcl:/orcl file=C:\jds.dmp fu ...

  9. .NET面试题系列[5] - 垃圾回收:概念与策略

    面试出现频率:经常出现,但通常不会问的十分深入.通常来说,看完我这篇文章就足够应付面试了.面试时主要考察垃圾回收的基本概念,标记-压缩算法,以及对于微软的垃圾回收模板的理解.知道什么时候需要继承IDi ...

  10. Joshua Bloch错了? ——适当改变你的Builder模式实现

    注:这一系列都是小品文.它们偏重的并不是如何实现模式,而是一系列在模式实现,使用等众多方面绝对值得思考的问题.如果您仅仅希望知道一个模式该如何实现,那么整个系列都会让您失望.如果您希望更深入地了解各个 ...