【转】C# client 与java netty 服务端的简单通信,客户端采用Unity

http://blog.csdn.net/wilsonke/article/details/24721057

近日根据官方提供的通信例子自己写了一个关于Unity(C#)和后台通信的类,拿出来和大家分享一下。

具体请参考:

1.java服务端用的apach.mina框架搭建。java服务端请参考:http://blog.9tech.cn/?c=site&m=article&id=548

2.C#环境:.NET framework 2.0

3.C#帮组文档,及Socket注解:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket(v=vs.85).aspx

4.官方例子:http://msdn.microsoft.com/zh-cn/library/bew39x2a(v=VS.85).aspx#CommunityContent

个人觉得,最难的地方在与以下几个地方:

1.封装数据,和后台的编解码格式保持一致

封装数据,其实就是一个前后台约定好一个通信格式。比如:获得所以数据后并写入字节数组后,在吧这个字节数组的长度读出来(4个字节的整形数据),再封装进一个字节数组中。

所以最终的数据字节数组内容是:4个字节的数据长度+实际数据的字节数组。

当然数据的加密加压可以在吧实际数据存入字节数组后就进行,那么发送的长度就是加密加压后的数据长度了。

实现方法:


1

2

3

4

5

6

7

8

9

String message = "shit ";//5个字节

byte[] bytes = Encoding.UTF8.GetBytes(message);//未加密加压的实际数据

byte[] prefixBytes =BitConverter.GetBytes(bytes.Length);//数据长度

Array.Reverse(prefixBytes);

byte[] sendBytes = new byte[bytes.Length + 4];

//吧字节数组合并到sendBytes 

System.Buffer.BlockCopy(prefixBytes ,0,sendBytes ,0,4);

System.Buffer.BlockCopy(bytes,0,sendBytes ,4,bytes.Length);

//合并完毕,就可以使用socket吧sendBytes发送出去了,后台也是有同样的格式解码就可以了。

2.异步通信的线程管理

异步通信的线程安全一直是一个难点,它不像ActionScript那样,建立通信连接后注册事件用来侦听即可。但是在C#中就必须让线程等待当前的异步操作完成后才可以继续向下执行。C#异步通信中可以使用ManualResetEvent类来处理这类问题。当需要暂停执行后续代码以完成异步操作时。使用ManualResetEvent的WaitOne();方法来阻塞这个线程(类似与java的ReentrantLock类),但是必须在异步操作完成后使线程恢复,否则就会出现线程被锁死的情况。使用ManualResetEvent的Set()方法即可。

实现方法:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

private static ManualResetEvent connectDone = new ManualResetEvent(false);

public void startConnect(){

    Connect();

    connectDone.WaitOne();//阻塞线程,

    

     receive();

}

public void Connect(){

    // to do connection

    //socket.BeginConnect....

}

public void connectCallback(IAsyncResult ar){

    Socket socket = (Socket) ar.AsyncState;

    socket .EndConnect(ar);

    connectDone.Set();//恢复线程,继续向下执行

}

public void receive(){

    // to do received message

}

ManualResetEvent类的帮组文档及例子:http://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent_members(v=vs.85).aspx

3.关于数据的压缩,和解压.

对于数据的压缩和解压可以采用ICSharpCode.SharpZipLib这个动态链接库。下载地址:http://www.icsharpcode.net/opensource/sharpziplib/

下载后再MonoDevelop里倒入引用就可以了,位置:Project->Edit References..

using ICSharpCode.SharpZipLib.Zip;

然后在代码里就可以倒入类了

参考:http://blog.sina.com.cn/s/blog_62fda93c0101d51j.html

4.接收和读取数据的操作

在接受服务端发送的数据时,也根据同样的格式进行解读;先读取4个字节的数据长度,再跟进这个长度得到实际的数据,最后在解密和解压就可以得到最终的数据了。

但是在这个操作过程中,会出现一些意想不到的麻烦。

大致流程是:

采取分段读取的方式,第一次只读取4个字节的长度信息。

取得长度后,根据设置的每次分段读取数据长度来读取,知道所得的数据和总长度相同;

每次分段读取的数据的长度不一定都是设置的长度,所以将每次读取的数据写入内存流MemoryStream类中。特别重要的每次操作MemoryStream类时注意设置它的Position位置,不然会出现你本来已经成功的存入了数据,但是由于Position的原因没有找准需要取出或者存入数据的准确位置而读取数据失败。

详细代码如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

using UnityEngine;

using System.Collections;

using System.Collections.Generic;

using System;

using System.IO;

using System.Threading;

using System.Text;

using System.Net;

using System.Net.Sockets;

using ICSharpCode.SharpZipLib.Zip;

using ICSharpCode.SharpZipLib.GZip;

using LitJson;

/**

 * 连接对象

 * @author duwei

 * @version 1.0.0

 * build time :2013.11.7

 * */

public class BufferConnection{

    public Socket socket = null;

    public const int prefixSize = 4;

    public String ip = "192.168.1.105";

    public int port = 8005;

    

    private static ManualResetEvent connectDone = 

        new ManualResetEvent(false);

    private static ManualResetEvent sendDone = 

        new ManualResetEvent(false);

    private static ManualResetEvent receiveDone = 

        new ManualResetEvent(false);

    

    public BufferConnection(){

            

    }

    // State object for receiving data from remote device.

    public class StateObject {

        // Client socket.

        public Socket workSocket = null;

        // Size of receive buffer.

        public const int BufferSize = 1024;

        // Receive buffer.

        public byte[] buffer = new byte[BufferSize];

    }

    /**开始建立socket连接*/

    public void startConnect(){

        try{

            Debug.Log("starting connection...");

            IPAddress ipd = IPAddress.Parse(ip);

            EndPoint endPoint = new IPEndPoint(ipd,port);

            

            socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);

            socket.BeginConnect(endPoint,new AsyncCallback(connectCallback),socket);

            connectDone.WaitOne();

            

            receive(socket);

              //receiveDone.WaitOne();

            

        }catch(Exception e){

            Console.WriteLine(e.ToString());

        }

    }

    public void connectCallback(IAsyncResult ar){

        try{

            Socket backSocket = (Socket)ar.AsyncState;

            backSocket.EndConnect(ar);

            connectDone.Set();

            Debug.Log("on connected");

        }catch(Exception e){

            Console.WriteLine(e.ToString());   

        }  

    }

    /**发送数据,目前只支持 String 类型数据*/

    public void send(Socket client,String msg){

        //封装数据

        byte[] byteData = Encoding.UTF8.GetBytes(msg);

        byte[] sendData = new byte[byteData.Length + prefixSize];

        byte[] sizeData = BitConverter.GetBytes(byteData.Length);

        //反转

       Array.Reverse(sizeData);

        //合并

        System.Buffer.BlockCopy(sizeData,0,sendData,0,prefixSize);

        System.Buffer.BlockCopy(byteData,0,sendData,prefixSize,byteData.Length);

        try{

            //socket.Send(sendData);

            client.BeginSend(sendData,0,sendData.Length,0,new AsyncCallback(sendCallback),client);

            Debug.Log("data send finished, data size:"+sendData.Length);

        }catch(Exception e){

            Console.WriteLine(e.ToString());

        }

    }

    public void send(String msg){

        if(socket != null){

            send(socket,msg);

            sendDone.WaitOne();

        }

    }

    public void sendCallback(IAsyncResult ar){

        try{

            Socket socket = (Socket)ar.AsyncState;

            if(ar.IsCompleted){

                int endPoint = socket.EndSend(ar);  

                Debug.Log("send data finished endpoint: "+endPoint);

                sendDone.Set();

            }

        }catch(Exception e){

            Console.WriteLine(e.ToString());

        }

    }

    public void receive(Socket socket){

        try{   

            StateObject so = new StateObject();

            so.workSocket = socket;

            //第一次读取数据的总长度

            socket.BeginReceive(so.buffer,0,prefixSize,0,new AsyncCallback(receivedCallback),so);

            //测试用:数据在1024以内的数据,一次性读取出来

            //socket.BeginReceive(so.buffer,0,StateObject.BufferSize,0,new AsyncCallback(simpleCallback),so);

        }catch(Exception e){

            Console.WriteLine(e.ToString());

        }

    }

    public void simpleCallback(IAsyncResult ar){

        StateObject so = (StateObject)ar.AsyncState;  

        Socket socket = so.workSocket;

        byte[] presixBytes = new byte[prefixSize];

        int presix = 0;

        Buffer.BlockCopy(so.buffer,0,presixBytes,0,prefixSize);

        Array.Reverse(presixBytes);

        presix = BitConverter.ToInt32(presixBytes,0);

        if(presix <= 0){

            return;

        }  

        byte [] datas = new byte[presix];

        Buffer.BlockCopy(so.buffer,prefixSize,datas,0,datas.Length);

        String str = Encoding.UTF8.GetString(datas);

        Debug.Log("received message :"+str);

    }

    

    public MemoryStream receiveData = new MemoryStream();

    private bool isPresix = true;

    public int curPrefix = 0;//需要读取的数据总长度

    public void receivedCallback(IAsyncResult ar){

        try{

            StateObject so = (StateObject)ar.AsyncState;

            Socket client = so.workSocket;

            int readSize = client.EndReceive (ar);//结束读取,返回已读取的缓冲区里的字节数组长度

            //将每次读取的数据,写入内存流里

            receiveData.Write(so.buffer,0,readSize);

            receiveData.Position = 0;

            //读取前置长度,只读取一次

            if((int)receiveData.Length >= prefixSize && isPresix){

                byte[] presixBytes = new byte[prefixSize];

                receiveData.Read(presixBytes,0,prefixSize);

                Array.Reverse(presixBytes);

                curPrefix = BitConverter.ToInt32(presixBytes,0);

                isPresix = false;

            }

            if(receiveData.Length - (long)prefixSize < (long)curPrefix){

                //如果数据没有读取完毕,调整Position到最后,接着读取。

                receiveData.Position = receiveData.Length;

            }else{

                //如果内存流中的实际数字总长度符合要求,则说明数据已经全部读取完毕。

                //将position位置调整到第4个节点,开始准备读取数据。

                receiveData.Position = prefixSize;

                //读取数据

                byte[] datas = new byte[curPrefix];

                receiveData.Read(datas,0,datas.Length);

                //有压缩的话需要先解压,然后在操作。

                byte[] finallyBytes = decompress(datas);

                String str = Encoding.UTF8.GetString(finallyBytes);

                Debug.Log("the finally message is : "+str);

            }

            //重复读取,每次读取1024个字节数据

            client.BeginReceive(so.buffer,0,StateObject.BufferSize,0,new AsyncCallback(receivedCallback), so);

        }catch(Exception e){

            Console.WriteLine(e.ToString());

        }

    }

    private byte [] temp = new byte[1024];

    //解压

    public byte[] decompress(byte[] bytes){

        MemoryStream memory = new MemoryStream ();

        ICSharpCode.SharpZipLib.Zip.Compression.Inflater inf = new ICSharpCode.SharpZipLib.Zip.Compression.Inflater ();

        inf.SetInput (bytes);

        while (!inf.IsFinished) {

            int extracted = inf.Inflate (temp);

            if (extracted > 0) {

                memory.Write (temp, 0, extracted);

            else {

                break

            }

        }

        return memory.ToArray ();

    }

}

时间: 2024-11-04 23:52:07

【转】C# client 与java netty 服务端的简单通信,客户端采用Unity的相关文章

java实现服务端守护进程来监听客户端通过上传json文件写数据到hbase中

1.项目介绍: 由于大数据部门涉及到其他部门将数据传到数据中心,大部分公司采用的方式是用json文件的方式传输,因此就需要编写服务端和客户端的小程序了.而我主要实现服务端的代码,也有相应的客户端的测试代码.这里须有一个需要提到的是,我在实现接收json文件的同时,而且还需将数据写到hbase中.写入到hbase当中采用的是批量插入的方式,即一次插入多条记录. 好了,有了前面的说明,下面来简单的说一下我实现的服务端的小程序把. 2.为了实现服务端能够监听客户端的行为,因此我在服务端采用多线程的技术

[Java]命令行模拟TCP客户端与服务端的简单小程序遇到的问题(基础不牢!扎实和亲手实践比什么都重要!)

简单得不能再简单的需求: 简单模拟TCP客户端与服务端的一次连接和通信,客户端发出一个消息,服务端回馈一个消息 自己第一次编写的代码: Client: class TcpClient1 { public static void main(String[] args) throws Exception { Socket s=new Socket("127.0.0.1",10010); OutputStream out=s.getOutputStream(); out.write(&quo

Netty 服务端启动过程

在 Netty 中创建 1 个 NioServerSocketChannel 在指定的端口监听客户端连接,这个过程主要有以下  个步骤: 创建 NioServerSocketChannel 初始化并注册 NioServerSocketChannel 绑定指定端口 首先列出一个简易服务端的启动代码: 1 public void start() { 2 EventLoopGroup bossGroup = new NioEventLoopGroup(1); 3 EventLoopGroup work

搭建一个java web服务端

最近也是做了一个简单的java web 项目,由于以前也是没接触过,在这里记录下搭建一个web服务端的过程. 一般我们做一个服务端要么在本地自己的电脑上先安装环境,一般是windows系统,主要安装jdk + tomcat + mysql,这些安装教程网上都有,也很简单,我这里就不多说了,我要讲的是在一个远程linux服务器上搭建web服务端环境. 对于一个liunx服务器,我们可以使用xshell等终端工具登录来操作远程服务器,使用xshell的好处是,我们可以使用rz ,sz命令上传上载文件

Unity手游之路&lt;二&gt;Java版服务端使用protostuff简化protobuf开发

http://blog.csdn.net/janeky/article/details/17151465 开发一款网络游戏,首先要考虑的是客户端服务端之间用何种编码格式进行通信.之前我们介绍了Unity游戏前端使用protobuf的方法.今天我们来谈谈服务端如何使protobuf.游戏服务端语言百花齐放,除了比较传统的c/c++外,Java,Erlang,Python都有很多团队在使用. 今天推荐一下Java作为服务端开发语言.国内很多出色的页游和手游都是采用Java作为服务端语言的.比如<神曲

Android服务端开发1-使用Eclipse搭建Java Web服务端

本篇博客介绍如何使用Eclipse来创建一个Java Web程序,为后面讲通过Android客户端跟服务端进行交互打下基础,关于服务端可以选用的程序很多,主流的搭配是Android客户端+PHP服务端,我们也可以使用Android客户端+Java EE服务端这样的搭配,就看公司是以哪种方式提供了. 创建一个Java Web程序,没有特别复杂的流程,我们先准备一下原材料: 1. Eclipse(注:这个不是ADT Bundle,最好到官网下载针对开发Java EE的IDE,如果可以的话,选中MyE

Netty服务端启动

Netty服务端启动过程 (1)创建服务端Channel:调用JDK底层的API创建一个JDK的Channel,然后netty将其包装成自己的Channel,同时创建一些基本组件绑定在此Channel上 (2)初始化服务端Channel:初始化一些基本属性,以及添加一些逻辑处理器 (3)注册selector:Netty将JDK底层的Channel注册到事件轮询器selector上面 (4)端口绑定:最终也是调用底层JDK的API实现对本地端口的监听 bind()[用户代码入口] initAndR

netty服务端启动--ServerBootstrap源码解析

netty服务端启动--ServerBootstrap源码解析 前面的第一篇文章中,我以spark中的netty客户端的创建为切入点,分析了netty的客户端引导类Bootstrap的参数设置以及启动过程.显然,我们还有另一个重要的部分--服务端的初始化和启动过程没有探究,所以这一节,我们就来从源码层面详细分析一下netty的服务端引导类ServerBootstrap的启动过程. spark中netty服务端的创建 我们仍然以spark中对netty的使用为例,以此为源码分析的切入点,首先我们看

FireFly 服务端 Unity3D黑暗世界 客户端 问题

启动服务端成功截图: 连接成功截图: 测试服务端是否启动成功: 在Web输入:http://localhost:11009/  按回车 (查看cmd启动的服务端 是否多出如下显示) 服务端启动成功.PC unity3d客户端 连接也成功.但在手机端连接不成功 一直显示连接服务器有误 请检查网络 --------- beginning of /dev/log/system--------- beginning of /dev/log/mainD/Unity (32743): GL_EXT_debu