您的位置:首页 > 理论基础 > 计算机网络

unity网络编程学习(4)与java服务器的Socket通信

2019-02-07 09:57 1911 查看

转自:https://www.geek-share.com/detail/2630485221.html

前言

在上一篇博客中,我们通过unity中的www类来和web服务器进行数据的交互,所使用的方式就是http通信,那么http通信的原理是什么呢,socket通信原理又是什么呢,这里推荐两篇写的比较不错的博文:这里还有这里。

http通信原理

HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。采用了请求/响应模型。客户端向服务器发送一个请求,请求头包含了请求的方法、URI、协议版本,以及包含请求修饰符、 客户信息和内容的类似于MIME的消息结构。服务器以一个状态行作为响应,响应的内容包括消息协议的版本、成功或者错误编码,还包含服务器信息、实体元信息以及可能的实体内容。它是一个属于应用层的面向对象的协议,由于其简洁、快速,它适用于分布式超媒体信息系统。
http通信中使用最多的就是Get和Post,Get请求可以获取静态页面,也可以把参数放在URL字符串后面,传递给服务器。Post与Get的不同之处在于Post的参数不是放在URL字符串里面,而是放在http请求数据中。所以更为安全,由于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种“短连接”,要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。通常 的做法是即时不需要获得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明知道 客户端“在线”。若服务器长时间无法收到客户端的请求,则认为客户端“下线”,若客户端长时间无法收到服务器的回复,则认为网络已经断开。

socket通信原理

  套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。有两种主要的操作方式:面向连接(TCP协议)和无连接(UDP协议)的。面向连接的操作比无连接操作的效率更低,但是数据的安全性更高。可以说,网络通信的核心就是socket通信,
在socket通信中,需要了解的TCP协议的三次握手连接和四次握手断开连接,都可以通过各种搜索以及上面推荐的博客做详细的了解,socekt通信中常用的函数有socket()函数bind()函数listen()、connect()函数、accept()函数、read()、write()函数、close()函数

socket通信和http通信的区别

由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。
而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。
很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端

unity中的socket通信

这里,客户端是用c#编写,服务器端使用java编写,所以就是unity和java服务器端的scoket通信,连接成功后,客户端和服务器端之间传递字符串(真正的项目中应该是数据包,对象,列表啥的,恩,我自己认为的)先看看客户端代码吧,这里将开启socket通信写成一个单例模式。
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Net;
  6. using System.Net.Sockets;
  7. using System.Threading;
  8. using UnityEngine;
  9. /*
  10. *
  11. *Socket客户端通信类
  12. *
  13. */
  14. public class SocketHelper
  15. {
  16. private static SocketHelper socketHelper = new SocketHelper();
  17. private Socket socket;
  18. //单例模式
  19. public static SocketHelper GetInstance()
  20. {
  21. return socketHelper;
  22. }
  23. private SocketHelper()
  24. {
  25. //采用TCP方式连接
  26. socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  27. //服务器IP地址
  28. IPAddress address = IPAddress.Parse("127.0.0.1");
  29. //服务器端口
  30. IPEndPoint endpoint = new IPEndPoint(address, 8000);
  31. //异步连接,连接成功调用connectCallback方法
  32. IAsyncResult result = socket.BeginConnect(endpoint, new AsyncCallback(ConnectCallback), socket);
  33. //这里做一个超时的监测,当连接超过5秒还没成功表示超时
  34. bool success = result.AsyncWaitHandle.WaitOne(5000, true);
  35. if (!success)
  36. {
  37. //超时
  38. Closed();
  39. Debug.Log("connect Time Out");
  40. }
  41. else
  42. {
  43. //与socket建立连接成功,开启线程接受服务端数据。
  44. Thread thread = new Thread(new ThreadStart(ReceiveSorket));
  45. thread.IsBackground = true;
  46. thread.Start();
  47. }
  48. }
  49. private void ConnectCallback(IAsyncResult asyncConnect)
  50. {
  51. Debug.Log("connect success");
  52. }
  53. private void ReceiveSorket()
  54. {
  55. //在这个线程中接受服务器返回的数据
  56. while (true)
  57. {
  58. if (!socket.Connected)
  59. {
  60. //与服务器断开连接跳出循环
  61. Debug.Log("Failed to clientSocket server.");
  62. socket.Close();
  63. break;
  64. }
  65. try
  66. {
  67. //接受数据保存至bytes当中
  68. byte[] bytes = new byte[4096];
  69. //Receive方法中会一直等待服务端回发消息
  70. //如果没有回发会一直在这里等着。
  71. int i = socket.Receive(bytes);
  72. if (i <= 0)
  73. {
  74. socket.Close();
  75. break;
  76. }
  77. Debug.Log(System.Text.Encoding.Default.GetString(bytes));
  78. }
  79. catch (Exception e)
  80. {
  81. Debug.Log("Failed to clientSocket error." + e);
  82. socket.Close();
  83. break;
  84. }
  85. }
  86. }
  87. //关闭Socket
  88. public void Closed()
  89. {
  90. if (socket != null && socket.Connected)
  91. {
  92. socket.Shutdown(SocketShutdown.Both);
  93. socket.Close();
  94. }
  95. socket = null;
  96. }
  97. //向服务端发送一条字符串
  98. //一般不会发送字符串 应该是发送数据包
  99. public void SendMessage(string str)
  100. {
  101. byte[] msg = Encoding.UTF8.GetBytes(str);
  102. if (!socket.Connected)
  103. {
  104. socket.Close();
  105. return;
  106. }
  107. try
  108. {
  109. IAsyncResult asyncSend = socket.BeginSend(msg, 0, msg.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket);
  110. bool success = asyncSend.AsyncWaitHandle.WaitOne(5000, true);
  111. if (!success)
  112. {
  113. socket.Close();
  114. Debug.Log("Failed to SendMessage server.");
  115. }
  116. }
  117. catch
  118. {
  119. Debug.Log("send message error");
  120. }
  121. }
  122. private void SendCallback(IAsyncResult asyncConnect)
  123. {
  124. Debug.Log("send success");
  125. }
  126. }
