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 ();
    }
}

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

  1. 基于node的tcp客户端和服务端的简单通信

    1.简单介绍下TCP/IP TCP/IP是互联网相关协议的集合,分为以下四层:应用层.传输层.网络层.数据链路层. 分成四层的好处是,假如只有一层,某个地方需要改变设计时,就必须把所有整体替换掉,而分 ...

  2. Netty 服务端创建

    参考:http://blog.csdn.net/suifeng3051/article/details/28861883?utm_source=tuicool&utm_medium=refer ...

  3. Netty搭建服务端的简单应用

    Netty简介 Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客 ...

  4. Netty之旅三:Netty服务端启动源码分析,一梭子带走!

    Netty服务端启动流程源码分析 前记 哈喽,自从上篇<Netty之旅二:口口相传的高性能Netty到底是什么?>后,迟迟两周才开启今天的Netty源码系列.源码分析的第一篇文章,下一篇我 ...

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

    1.项目介绍: 由于大数据部门涉及到其他部门将数据传到数据中心,大部分公司采用的方式是用json文件的方式传输,因此就需要编写服务端和客户端的小程序了.而我主要实现服务端的代码,也有相应的客户端的测试 ...

  6. Netty 服务端启动过程

    在 Netty 中创建 1 个 NioServerSocketChannel 在指定的端口监听客户端连接,这个过程主要有以下  个步骤: 创建 NioServerSocketChannel 初始化并注 ...

  7. Netty服务端NioEventLoop启动及新连接接入处理

    一 Netty服务端NioEventLoop的启动 Netty服务端创建.初始化完成后,再向Selector上注册时,会将服务端Channel与NioEventLoop绑定,绑定之后,一方面会将服务端 ...

  8. Netty服务端的启动源码分析

    ServerBootstrap的构造: public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, Serve ...

  9. Unity手游之路<二>Java版服务端使用protostuff简化protobuf开发

    http://blog.csdn.net/janeky/article/details/17151465 开发一款网络游戏,首先要考虑的是客户端服务端之间用何种编码格式进行通信.之前我们介绍了Unit ...

随机推荐

  1. Python常见编程规范总结

    Pythonic定义 Python最常用的编码风格还是PEP8,详见:http://jython.cn/dev/peps/pep-0008/ Pythonic确实很难定义,先简单引用下<Pyth ...

  2. [Rodbourn's Blog]How to export Excel plots to a vector image (EPS, EMF, SVG, etc.)

    This is a bit of a workaround, but it's the only way I know of to export an Excel plot into a vector ...

  3. POJ 1703 Find them, Catch them(并查集,等价关系)

    DisjointSet保存的是等价关系,对于某个人X,设置两个变量Xa,Xb.Xa表示X属于a帮派,Xb类似. 如果X和Y不是同一个帮派,那么Xa -> Yb,Yb -> Xa... (X ...

  4. 解决wget下载https时报错 --no-check-certificate (不检查证书)

    如果使用 wget下载https开头的网址域名 时报错,你需要加上 --no-check-certificate (不检查证书)选项 例如: wget https://pypi.python.org/ ...

  5. 解决MySQL安装到最后一步未响应的三种方法

    这种情况一般是你以前安装过MySQL数据库服务项被占用了.解决方法: 方法一:安装MySQL的时候在这一步时它默认的服务名是“MySQL” 只需要把这个名字改了就可以了.可以把默认的服务器的名称手动改 ...

  6. SummerVocation_Learning--StringBuffer类

    java.lang.StringBuffer代表可变的字符序列.与String类基本类似. 常见的构造方法: StringBuffer(),创建一个不包含字符序列的空的StringBuffer对象. ...

  7. mac利用套件管理工具homebrew正确地同时安装python2.7和python3

    MAC OSX 正確地同時安裝 PYTHON 2.7 和 PYTHON3     Python3 出來了(其實已經出來很久了,暈)!但是還是有很多 library 還是使用 Python2.7,所以要 ...

  8. 4.在Cisco Packet Tracerl里路由器密码重置

    在路由器的特权模式的密码忘记的情况下,关闭路由器的电源,在接通电源,在路由器载入的时候,按ctrl+c,直接进入monitor模式 输入:confreg 0x2142 reset 重新进入后 enab ...

  9. lnmp启用pathinfo并隐藏index.php

    编辑如下区段: location ~ [^/]\.php(/|$) { # comment try_files $uri =404; to enable pathinfo try_files $uri ...

  10. JZOJ 5773. 【NOIP2008模拟】简单数学题

    5773. [NOIP2008模拟]简单数学题 (File IO): input:math.in output:math.out Time Limits: 1000 ms  Memory Limits ...