版本:unity 5.4.1  语言:C#

 

做过有网络相关游戏的人都知道protobuf,google的一套开源工具,用于发送信息的序列化和反序列化,是一个非常重要的网络工具。

 

在上一家公司,就是使用该技术与服务器交互,而现在的工作是把它搬到Unity上,当然原理是一样的,其中我还会把服务器方面的代码贴出来,这边使用的是eclipse的Java EE版。

 

这边参考了实战核心技术的第四章,还有jiange啊啊啊网友的手游之路博文。Java方面参考了非常多的博客和知道,在这里就不详细说了。

 

这边提供我工程中使用的protobuf和其转换器:


 

我们做的是客户端,首先说Unity方面的导入吧。

 

Unity直接导入cs的源代码,而不是dll文件,其实差不多。我的资源的话直接把根目录下的protobuf-net拖入Unity工程就行了,不过网上自己去下载最新版本的同学可能会碰到两个问题:

unity3d服务器选择 unity 服务器_Unity客户端


1.如上图,说使用了不安全的代码,这个时候只要在Assets目录下新建一个smcs.rsp的文件,内容是-unsafe,保存后重启Unity就能通过运行了;


unity3d服务器选择 unity 服务器_unity3d服务器选择_02



2.MetaType中有无法解析的符号,这个去vs中看的话,它会提示你我们用的C#版本是4,而语法的版本是6,这个无法解决。我最后是直接替换老版本的MetaType脚本进去,其他的还是新的,然后就没有任何错误了。

 

环境搭建完成之后接下来就是制作与服务器通信的类了,直接看例子,以下是通信协议:


unity3d服务器选择 unity 服务器_unity3d服务器选择_03



文件名称并非是无关紧要的,common会转换成C#中的namespace,而message后面的mymessage会转换成具体的类。

 

使用的时候就应该这样使用:

using common;
mymessage myMsg = new mymessage();



这里有个坑,大家也看到了java_package的一行,没错,在java的转换中文件名就是个类,而mymessage是个内部类,所以使用java_package指定包名,大家可以试试不写的结果,我查了一下,没找到导入默认包的方法。

 

使用的时候如此使用:

import protobuf.Common;
Common.mymessage.Builder msg2b = Common.mymessage.newBuilder();
Common.mymessage msg2 = msg2b.build();
byte[] b = msg2.toByteArray();



现在这些代码是无法使用的,接下来就是转换器,把上面协议的内容转换成C#、Java代码,可以参考我资源中message文件夹下的makeProtobuf.bat文件。

 

贴出来给大家看看吧,没什么好说的,就是使用相应的exe应用程序进行转换:

@echo off
set tool=..\protobuf-net\net

rem ============================================
rem Support
set proto=common.proto
%tool%\protogen.exe -i:%proto% -o:%proto%.cs -q

set proto=message.proto
%tool%\protogen.exe -i:%proto% -o:%proto%.cs -q

..\protobuf-java\protoc.exe -I=. --java_out=. common.proto
..\protobuf-java\protoc.exe -I=. --java_out=. message.proto

pause



没有问题的话就直接输出请继续字样的提示。

 

运行之后会出现如下的文件:


unity3d服务器选择 unity 服务器_java服务器_04



protobuf中的是java的,而两个cs文件是Unity的。

 

Unity是最方便的,这两个文件你随便放哪都行,当然工程中要导入protobuf的前期下哈。

 

接下来是发送协议数据的代码:

using UnityEngine;
using System.IO;
using ProtoBuf;
using common;

public class ProtobufTest : MonoBehaviour {

    SocketSink socketSink;

    // Use this for initialization
    void Start () {
        socketSink = SocketSink.getInstance();
    }

    float dTime = 0f;
    float maxDTime = 1f;
	
	// Update is called once per frame
	void Update () {
	    // 如果服务器没有连接
        if(!socketSink.isConnected)
        {
            dTime += Time.deltaTime;
            if(dTime > maxDTime)
            {
                dTime = 0f;
                socketSink.init();  //每隔一秒重连一次服务器
            }
        }
	}

