1. 程式人生 > >Unity3D —— Socket通訊(C#)

Unity3D —— Socket通訊(C#)

前言:

        在開始編寫程式碼之前,我們首先需要明確:聯網方式聯網步驟資料收發以及協議資料格式

        當然在設計時也應該減低程式碼的耦合性,儘量使得網路層可以在其他地方進行復用,這就需要我們進行介面式的開發。我們這裡使用的通訊模式是Socket強連線的通訊方式,並且使用C#作為程式語言,其實與.NET的Socket通訊是一致的。

一、設計思想:

        為了方便測試,我直接使用C#寫的一個控制檯應用,作為伺服器,等待客戶端的連線,然後使用Unity建立Socket客戶端去連線伺服器,進行簡單的資料通訊。這麼設計的原因是都基於.net進行開發,也方便理解。

二、實現步驟:

        對於網路通訊有所瞭解的都應該知道,資料在網路傳輸的格式必須以位元組流的形式進行,那麼免不了要對位元組流進行寫入和讀出操作,為了方便後面的操作,我們有必要封裝一個讀寫位元組流的操作類,在這裡我定義了一個位元組流的操作類ByteBuffer類,用於將各個型別資料寫入流中,也可從位元組流中讀取各種型別的資料:

using System.IO;
using System.Text;
using System;

namespace Net {
    public class ByteBuffer {
        MemoryStream stream = null;
        BinaryWriter writer = null;
        BinaryReader reader = null;

        public ByteBuffer() {
            stream = new MemoryStream();
            writer = new BinaryWriter(stream);
        }

        public ByteBuffer(byte[] data) {
            if (data != null) {
                stream = new MemoryStream(data);
                reader = new BinaryReader(stream);
            } else {
                stream = new MemoryStream();
                writer = new BinaryWriter(stream);
            }
        }

        public void Close() {
            if (writer != null) writer.Close();
            if (reader != null) reader.Close();

            stream.Close();
            writer = null;
            reader = null;
            stream = null;
        }

        public void WriteByte(byte v) {
            writer.Write(v);
        }

        public void WriteInt(int v) {
            writer.Write((int)v);
        }

        public void WriteShort(ushort v) {
            writer.Write((ushort)v);
        }

        public void WriteLong(long v) {
            writer.Write((long)v);
        }

        public void WriteFloat(float v) {
            byte[] temp = BitConverter.GetBytes(v);
            Array.Reverse(temp);
            writer.Write(BitConverter.ToSingle(temp, 0));
        }

        public void WriteDouble(double v) {
            byte[] temp = BitConverter.GetBytes(v);
            Array.Reverse(temp);
            writer.Write(BitConverter.ToDouble(temp, 0));
        }

        public void WriteString(string v) {
            byte[] bytes = Encoding.UTF8.GetBytes(v);
            writer.Write((ushort)bytes.Length);
            writer.Write(bytes);
        }

        public void WriteBytes(byte[] v) {
            writer.Write((int)v.Length);
            writer.Write(v);
        }

        public byte ReadByte() {
            return reader.ReadByte();
        }

        public int ReadInt() {
            return (int)reader.ReadInt32();
        }

        public ushort ReadShort() {
            return (ushort)reader.ReadInt16();
        }

        public long ReadLong() {
            return (long)reader.ReadInt64();
        }

        public float ReadFloat() {
            byte[] temp = BitConverter.GetBytes(reader.ReadSingle());
            Array.Reverse(temp);
            return BitConverter.ToSingle(temp, 0);
        }

        public double ReadDouble() {
            byte[] temp = BitConverter.GetBytes(reader.ReadDouble());
            Array.Reverse(temp);
            return BitConverter.ToDouble(temp, 0);
        }

        public string ReadString() {
            ushort len = ReadShort();
            byte[] buffer = new byte[len];
            buffer = reader.ReadBytes(len);
            return Encoding.UTF8.GetString(buffer);
        }

        public byte[] ReadBytes() {
            int len = ReadInt();
            return reader.ReadBytes(len);
        }

        public byte[] ToBytes() {
            writer.Flush();
            return stream.ToArray();
        }

        public void Flush() {
            writer.Flush();
        }
    }
}

        使用此操作類進行讀寫資料的操作範例如下:

  • 讀取資料:
       //result是位元組陣列byte[],從中讀取兩個int型別的資料
        ByteBuffer buff = new ByteBuffer(result);
        int len = buff.ReadShort();
        int protoId = buff.ReadShort();
  • 寫入資料:
        //result是位元組陣列byte[],從寫入兩個不同型別的資料
        ByteBuffer buff = new ByteBuffer();
        int protoId = ProtoDic.GetProtoIdByProtoType(0);
        buff.WriteShort((ushort)protoId);
        buff.WriteBytes(ms.ToArray());
        byte[] result = buff.ToBytes();

1.伺服器建立:

        在VS中新建一個C#控制檯應用,新建專案完成後將上面定義的ByteBuffer.cs類匯入工程中,然後開始在入口類Program中開始建立Socket伺服器的邏輯。

        先引入必要的名稱空間:

using System.Net;
using System.Net.Sockets;
using System.Threading;
        基本的步驟如下:
  • 建立一個伺服器Socket物件,並繫結伺服器IP地址和埠號;
        private const int port = 8088;
        private static string IpStr = "127.0.0.1";
        private static Socket serverSocket;

        static void Main(string[] args)
        {
            IPAddress ip = IPAddress.Parse(IpStr);
            IPEndPoint ip_end_point = new IPEndPoint(ip, port);
            //建立伺服器Socket物件,並設定相關屬性
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //繫結ip和埠
            serverSocket.Bind(ip_end_point);
            //設定最長的連線請求佇列長度
            serverSocket.Listen(10);
            Console.WriteLine("啟動監聽{0}成功", serverSocket.LocalEndPoint.ToString());
        }
        完成上述程式碼之後,已經能正常啟動一個伺服器Socket,但是還沒有處理連線監聽邏輯和資料接收,所以執行應用會出現一閃就關掉的情況。
  • 啟動一個執行緒,並在執行緒中監聽客戶端的連線,為每個連線建立一個Socket物件;
  • 建立接受資料和傳送資料的方法。
        完整的程式碼如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Threading;
using Net;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        private static byte[] result = new byte[1024];
        private const int port = 8088;
        private static string IpStr = "127.0.0.1";
        private static Socket serverSocket;

        static void Main(string[] args)
        {
            IPAddress ip = IPAddress.Parse(IpStr);
            IPEndPoint ip_end_point = new IPEndPoint(ip, port);
            //建立伺服器Socket物件,並設定相關屬性
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //繫結ip和埠
            serverSocket.Bind(ip_end_point);
            //設定最長的連線請求佇列長度
            serverSocket.Listen(10);
            Console.WriteLine("啟動監聽{0}成功", serverSocket.LocalEndPoint.ToString());
            //在新執行緒中監聽客戶端的連線
            Thread thread = new Thread(ClientConnectListen);
            thread.Start();
            Console.ReadLine();
        }

        /// <summary>
        /// 客戶端連線請求監聽
        /// </summary>
        private static void ClientConnectListen()
        {
            while (true)
            {
                //為新的客戶端連線建立一個Socket物件
                Socket clientSocket = serverSocket.Accept();
                Console.WriteLine("客戶端{0}成功連線", clientSocket.RemoteEndPoint.ToString());
                //向連線的客戶端傳送連線成功的資料
                ByteBuffer buffer = new ByteBuffer();
                buffer.WriteString("Connected Server");
                clientSocket.Send(WriteMessage(buffer.ToBytes()));
                //每個客戶端連線建立一個執行緒來接受該客戶端傳送的訊息
                Thread thread = new Thread(RecieveMessage);
                thread.Start(clientSocket);
            }
        }

        /// <summary>
        /// 資料轉換,網路傳送需要兩部分資料,一是資料長度,二是主體資料
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        private static byte[] WriteMessage(byte[] message)
        {
            MemoryStream ms = null;
            using (ms = new MemoryStream())
            {
                ms.Position = 0;
                BinaryWriter writer = new BinaryWriter(ms);
                ushort msglen = (ushort)message.Length;
                writer.Write(msglen);
                writer.Write(message);
                writer.Flush();
                return ms.ToArray();
            }
        }

        /// <summary>
        /// 接收指定客戶端Socket的訊息
        /// </summary>
        /// <param name="clientSocket"></param>
        private static void RecieveMessage(object clientSocket)
        {
            Socket mClientSocket = (Socket)clientSocket;
            while (true)
            {
                try
                {
                    int receiveNumber = mClientSocket.Receive(result);
                    Console.WriteLine("接收客戶端{0}訊息, 長度為{1}", mClientSocket.RemoteEndPoint.ToString(), receiveNumber);
                    ByteBuffer buff = new ByteBuffer(result);
                    //資料長度
                    int len = buff.ReadShort();
                    //資料內容
                    string data = buff.ReadString();
                    Console.WriteLine("資料內容:{0}", data);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    mClientSocket.Shutdown(SocketShutdown.Both);
                    mClientSocket.Close();
                    break;
                }
            }
        }
    }
}

