Unity3D —— Socket通信(C#)

来源:互联网 发布:海尔cosmo平台 知乎 编辑:程序博客网 时间:2024/06/11 01:46

原文链接:http://blog.csdn.net/linshuhe1/article/details/51386559

前言:

        在开始编写代码之前,我们首先需要明确:联网方式联网步骤数据收发以及协议数据格式

        当然在设计时也应该减低代码的耦合性,尽量使得网络层可以在其他地方进行复用,这就需要我们进行接口式的开发。我们这里使用的通信模式是Socket强连接的通信方式,并且使用C#作为编程语言,其实与.NET的Socket通信是一致的。


一、设计思想:

        为了方便测试,我直接使用C#写的一个控制台应用,作为服务器,等待客户端的连接,然后使用Unity建立Socket客户端去连接服务器,进行简单的数据通信。这么设计的原因是都基于.net进行开发,也方便理解。


二、实现步骤:

        对于网络通信有所了解的都应该知道,数据在网络传输的格式必须以字节流的形式进行,那么免不了要对字节流进行写入和读出操作,为了方便后面的操作,我们有必要封装一个读写字节流的操作类,在这里我定义了一个字节流的操作类ByteBuffer类,用于将各个类型数据写入流中,也可从字节流中读取各种类型的数据:

[csharp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. using System.IO;  
  2. using System.Text;  
  3. using System;  
  4.   
  5. namespace Net {  
  6.     public class ByteBuffer {  
  7.         MemoryStream stream = null;  
  8.         BinaryWriter writer = null;  
  9.         BinaryReader reader = null;  
  10.   
  11.         public ByteBuffer() {  
  12.             stream = new MemoryStream();  
  13.             writer = new BinaryWriter(stream);  
  14.         }  
  15.   
  16.         public ByteBuffer(byte[] data) {  
  17.             if (data != null) {  
  18.                 stream = new MemoryStream(data);  
  19.                 reader = new BinaryReader(stream);  
  20.             } else {  
  21.                 stream = new MemoryStream();  
  22.                 writer = new BinaryWriter(stream);  
  23.             }  
  24.         }  
  25.   
  26.         public void Close() {  
  27.             if (writer != null) writer.Close();  
  28.             if (reader != null) reader.Close();  
  29.   
  30.             stream.Close();  
  31.             writer = null;  
  32.             reader = null;  
  33.             stream = null;  
  34.         }  
  35.   
  36.         public void WriteByte(byte v) {  
  37.             writer.Write(v);  
  38.         }  
  39.   
  40.         public void WriteInt(int v) {  
  41.             writer.Write((int)v);  
  42.         }  
  43.   
  44.         public void WriteShort(ushort v) {  
  45.             writer.Write((ushort)v);  
  46.         }  
  47.   
  48.         public void WriteLong(long v) {  
  49.             writer.Write((long)v);  
  50.         }  
  51.   
  52.         public void WriteFloat(float v) {  
  53.             byte[] temp = BitConverter.GetBytes(v);  
  54.             Array.Reverse(temp);  
  55.             writer.Write(BitConverter.ToSingle(temp, 0));  
  56.         }  
  57.   
  58.         public void WriteDouble(double v) {  
  59.             byte[] temp = BitConverter.GetBytes(v);  
  60.             Array.Reverse(temp);  
  61.             writer.Write(BitConverter.ToDouble(temp, 0));  
  62.         }  
  63.   
  64.         public void WriteString(string v) {  
  65.             byte[] bytes = Encoding.UTF8.GetBytes(v);  
  66.             writer.Write((ushort)bytes.Length);  
  67.             writer.Write(bytes);  
  68.         }  
  69.   
  70.         public void WriteBytes(byte[] v) {  
  71.             writer.Write((int)v.Length);  
  72.             writer.Write(v);  
  73.         }  
  74.   
  75.         public byte ReadByte() {  
  76.             return reader.ReadByte();  
  77.         }  
  78.   
  79.         public int ReadInt() {  
  80.             return (int)reader.ReadInt32();  
  81.         }  
  82.   
  83.         public ushort ReadShort() {  
  84.             return (ushort)reader.ReadInt16();  
  85.         }  
  86.   
  87.         public long ReadLong() {  
  88.             return (long)reader.ReadInt64();  
  89.         }  
  90.   
  91.         public float ReadFloat() {  
  92.             byte[] temp = BitConverter.GetBytes(reader.ReadSingle());  
  93.             Array.Reverse(temp);  
  94.             return BitConverter.ToSingle(temp, 0);  
  95.         }  
  96.   
  97.         public double ReadDouble() {  
  98.             byte[] temp = BitConverter.GetBytes(reader.ReadDouble());  
  99.             Array.Reverse(temp);  
  100.             return BitConverter.ToDouble(temp, 0);  
  101.         }  
  102.   
  103.         public string ReadString() {  
  104.             ushort len = ReadShort();  
  105.             byte[] buffer = new byte[len];  
  106.             buffer = reader.ReadBytes(len);  
  107.             return Encoding.UTF8.GetString(buffer);  
  108.         }  
  109.   
  110.         public byte[] ReadBytes() {  
  111.             int len = ReadInt();  
  112.             return reader.ReadBytes(len);  
  113.         }  
  114.   
  115.         public byte[] ToBytes() {  
  116.             writer.Flush();  
  117.             return stream.ToArray();  
  118.         }  
  119.   
  120.         public void Flush() {  
  121.             writer.Flush();  
  122.         }  
  123.     }  
  124. }  

        使用此操作类进行读写数据的操作范例如下:

  • 读取数据:
[csharp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. //result是字节数组byte[],从中读取两个int类型的数据  
  2.  ByteBuffer buff = new ByteBuffer(result);  
  3.  int len = buff.ReadShort();  
  4.  int protoId = buff.ReadShort();  
  • 写入数据:
[csharp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. //result是字节数组byte[],从写入两个不同类型的数据  
  2. ByteBuffer buff = new ByteBuffer();  
  3. int protoId = ProtoDic.GetProtoIdByProtoType(0);  
  4. buff.WriteShort((ushort)protoId);  
  5. buff.WriteBytes(ms.ToArray());  
  6. byte[] result = buff.ToBytes();  


1.服务器创建:

        在VS中新建一个C#控制台应用,新建项目完成后将上面定义的ByteBuffer.cs类导入工程中,然后开始在入口类Program中开始创建Socket服务器的逻辑。

        先引入必要的命名空间:

[csharp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. using System.Net;  
  2. using System.Net.Sockets;  
  3. using System.Threading;  
        基本的步骤如下:

  • 创建一个服务器Socket对象,并绑定服务器IP地址和端口号;
[csharp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. private const int port = 8088;  
  2. private static string IpStr = "127.0.0.1";  
  3. private static Socket serverSocket;  
  4.   
  5. static void Main(string[] args)  
  6. {  
  7.     IPAddress ip = IPAddress.Parse(IpStr);  
  8.     IPEndPoint ip_end_point = new IPEndPoint(ip, port);  
  9.     //创建服务器Socket对象,并设置相关属性  
  10.     serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  
  11.     //绑定ip和端口  
  12.     serverSocket.Bind(ip_end_point);  
  13.     //设置最长的连接请求队列长度  
  14.     serverSocket.Listen(10);  
  15.     Console.WriteLine("启动监听{0}成功", serverSocket.LocalEndPoint.ToString());  
  16. }  
        完成上述代码之后,已经能正常启动一个服务器Socket,但是还没有处理连接监听逻辑和数据接收,所以运行应用会出现一闪就关掉的情况。
  • 启动一个线程,并在线程中监听客户端的连接,为每个连接创建一个Socket对象;
  • 创建接受数据和发送数据的方法。
        完整的代码如下:

[csharp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Net.Sockets;  
  5. using System.Text;  
  6. using System.Threading.Tasks;  
  7. using System.Net;  
  8. using System.Threading;  
  9. using Net;  
  10. using System.IO;  
  11.   
  12. namespace ConsoleApplication1  
  13. {  
  14.     class Program  
  15.     {  
  16.         private static byte[] result = new byte[1024];  
  17.         private const int port = 8088;  
  18.         private static string IpStr = "127.0.0.1";  
  19.         private static Socket serverSocket;  
  20.   
  21.         static void Main(string[] args)  
  22.         {  
  23.             IPAddress ip = IPAddress.Parse(IpStr);  
  24.             IPEndPoint ip_end_point = new IPEndPoint(ip, port);  
  25.             //创建服务器Socket对象,并设置相关属性  
  26.             serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  
  27.             //绑定ip和端口  
  28.             serverSocket.Bind(ip_end_point);  
  29.             //设置最长的连接请求队列长度  
  30.             serverSocket.Listen(10);  
  31.             Console.WriteLine("启动监听{0}成功", serverSocket.LocalEndPoint.ToString());  
  32.             //在新线程中监听客户端的连接  
  33.             Thread thread = new Thread(ClientConnectListen);  
  34.             thread.Start();  
  35.             Console.ReadLine();  
  36.         }  
  37.   
  38.         /// <summary>  
  39.         /// 客户端连接请求监听  
  40.         /// </summary>  
  41.         private static void ClientConnectListen()  
  42.         {  
  43.             while (true)  
  44.             {  
  45.                 //为新的客户端连接创建一个Socket对象  
  46.                 Socket clientSocket = serverSocket.Accept();  
  47.                 Console.WriteLine("客户端{0}成功连接", clientSocket.RemoteEndPoint.ToString());  
  48.                 //向连接的客户端发送连接成功的数据  
  49.                 ByteBuffer buffer = new ByteBuffer();  
  50.                 buffer.WriteString("Connected Server");  
  51.                 clientSocket.Send(WriteMessage(buffer.ToBytes()));  
  52.                 //每个客户端连接创建一个线程来接受该客户端发送的消息  
  53.                 Thread thread = new Thread(RecieveMessage);  
  54.                 thread.Start(clientSocket);  
  55.             }  
  56.         }  
  57.   
  58.         /// <summary>  
  59.         /// 数据转换,网络发送需要两部分数据,一是数据长度,二是主体数据  
  60.         /// </summary>  
  61.         /// <param name="message"></param>  
  62.         /// <returns></returns>  
  63.         private static byte[] WriteMessage(byte[] message)  
  64.         {  
  65.             MemoryStream ms = null;  
  66.             using (ms = new MemoryStream())  
  67.             {  
  68.                 ms.Position = 0;  
  69.                 BinaryWriter writer = new BinaryWriter(ms);  
  70.                 ushort msglen = (ushort)message.Length;  
  71.                 writer.Write(msglen);  
  72.                 writer.Write(message);  
  73.                 writer.Flush();  
  74.                 return ms.ToArray();  
  75.             }  
  76.         }  
  77.   
  78.         /// <summary>  
  79.         /// 接收指定客户端Socket的消息  
  80.         /// </summary>  
  81.         /// <param name="clientSocket"></param>  
  82.         private static void RecieveMessage(object clientSocket)  
  83.         {  
  84.             Socket mClientSocket = (Socket)clientSocket;  
  85.             while (true)  
  86.             {  
  87.                 try  
  88.                 {  
  89.                     int receiveNumber = mClientSocket.Receive(result);  
  90.                     Console.WriteLine("接收客户端{0}消息, 长度为{1}", mClientSocket.RemoteEndPoint.ToString(), receiveNumber);  
  91.                     ByteBuffer buff = new ByteBuffer(result);  
  92.                     //数据长度  
  93.                     int len = buff.ReadShort();  
  94.                     //数据内容  
  95.                     string data = buff.ReadString();  
  96.                     Console.WriteLine("数据内容:{0}", data);  
  97.                 }  
  98.                 catch (Exception ex)  
  99.                 {  
  100.                     Console.WriteLine(ex.Message);  
  101.                     mClientSocket.Shutdown(SocketShutdown.Both);  
  102.                     mClientSocket.Close();  
  103.                     break;  
  104.                 }  
  105.             }  
  106.         }  
  107.     }  
  108. }  


2.客户端创建:

        客户端连接服务器的逻辑相对简单一些,跟服务器一样,先把ByteBuffer类导入到工程中,基本步骤如下:

  • 创建一个Socket对象,这个对象在客户端是唯一的,可以理解为单例模式;
  • 使用上面创建Socket连接指定服务器IP和端口号;
  • 接收服务器数据和发送数据给服务器。
        先创建一个ClientSocket类用于管理Socket的一些方法:连接服务器、接受数据和发送数据等:
[csharp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. using UnityEngine;  
  2. using System.Collections;  
  3. using System.Net;  
  4. using System.Net.Sockets;  
  5. using System.IO;  
  6.   
  7. namespace Net  
  8. {  
  9.     public class ClientSocket  
  10.     {  
  11.         private static byte[] result = new byte[1024];  
  12.         private static Socket clientSocket;  
  13.         //是否已连接的标识  
  14.         public bool IsConnected = false;  
  15.   
  16.         public ClientSocket(){  
  17.             clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  
  18.         }  
  19.   
  20.         /// <summary>  
  21.         /// 连接指定IP和端口的服务器  
  22.         /// </summary>  
  23.         /// <param name="ip"></param>  
  24.         /// <param name="port"></param>  
  25.         public void ConnectServer(string ip,int port)  
  26.         {  
  27.             IPAddress mIp = IPAddress.Parse(ip);  
  28.             IPEndPoint ip_end_point = new IPEndPoint(mIp, port);  
  29.   
  30.             try {  
  31.                 clientSocket.Connect(ip_end_point);  
  32.                 IsConnected = true;  
  33.                 Debug.Log("连接服务器成功");  
  34.             }  
  35.             catch  
  36.             {  
  37.                 IsConnected = false;  
  38.                 Debug.Log("连接服务器失败");  
  39.                 return;  
  40.             }  
  41.             //服务器下发数据长度  
  42.             int receiveLength = clientSocket.Receive(result);  
  43.             ByteBuffer buffer = new ByteBuffer(result);  
  44.             int len = buffer.ReadShort();  
  45.             string data = buffer.ReadString();  
  46.             Debug.Log("服务器返回数据:" + data);  
  47.         }  
  48.   
  49.         /// <summary>  
  50.         /// 发送数据给服务器  
  51.         /// </summary>  
  52.         public void SendMessage(string data)  
  53.         {  
  54.             if (IsConnected == false)  
  55.                 return;  
  56.             try  
  57.             {  
  58.                 ByteBuffer buffer = new ByteBuffer();  
  59.                 buffer.WriteString(data);  
  60.                 clientSocket.Send(WriteMessage(buffer.ToBytes()));  
  61.             }  
  62.             catch  
  63.             {  
  64.                 IsConnected = false;  
  65.                 clientSocket.Shutdown(SocketShutdown.Both);  
  66.                 clientSocket.Close();  
  67.             }  
  68.         }  
  69.   
  70.         /// <summary>  
  71.         /// 数据转换,网络发送需要两部分数据,一是数据长度,二是主体数据  
  72.         /// </summary>  
  73.         /// <param name="message"></param>  
  74.         /// <returns></returns>  
  75.         private static byte[] WriteMessage(byte[] message)  
  76.         {  
  77.             MemoryStream ms = null;  
  78.             using (ms = new MemoryStream())  
  79.             {  
  80.                 ms.Position = 0;  
  81.                 BinaryWriter writer = new BinaryWriter(ms);  
  82.                 ushort msglen = (ushort)message.Length;  
  83.                 writer.Write(msglen);  
  84.                 writer.Write(message);  
  85.                 writer.Flush();  
  86.                 return ms.ToArray();  
  87.             }  
  88.         }  
  89.     }  
  90. }  


三、样例测试:

1.客户端测试:

        在Unity中写一个测试脚本TestSocket.cs,并将此脚本绑到当前场景的相机上:

[csharp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. using UnityEngine;  
  2. using System.Collections;  
  3. using Net;  
  4.   
  5. public class TestSocket : MonoBehaviour {  
  6.   
  7.     // Use this for initialization  
  8.     void Start () {  
  9.         ClientSocket mSocket = new ClientSocket();  
  10.         mSocket.ConnectServer("127.0.0.1", 8088);  
  11.         mSocket.SendMessage("服务器傻逼!");  
  12.     }  
  13.       
  14.     // Update is called once per frame  
  15.     void Update () {  
  16.       
  17.     }  
  18. }  

2.启动服务器:

        在Visual Studio中点击运行按钮,启动服务器:

        

        启动正常的话,会弹出一个窗口如下图所示:

        


3.开始连接:

        在Unity中运行当前场景,查看输出日志,假如连接成功,输出如下:

        

        查看服务器窗口,发现双向通信都正常:

         


四、总结:

        这里测试案例其实很简单,协议没有进行如何优化,单纯地发送字符串数据而已,假如针对复杂的数据的话,需要创建完整打包和解包协议数据的机制,而且必要时还需要对数据进行加密操作。

0 0
原创粉丝点击