    private void OnGUI()
    {
        // 连上服务器之后,可以发送消息
        if(socketSink.isConnected)
        {
            if(GUILayout.Button("发送"))
            {
                //socketSink.sendDataToServer("hello#");

                // 建立一个消息
                mymessage myMsg = new mymessage();
                myMsg.myid = 1;
                myMsg.message = "haha";

                using (MemoryStream ms = new MemoryStream())
                {
                    // 进行序列化
                    Serializer.Serialize<mymessage>(ms, myMsg);
                    byte[] data = new byte[ms.Length];
                    ms.Position = 0;
                    ms.Read(data, 0, data.Length);  //将序列化完成的数据方法byte数组中
                    
                    // 向服务器发送
                    socketSink.sendProtobufDataToServer(data);
                }
            }
        }
    }
}



一个简单的事例,就是按下屏幕的按钮就会发送数据。额,SocketSink是客户端建Socket底层的代码,在这就不提供给大家了,如果有问题可以评论区里提问,实在有需要的话,我可以考虑提供一下(不过估计没什么人看,哈哈)。

 

这样Unity上的开发就搞定了。

 

接下来是服务器,Eclipse上的开发是蛋疼的,感觉大学里学的java都白学了。。。

 

安装完成后,新建个java项目输出Helloworld看看有没有问题,嗯对,普通项目就可以了。

 

首先将资源中的protobuf-java-2.5.0.jar导入工程,再将之前协议转换出来的Java代码导入,最后的目录结构是这样:


unity3d服务器选择 unity 服务器_java服务器_05



这个完成之后,只要开一下Socket服务器监听,然后接受Unity发送来的数据就可以了。

 

下面是Java代码:

public class SimpleServer {
	public static void main(String[] args)
	{
		try{
			// 服务器监听的总类,只存在一个,端口是5200
	        ServerSocket serverSocket=new ServerSocket(5200);
	        Socket socket=null;
	        
	        while(true){
	        	// 获取Unity客户端的连接,每有一个客户端,对应一个Socket
	            socket=serverSocket.accept();
	            
	            // 开启监听线程
	            SimpleServerThread serverThread=new SimpleServerThread(socket);
	            serverThread.start();

	            InetAddress address=socket.getInetAddress();
	            System.out.println("来了一个客户端:"+address.getHostAddress());
	        }
	    } catch (IOException e) {
	        e.printStackTrace();
	    }
	}
}



上面开启了一个监听,具体处理交给了SimpleServerThread,下面是具体脚本:

public class SimpleServerThread extends Thread {

	Socket socket = null;	// 记录客户端对应的Socket	
	SimpleServerThread(Socket socket)
	{
		this.socket = socket;
	}
	
	public void run()
	{
		System.out.println("读取线程启动");
		//循环读取数据
		while(true)
		{
			try{
				System.out.println("读取中。。。");
//				StringBuilder sb = new StringBuilder();
				byte[] bs = new byte[1024];	//存放消息的byte数组
				InputStream is = socket.getInputStream();	//socket产生的一个读入流
				int len = -1;
				
				// 读入
				len = is.read(bs);
				
				// 转换成对应长度的数组,这里绝对是不能这么写的,不过具体处理方法暂时没想到,以后看看会不会完善一下,嘛,毕竟这是服务端的事情
				byte[] mybs = new byte[len];
				for(int i=0 ; i < mybs.length ; ++i)
				{
					mybs[i] = bs[i];
				}
				
				// 将获取到的数据转换成相应的类
				Common.mymessage msg = Common.mymessage.parseFrom(mybs);
				int id = msg.getMyid();
				String m = msg.getMessage();
				
				// 打印
				System.out.println(id + ":" + m);
			}
			catch(IOException e)
			{
				e.printStackTrace();
			}
		}
	}
}



这样Unity中点击发送,Eclipse里应该能接受到信息了。最终效果再来个图:


unity3d服务器选择 unity 服务器_Protobuf_06



这篇博文我弄了我两天,主要找资源、搞Java的时候费了很长时间,最后总结一句话Java真NM蛋疼,哈哈。

 

国内的文章就是实用性高,高手看了嗤之以鼻,新手看了如获至宝,讲真的我觉得我还挺适合这样的实战学习的,不喜欢光搞理论。

 

希望能快点成长到嗤之以鼻的阶段。