[/code]在unity中创建一脚本,继承MonoBehaviour,挂在MainCamera上
  1. using UnityEngine;
  2. public class SocketTest : MonoBehaviour {
  3. void Start () {
  4. //创建socket连接
  5. SocketHelper s = SocketHelper.GetInstance();
  6. //发送信息向服务器端
  7. s.SendMessage("i am client");
  8. }
  9. }
[/code]--------------服务器端--------------
  1. package u3d_server;
  2. import java.io.BufferedInputStream;
  3. import java.io.IOException;
  4. import java.io.OutputStream;
  5. import java.net.ServerSocket;
  6. import java.net.Socket;
  7. /**
  8. * unity3d 服务端
  9. * @author lm
  10. *
  11. */
  12. public class U3dServer implements Runnable {
  13. public void run() {
  14. ServerSocket u3dServerSocket = null;
  15. while(true){
  16. try {
  17. u3dServerSocket=new ServerSocket(8000);
  18. System.out.println("u3d服务已经启动,监听8000端口");
  19. while (true) {
  20. Socket socket = u3dServerSocket.accept();
  21. System.out.println("客户端接入");
  22. new RequestReceiver(socket).start();
  23. }
  24. } catch (IOException e) {
  25. System.err.println("服务器接入失败");
  26. if (u3dServerSocket != null) {
  27. try {
  28. u3dServerSocket.close();
  29. } catch (IOException ioe) {
  30. }
  31. u3dServerSocket = null;
  32. }
  33. }
  34. // 服务延时重启
  35. try {
  36. Thread.sleep(5000);
  37. } catch (InterruptedException e) {
  38. }
  39. }
  40. }
  41. /**
  42. * 客户端请求接收线程
  43. * @author lm
  44. *
  45. */
  46. class RequestReceiver extends Thread {
  47. /** 报文长度字节数 */
  48. private int messageLengthBytes = 1024;
  49. private Socket socket;
  50. /** socket输入处理流 */
  51. private BufferedInputStream bis = null;
  52. public RequestReceiver(Socket socket) {
  53. this.socket = socket;
  54. }
  55. @Override
  56. public void run() {
  57. try {
  58. //获取socket中的数据
  59. bis = new BufferedInputStream(socket.getInputStream());
  60. byte[] buf = new byte[messageLengthBytes];
  61. /**
  62. * 在Socket报文传输过程中,应该明确报文的域
  63. */
  64. while (true) {
  65. /*
  66. * 这种业务处理方式是根据不同的报文域,开启线程,采用不同的业务逻辑进行处理
  67. * 依据业务需求而定
  68. */
  69. //读取字节数组中的内容
  70. bis.read(buf);
  71. //输出
  72. System.out.println(new String(buf,"utf-8"));
  73. OutputStream out = socket.getOutputStream();
  74. //向客户端传输数据的字节数组
  75. out.write(new String("i am server").getBytes());
  76. }
  77. } catch (IOException e) {
  78. System.err.println("读取报文出错");
  79. } finally {
  80. if (socket != null) {
  81. try {
  82. socket.close();
  83. } catch (IOException e) {
  84. }
  85. socket = null;
  86. }
  87. }
  88. }
  89. }
  90. }
[/code]
  1. package u3d_server;
  2. public class U3dApplication {
  3. private static U3dApplication instance = null;
  4. private boolean stop;
  5. private U3dApplication() {
  6. }
  7. public static synchronized U3dApplication getApplication() {
  8. if (instance == null) {
  9. instance = new U3dApplication();
  10. }
  11. return instance;
  12. }
  13. public void start() {
  14. init();
  15. new Thread(new U3dServer(), "U3d Server").start();
  16. while (!stop) {
  17. try {
  18. Thread.sleep(1000);
  19. } catch (InterruptedException e) {
  20. }
  21. }
  22. }
  23. /**
  24. * @param args
  25. */
  26. public static void main(String[] args) {
  27. Runtime.getRuntime().addShutdownHook(new Thread() {
  28. @Override
  29. public void run() {
  30. getApplication().stopMe();
  31. }
  32. });
  33. getApplication().start();
  34. }
  35. public void stopMe() {
  36. System.out.println("系统即将关闭...");
  37. }
  38. /**
  39. * 初始化系统
  40. */
  41. private void init() {
  42. }
  43. }