2.客戶端建立:

        客戶端連線伺服器的邏輯相對簡單一些,跟伺服器一樣,先把ByteBuffer類匯入到工程中,基本步驟如下:

  • 建立一個Socket物件,這個物件在客戶端是唯一的,可以理解為單例模式;
  • 使用上面建立Socket連線指定伺服器IP和埠號;
  • 接收伺服器資料和傳送資料給伺服器。
        先建立一個ClientSocket類用於管理Socket的一些方法:連線伺服器、接受資料和傳送資料等:
using UnityEngine;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace Net
{
    public class ClientSocket
    {
        private static byte[] result = new byte[1024];
        private static Socket clientSocket;
        //是否已連線的標識
        public bool IsConnected = false;

        public ClientSocket(){
            clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        /// <summary>
        /// 連線指定IP和埠的伺服器
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        public void ConnectServer(string ip,int port)
        {
            IPAddress mIp = IPAddress.Parse(ip);
            IPEndPoint ip_end_point = new IPEndPoint(mIp, port);

            try {
                clientSocket.Connect(ip_end_point);
                IsConnected = true;
                Debug.Log("連線伺服器成功");
            }
            catch
            {
                IsConnected = false;
                Debug.Log("連線伺服器失敗");
                return;
            }
            //伺服器下發資料長度
            int receiveLength = clientSocket.Receive(result);
            ByteBuffer buffer = new ByteBuffer(result);
            int len = buffer.ReadShort();
            string data = buffer.ReadString();
            Debug.Log("伺服器返回資料:" + data);
        }

        /// <summary>
        /// 傳送資料給伺服器
        /// </summary>
        public void SendMessage(string data)
        {
            if (IsConnected == false)
                return;
            try
            {
                ByteBuffer buffer = new ByteBuffer();
                buffer.WriteString(data);
                clientSocket.Send(WriteMessage(buffer.ToBytes()));
            }
            catch
            {
                IsConnected = false;
                clientSocket.Shutdown(SocketShutdown.Both);
                clientSocket.Close();
            }
        }

        /// <summary>
        /// 資料轉換,網路傳送需要兩部分資料,一是資料長度,二是主體資料
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        private static byte[] WriteMessage(byte[] message)
        {
            MemoryStream ms = null;
            using (ms = new MemoryStream())
            {
                ms.Position = 0;
                BinaryWriter writer = new BinaryWriter(ms);
                ushort msglen = (ushort)message.Length;
                writer.Write(msglen);
                writer.Write(message);
                writer.Flush();
                return ms.ToArray();
            }
        }
    }
}

三、樣例測試:

1.客戶端測試:

        在Unity中寫一個測試指令碼TestSocket.cs,並將此指令碼綁到當前場景的相機上:

using UnityEngine;
using System.Collections;
using Net;

public class TestSocket : MonoBehaviour {

	// Use this for initialization
	void Start () {
        ClientSocket mSocket = new ClientSocket();
        mSocket.ConnectServer("127.0.0.1", 8088);
        mSocket.SendMessage("伺服器傻逼!");
    }
	
	// Update is called once per frame
	void Update () {
	
	}
}

2.啟動伺服器:

        在Visual Studio中點選執行按鈕,啟動伺服器:

        

        啟動正常的話,會彈出一個視窗如下圖所示:

        

3.開始連線:

        在Unity中運行當前場景,檢視輸出日誌,假如連線成功,輸出如下:

        

        檢視伺服器視窗,發現雙向通訊都正常:

         

四、總結:

        這裡測試案例其實很簡單,協議沒有進行如何優化,單純地傳送字串資料而已,假如針對複雜的資料的話,需要建立完整打包和解包協議資料的機制,而且必要時還需要對資料進行加密操作。

相關推薦

Unity3D —— Socket通訊(C#)

前言:         在開始編寫程式碼之前,我們首先需要明確:聯網方式、聯網步驟、資料收發以及協議資料格式         當然在設計時也應該減低程式碼的耦合性,儘量使得網路層可以在其他地方進行復用,這就需要我們進行介面式的開發。我們這裡使用的通訊模式是Socket強連線

Socket通訊——C++伺服器端和Java客戶端

//更新 這件事可以用現有的序列化框架來做 比如 protobuf 一句話來說就是,C++和Java 通過socket進行通訊、資料傳輸,通過傳送“位元組流”即可。 位元組對於C++和java來說是通用的,但是傳輸的過程有許多問題需要注意,我為了弄清楚這個過程,查了一些資料

java與c語言之間的socket通訊c客戶端java伺服器端

寫在前面的宣告:程式例子均執行在ubuntu(是一個以桌面應用為主的Linux作業系統)上。當然你也可以把java執行在其它系統上,這裡只是為了方便。 上一篇文章已經說明了關於socket的一些知識,but這是遠遠不夠的,我相信只要你感興趣,學習它並不是什麼難事。 好吧,我

基於Socket通訊(C#)和WebSocket協議(net)編寫的兩種聊天功能(文末附源碼下載地址)

消息 客戶端和服務器端 win 屬性 比較 com 端口 caption .html 轉載:https://www.cnblogs.com/xiongze520/p/10338802.html 今天我們來盤一盤Socket通訊和WebSocket協議在即時通訊的小應

Socket通訊 C#寫服務商 Delphi客戶端

      摘要: 最近在做Wince開發,搞一個超市賣場採用手持機盤點的現場作業模組。通訊部分的實現有兩種,其一是通過USB線把資料拷到PDA 上,掃描條碼後,查詢本地的商品資料庫(用SQLite做本地庫),盤點完成後再通過USB把盤點結果匯入伺服器(還是通過讀取SQLi

C#socket通訊服務器(連接狀態監控)

del endpoint etc acc ipa ack ipaddress ava listening class SocketServerManager { public delegate void ConnectStateEventHandler

C#Socket通訊基礎(非同步Socket通訊TCP)伺服器與客戶端

一、效果圖 二、伺服器端程式碼(原始碼下載地址:https://download.csdn.net/download/xiaochenxihua/10748789) using System; using System.Collections.Generic; using System

C#Socket通訊基礎知識(非同步Socket通訊TCP)

一、Socket通訊基礎 《1》TCP/IP層次模型 這裡只討論重要的四層        01,應用層(Application):應用層是個很廣泛的概念,有一些基本相同的系統級TCP/IP應用以及應用協議,也有許多的企業應用和網際網路應用。http

C#Socket通訊基礎(非同步Socket通訊UDP)

一、通訊原理參考https://blog.csdn.net/xiaochenXIHUA/article/details/83446031。 非同步通訊解決同步通訊的缺點可以雙向傳送和接收資訊;非同步通訊思路是引入多執行緒機制,在一個程序中使用兩個執行緒,分別處理接收執行緒與傳送執行緒,這種機制稱

C#Socket通訊基礎(同步Socket通訊UDP)

一、UDP通訊原理 UDP協議使用無連線的套接字,因此不需要再網路裝置之間傳送連線資訊,但是必須用Bind方法繫結到一個本地地址/埠上。 ①建立套接字 ②繫結IP地址和埠作為伺服器端 ③直接使用SendTo/ReceiveFrom來執行操作 注意:同步Socket(UDP)通訊存

C# FrameworkAPI之Socket通訊

服務端: 1:建立一個socket的物件 Socket socketserver=new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 第一個引數是指定socket物件使用的定址方案,

[Visual Studio C++][Socket程式設計] Socket通訊原理詳細講解

(本文參考:https://www.cnblogs.com/wangcq/p/3520400.html  在原文的基礎上進行了擴充。)    對TCP/IP、UDP、Socket程式設計這些詞你不會很陌生吧?隨著網路技術的發展,這些詞充斥著我們的耳朵。那麼我想問

C#一個伺服器端多個客戶端Socket通訊

原理: 啟動服務端後,服務端通過持續監聽客戶端發來的請求,一旦監聽到客戶端傳來的資訊後,兩端便可以互發資訊了。伺服器端需要繫結一個IP和埠號,用於客戶端在網路中尋找並建立連線。資訊傳送原理:將手動輸入字串資訊轉換成機器可以識別的位元組陣列,然後呼叫套接字的Send()方法將位元組陣列傳送出去

Untiy中用C#實現TCP通訊Socket通訊)服務端與客戶端皆可

簡而言之,TCP通訊原理大家可以從各種網路文獻上找到,這裡不做贅述。 只提出C#實現TCP通訊的一般方法和常用程式碼工具供第一次接觸TCP通訊的玩家參考,老玩家繞道。。。 為了方便大家理解我的程式碼,會適當提及通訊遠離。 1、建立服務端,TCP連線的基本: using U

C#實現Socket通訊(同時監聽多客戶端)

//建立socket物件 //第一個引數:設定網路定址的協議、第二引數設定資料傳輸的方式、第三個引數設定通訊協議 Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketT

Socket通訊總結(附C++實現)

文章目錄[隱藏]        因為專案需要,服務端需要一個SOCKET來接收客戶端的請求,好吧,沒辦法度娘哇,結果很多都是linux的例子,功夫不負有心人啊,終於找到個demo,並且客戶端程式碼詳盡,記錄之,以便以後檢視。 一、Socket是什麼    Socket

Socket通訊中粘包分包問題的介紹和解決(C#)

最近在做Unity區域網時,用到了Socket通訊基於TCP協議,然後使用非同步方式,主要用到了BeginAccept和BeginReceive方法 然而就可以實現非同步通訊,然而還是要解決粘包和分包問題 這裡我先說明一下什麼是分包和粘包,TCP提供面向連線的、可靠的資料流傳輸,所以當我們傳

C++:實現socket通訊(TCP/IP)例項

       首先宣告,博主之前從來沒有寫過通訊方面的東西,這次之所以寫這個是因為專案需要,因此本文主要介紹一個使用C++語言及Socket來實現TCP/IP通訊的例項,希望可以幫助入門者。 一、什麼是TCP/IP?         TCP提供基於IP環境下的資料可靠性傳

c# socket通訊實現簡單的視窗資訊互相傳送 (聊天室的deom)

這次使用socket來實現簡單的視窗資訊互相傳送  首先我們建立一個伺服器端 services (winfrom檔案) 這邊注意。你的ip地址和埠號可以  命令建+r  開啟cmd  輸入ipconfig找到自己的ip地址 每個人的ip地址都不一樣 程式碼部

.net平臺下C#socket通訊(上)

在開始介紹socket前先補充補充基礎知識,在此基礎上理解網路通訊才會順理成章,當然有基礎的可以跳過去了。都是廢話,進入正題。   TCP/IP:Transmission Control Protocol/Internet Protocol,傳輸控制協議/因特網互聯協議,又