[/code]完成了,开启服务器和客户端,让我们看看效果如何
看来成功了 下面要尝试在socket通信中传递对象,最基本的就是字节数组,通过将对象序列化和反序列化,即可以达到对象的传递,关于c#中字节数组和对象之间的相互转换,可以学习 这篇博客,它介绍了c#中序列化对象的三种方式。 开始我认为,在java和c#之间进行socket通信,就是将c#这边对象序列化为字节数组,之后传递到java服务器,再将字节数组反序列化就行了,最后得到的结果就是:java得到的字节数组反序列化出错。 原因就是:由于是跨语言的交互,我们即不能用Java特有的序列化方式,也不能用C#特有的序列化方式,必须找一个通用的序列化格式才能交互。所以我又开始尝试通过json来进行数据传递
对于c#的json序列化,我使用了System.Runtime.Serialization.Json命名空间中的DataContractJsonSerializer 这个类,不过想使用这个类还真是复杂,在引用System.Runtime.Serialization.Json之前,你要先添加,System.ServiceModel , System.ServiceModel.Web这两个引用,然而我在写using时,却找不到这个引用,为什么呢,是因为.net的版本不同导致的,对于.net开发,我也是新手,上网搜索后,得出解决办法,项目 右键 引用 选中 .Net 找到 System.ServiceModel 引用,3.5里有 System.ServiceModel.Web,啊终于ok了,妈的好累~~~~~
  1. DataContractJsonSerializer dJson = new DataContractJsonSerializer(typeof(Person));
  2. MemoryStream stream = new MemoryStream();
  3. dJson.WriteObject(stream, p);
  4. byte[] dataBytes = new byte[stream.Length];
  5. stream.Position = 0;
  6. stream.Read(dataBytes, 0, (int)stream.Length);
  7. string dataString = Encoding.UTF8.GetString(dataBytes);
[/code]以为解决了问题 ,但是。。。。 unit报错说,引用不存在??什么情况?? 顿时累觉不爱。。。。赶紧百度之,缺少引用dll?是因为: 虽然可以用VisualStudio编写Unity代码,但实际上Unity生成游戏时还要自己再编译一遍。因此,在VS中的引用设置不能被Unity所使用。我曾经尝试过把要引用的程序集放在GAC中也不行。正确的做法是把dll放在Asset下,Unity能很好地识别它。
但是我引用的是vs自身的dll啊?,为什么不行呢。。算啦,另谋出路,通过引入外来的dll总可以了吧, C#端可以用开源项目JSON.NET,下载后根据自己的.NET版本,选择相应的Newtonsoft.Json.dll。
这里我又发现了一个问题,在网上download后,将.net版本为3.5的文件夹放入unity assets文件下,发现:
版本错误,这里我unity的版本是4.6,难道他不支持.net3.5吗,我去~~~~什么情况? 尝试将版本为2.0的文件拖入unity后 。。。。没报错! 这里的原因,不懂啊,不管什么样终于可以进行下一步了,引用后添加:using Newtonsoft.Json;using Newtonsoft.Json.Converters;即可使用。赶紧新建一个类,测试一下效果
  1. //新建一个学生信息类
  2. StudentsInfo p = new StudentsInfo();
  3. //赋值
  4. p.StuId = 1001;
  5. p.StuAge = "20";
  6. p.StuName = "小王";
  7. p.StuClass = "1004班";
  8. //调用函数将对象转换为json字符串
  9. string str = JsonConvert.SerializeObject(p);
  10. Debug.Log(str);
  11. //在将字符串转换成对象,并输出对象属性信息
  12. StudentsInfo p1 = new StudentsInfo();
  13. p1 = JsonConvert.DeserializeObject<StudentsInfo>(str);
  14. Debug.Log(p1.StuId+" "+p1.StuName+" "+p1.StuAge+" "+p1.StuClass);
[/code] 成功了 ---------java服务器端--------- Java端可以用开源项目google-gson,下载后是一个jar格式的包,直接在项目中导入这个包,并添加引用:import com.google.gson.Gson;即可使用。
  1. //测试代码
  2. Gson gson = new Gson();
  3. Person p = new Person(1001,"小明");
  4. String str = gson.toJson(p);
  5. System.out.println(str);
  6. Person p1 = gson.fromJson(str, Person.class);
  7. System.out.println(p1.getId()+" "+p1.getName());
[/code]要注意的是string和字节数组转化时的编码格式要统一,为utf-8。去除字节数组中的空格可以使用string类中的trim(),下面就是让他们在socket中走一走,看看能否成功传递对象信息呢
学生对象的信息,通过json的方式,在c#客户端和java服务器端传递~~~~~

总结

socket通信是网络通信的基础,其中涉及到服务器的创建,监听客户端的连接,和客户端之间的信息传递,需要对IO流的一些知识进行认真的学习,也可以尝试http通信中通过json传递信息。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: