Unity網絡開發基礎 (3) Socket入門 TCP同步連接 與 簡單封裝練習

???????????????本文章不作任何商業用途 僅作學習與交流 教程來自Unity唐老獅

? ? ? ? 關于練習題部分是我觀看教程之后自己實現 所以和老師寫法可能不太一樣

????????唐老師說掌握其基本思路即可,因為前端程序一般不需要去寫后端邏輯

1.認識Socket的重要API

Socket是什么

???Socket(套接字)是計算機網絡編程中用于實現網絡通信的一種機制,它提供了不同主機之間進行數據交換的接口。通過?Socket,程序可以在網絡上發送和接收數據,實現客戶端與服務器之間的通信。在 .NET 框架中,Socket?類位于?System.Net.Sockets?命名空間,它封裝了底層的網絡通信細節,使得開發者能夠方便地進行網絡編程

創建Socket

Socket serverTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream,ProtocolType.Tcp);
參數含義常見取值及說明
AddressFamily網絡地址類型InterNetwork:IPv4;InterNetworkV6:IPv6
SocketType通信方式Stream:面向連接,用 TCP;Dgram:無連接,用 UDP
ProtocolType傳輸協議Tcp:配合?Stream?實現可靠通信;Udp:配合?Dgram?實現低延遲通信

Socket常用屬性

檢查接收緩沖區可用字節數

Socket.Available

獲取服務端本地綁定的地址信息

 IPEndPoint localEp = clientSocket.LocalEndPoint as IPEndPoint;Console.WriteLine($"服務端本地地址:{localEp.Address}, 端口:{localEp.Port}");
屬性名稱定義與作用返回值類型使用說明
Available獲取當前接收緩沖區中可讀取的字節數,用于判斷當前有多少網絡數據已到達且可被讀取int直接返回可用字節數,如?int availableBytes = socketTcp.Available;,輔助優化數據讀取邏輯
LocalEndPoint獲取套接字綁定的本地網絡端點(包含本地 IP 地址和端口號)object需強制轉換為?IPEndPoint?使用,如:
IPEndPoint localIpEndPoint = socketTcp.LocalEndPoint as IPEndPoint;
轉換后可通過?.Address?取 IP,.Port?取端口

Socket常用方法

服務端

步驟說明關鍵代碼
1-1綁定 IP 和端口,為服務端指定通信地址與端口IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
socketTcp.Bind(ipPoint);
1-2設置允許同時連接的客戶端最大數量,控制服務端連接隊列長度socketTcp.Listen(10);
1-3阻塞等待客戶端連接,成功連接后返回與客戶端通信的專屬套接字socketTcp.Accept();

客戶端

步驟說明關鍵代碼
客戶端發起連接讓客戶端套接字與目標服務端(通過 IP 地址和端口標識)建立網絡連接,構建通信鏈路socketTcp.Connect(IPAddress.Parse("118.12.123.11"), 8080);

通用

分類步驟說明關鍵代碼
客戶端服務端通用操作1釋放連接并關閉 Socket,需先調用?ShutdownsocketTcp.Shutdown(SocketShutdown.Both);
2關閉連接,釋放所有 Socket 關聯資源socketTcp.Close();
3同步發送和接收數據

通常如?socketTcp.Send(字節數組);?

socketTcp.Receive(字節數組);

4異步發送和接收數據通常涉及?BeginSend?BeginReceive?等異步方法,如?socketTcp.BeginReceive(回調, 狀態對象);

2.TCP連接(UDP同理)

服務端: 注意 服務端創建了兩個套接字

一個是服務端本體用于綁定、監聽客戶端連接

一個是通道 用于對 客戶端 收發數據

????????當然只是一個客戶端的情況 如果有多個客戶端 你可以將channel直接命名為對應客戶端的名字

如圖所示:

127.0.0.1是特殊地址 也就是 回環地址 可以不需要網絡 就讓本機成為服務端

using System.Net;
using System.Net.Sockets;
using System.Text;
//服務器端套接字流程//1 創建服務端套接字
Socket serverTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream,ProtocolType.Tcp);//2 服務端綁定門牌號 并 監聽請求客戶端連接 "127.0.0.1"是回環地址
IPEndPoint sIpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"),8080);
serverTcp.Bind(sIpoint);
serverTcp.Listen(1024);
Console.WriteLine("等待客戶端連接");//3 建立連接和等待返回  通道套接字 (三次握手以后,所以該處會阻塞線程)
Socket channelSocket = serverTcp.Accept();
Console.WriteLine("未建立連接時 不會打印這句話");//4 通過中間套接字 進行數據收發 
channelSocket.Send(Encoding.UTF8.GetBytes("服務端:歡迎連接服務端"));//服務端接收時需要有容器
byte[] result = new byte[1024]; 
int receiveNums = channelSocket.Receive(result);//返回接收的字節數
Console.WriteLine($"獲取的客戶端Ip和端口{channelSocket.RemoteEndPoint}," +$"接受到的消息為:{Encoding.UTF8.GetString(result, 0, receiveNums)}");//5 釋放連接
channelSocket.Shutdown(SocketShutdown.Both);
//6 關閉套接字
channelSocket.Close();

關于同步阻塞線程問題?

客戶端:客戶端只需要自己的套接字即可

using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;public class Socatct : MonoBehaviour
{// Start is called once before the first execution of Update after the MonoBehaviour is createdvoid Start(){//Socket clientUdp = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);//Socket clientTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//1 創建客戶端套接字Socket clientTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//2 通過IPEndPoint確定服務端ip和端口IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);//3 嘗試連接clientTcp.Connect(ipPoint);//4 客戶端接受服務端數據 和 發送數據  byte[] receiveBytes = new byte[1024];int resultNum = clientTcp.Receive(receiveBytes);clientTcp.Send(Encoding.UTF8.GetBytes("這句話是客戶端發來的"));//打印服務端來的信息Debug.Log(Encoding.UTF8.GetString(receiveBytes,0, resultNum));//5 釋放連接和關閉套接字clientTcp.Shutdown(SocketShutdown.Both);clientTcp.Close();}
}

圖解

3.服務端練習題

問題1:

using System.Net;
using System.Net.Sockets;
using System.Text;Server server = new Server();server.Init(true,false);
server.CloseAllCannel();
//服務端類
class Server
{public Socket serverSocket;public List<Socket> cannelSockets = new List<Socket>();private Thread? acceptThread;private Thread? getMsgThread;private readonly object _lockObject = new object();/// <summary>/// 初始化 選擇TCP還是UDP連接/// </summary>/// <param name="tcp"></param>/// <param name="udp"></param>public void Init(bool tcp, bool udp){if (tcp)serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);if (udp)serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);BindIPEndPoint();InitThread();}/// <summary>/// 綁定IP地址和端口號/// </summary>/// <param name="ip"></param>/// <param name="port"></param>private void BindIPEndPoint(string ip = "127.0.0.1", int port = 8080){IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);serverSocket.Bind(iPEndPoint);serverSocket.Listen(1024);Console.WriteLine("等待客戶端連接");}private void InitThread(){acceptThread = new Thread(AcceptClient);acceptThread.Start();getMsgThread = new Thread(GetMsgFromClient);getMsgThread.Start();}/// <summary>/// 等待客戶端連接/// </summary>private void AcceptClient(){while (true){Socket cannelSocket = serverSocket.Accept();// 默認發送消息cannelSocket.Send(Encoding.UTF8.GetBytes("歡迎來到服務器"));lock (_lockObject){cannelSockets.Add(cannelSocket);}}}private void GetMsgFromClient(){//容器 1024 * 1Kb = 1Mbbyte[] buffer = new byte[1024 * 1024];int length;while (true){List<Socket> socketsCopy;lock (_lockObject){socketsCopy = new List<Socket>(cannelSockets);}foreach (Socket cannelSocket in socketsCopy){if (cannelSocket.Available > 0) //有數據可讀{length = cannelSocket.Receive(buffer);//消息處理交給新線程ThreadPool.QueueUserWorkItem(HandleMsg, (cannelSocket, Encoding.UTF8.GetString(buffer, 0, length)));}}}}private void HandleMsg(object msg){(Socket s, string srt) info = ((Socket s, string srt))msg;Console.WriteLine($"客戶端IP以及端口號=>{info.s.RemoteEndPoint}" +$"發送消息:{info.srt}");}public void CloseAllCannel(){string input = Console.ReadLine();while (true){if (input == "Quit"){lock (_lockObject){foreach (Socket cannelSocket in cannelSockets){cannelSocket.Shutdown(SocketShutdown.Both);cannelSocket.Close();}cannelSockets.Clear();Console.WriteLine("已關閉所有連接");}break;}}}
}

問題2?

就是把服務端的Channel(也就是對應Client的通道 封裝成一個類)?

using System.Net;
using System.Net.Sockets;
using System.Text;Server server = new Server();
server.Init(true, false);
server.CloseAllCannel();// 通道類,封裝channelSocket相關操作
class Channel
{private readonly Socket _socket;private readonly byte[] _buffer = new byte[1024 * 1024];public Channel(Socket socket){_socket = socket;}// 發送消息public void SendMessage(string message){byte[] data = Encoding.UTF8.GetBytes(message);_socket.Send(data);}// 接收消息(異步處理更合適,這里簡化示例)public string? ReceiveMessage(){if (_socket.Available > 0){int length = _socket.Receive(_buffer);return Encoding.UTF8.GetString(_buffer, 0, length);}return null;}// 關閉通道public void Close(){_socket.Shutdown(SocketShutdown.Both);_socket.Close();}public IPEndPoint RemoteEndPoint => (IPEndPoint)_socket.RemoteEndPoint!;
}// 服務端類
class Server
{public Socket serverSocket;private List<Channel> channelList = new List<Channel>();private Thread? acceptThread;private Thread? getMsgThread;private readonly object _lockObject = new object();/// <summary>/// 初始化 選擇TCP還是UDP連接/// </summary>/// <param name="tcp"></param>/// <param name="udp"></param>public void Init(bool tcp, bool udp, string ip = "127.0.0.1", int port = 8080){if (tcp)serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);if (udp)serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);BindIPEndPoint(ip, port);InitThread();}/// <summary>/// 綁定IP地址和端口號/// </summary>/// <param name="ip"></param>/// <param name="port"></param>public void BindIPEndPoint(string ip, int port){IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);serverSocket.Bind(iPEndPoint);serverSocket.Listen(1024);Console.WriteLine("等待客戶端連接");}private void InitThread(){acceptThread = new Thread(AcceptClient);acceptThread.Start();getMsgThread = new Thread(GetMsgFromClient);getMsgThread.Start();}/// <summary>/// 等待客戶端連接/// </summary>private void AcceptClient(){while (true){Socket channelSocket = serverSocket.Accept();Channel channel = new Channel(channelSocket);channel.SendMessage("歡迎來到服務器");lock (_lockObject){channelList.Add(channel);}}}private void GetMsgFromClient(){while (true){List<Channel> channelsCopy;lock (_lockObject){channelsCopy = new List<Channel>(channelList);}foreach (Channel channel in channelsCopy){string? msg = channel.ReceiveMessage();if (msg != null){// 消息處理交給新線程ThreadPool.QueueUserWorkItem(HandleMsg, (channel, msg));}}}}private void HandleMsg(object state){(Channel channel, string msg) info = ((Channel channel, string msg))state;Console.WriteLine($"客戶端IP以及端口號=>{info.channel.RemoteEndPoint} 發送消息:{info.msg}");}public void CloseAllCannel(){string input = Console.ReadLine();while (true){if (input == "Quit"){lock (_lockObject){foreach (Channel channel in channelList){channel.Close();}channelList.Clear();Console.WriteLine("已關閉所有連接");}break;}}}
}

4.客戶端?練習題

問題:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;public class ClientManager
{private Socket clientTcp;private Thread receiveThread;private bool isRunning;public void Connect(string ip, int port){try{// 1 創建客戶端套接字clientTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);// 2 通過 IPEndPoint 確定服務端 ip 和端口IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse(ip), port);// 3 嘗試連接clientTcp.Connect(ipPoint);isRunning = true;// 啟動接收線程receiveThread = new Thread(ReceiveData);receiveThread.Start();}catch (Exception e){Debug.LogError($"連接失敗: {e.Message}");}}private void ReceiveData(){try{while (isRunning){byte[] receiveBytes = new byte[1024];int resultNum = clientTcp.Receive(receiveBytes);if (resultNum > 0){// 打印服務端來的信息string message = Encoding.UTF8.GetString(receiveBytes, 0, resultNum);Debug.Log($"接收到服務端消息: {message}");}}}catch (Exception e){Debug.LogError($"接收數據出錯: {e.Message}");}}public void SendMessage(string message){try{if (clientTcp != null && clientTcp.Connected){clientTcp.Send(Encoding.UTF8.GetBytes(message));}}catch (Exception e){Debug.LogError($"發送消息出錯: {e.Message}");}}public void Disconnect(){isRunning = false;if (receiveThread != null && receiveThread.IsAlive){receiveThread.Join();}if (clientTcp != null && clientTcp.Connected){// 5 釋放連接和關閉套接字clientTcp.Shutdown(SocketShutdown.Both);clientTcp.Close();}}
}

使用:

using UnityEngine;public class UseSocketClient練習題 : MonoBehaviour
{ClientManager clientManager;void Start(){clientManager = new ClientManager();// 修改連接的 IP 地址和端口號clientManager.Connect("127.0.0.1",8080);// 修改發送的消息內容clientManager.SendMessage("這是修改后的消息");}void OnDestroy(){clientManager.Disconnect();}
}

?測試

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/75468.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/75468.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/75468.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【linux】一文掌握 ssh和scp 指令的詳細用法(ssh和scp 備忘速查)

文章目錄 入門連接執行SCP配置位置SCP 選項配置示例ProxyJumpssh-copy-id SSH keygenssh-keygen產生鑰匙類型known_hosts密鑰格式 此快速參考備忘單提供了使用 SSH 的各種方法。 參考&#xff1a; OpenSSH 配置文件示例 (cyberciti.biz)ssh_config (linux.die.net) 入門 連…

真實筆試題

文章目錄 線程題樹的深度遍歷 線程題 實現一個類支持100個線程同時向一個銀行賬戶中存入一元錢.需通過同步機制消除競態條件,當所有線程執行完成后,賬戶余額必須精確等于100元 package com.itheima.thread;public class ShowMeBug {private double balance; // 賬戶余額priva…

2.2 路徑問題專題:LeetCode 63. 不同路徑 II

動態規劃解決LeetCode 63題&#xff1a;不同路徑 II&#xff08;含障礙物&#xff09; 1. 題目鏈接 LeetCode 63. 不同路徑 II 2. 題目描述 一個機器人位于 m x n 網格的左上角&#xff0c;每次只能向右或向下移動一步。網格中可能存在障礙物&#xff08;標記為 1&#xff…

2874. 有序三元組中的最大值 II

給你一個下標從 0 開始的整數數組 。nums 請你從所有滿足 的下標三元組 中&#xff0c;找出并返回下標三元組的最大值。 如果所有滿足條件的三元組的值都是負數&#xff0c;則返回 。i < j < k(i, j, k)0 下標三元組 的值等于 。(i, j, k)(nums[i] - nums[j]) * nums[k…

【論文筆記】Llama 3 技術報告

Llama 3中的頂級模型是一個擁有4050億參數的密集Transformer模型&#xff0c;并且它的上下文窗口長度可以達到128,000個tokens。這意味著它能夠處理非常長的文本&#xff0c;記住和理解更多的信息。Llama 3.1的論文長達92頁&#xff0c;詳細描述了模型的開發階段、優化策略、模…

JVM深入原理(一+二):JVM概述和JVM功能

目錄 1. JVM概述 1.1. Java程序結構 1.2. JVM作用 1.3. JVM規范和實現 2. JVM功能 2.1. 功能-編譯和運行 2.2. 功能-內存管理 2.3. 功能-即時編譯 1. JVM概述 1.1. Java程序結構 1.2. JVM作用 JVM全稱是Java Virtual Machine-Java虛擬機 JVM作用:本質上是一個運行在…

SQL Server Integration Services (SSIS) 服務無法啟動

問題現象&#xff1a; 安裝 SQL Server 2022 后&#xff0c;SQL Server Integration Services (SSIS) 服務無法啟動&#xff0c;日志報錯 “服務無法響應控制請求”&#xff08;錯誤代碼 1067&#xff09;或 “依賴服務不存在或已標記為刪除”。 快速診斷 檢查服務狀態與依賴項…

Spring Boot 定時任務的多種實現方式

&#x1f31f; 前言 歡迎來到我的技術小宇宙&#xff01;&#x1f30c; 這里不僅是我記錄技術點滴的后花園&#xff0c;也是我分享學習心得和項目經驗的樂園。&#x1f4da; 無論你是技術小白還是資深大牛&#xff0c;這里總有一些內容能觸動你的好奇心。&#x1f50d; &#x…

Java基礎之反射的基本使用

簡介 在運行狀態中&#xff0c;對于任意一個類&#xff0c;都能夠知道這個類的所有屬性和方法&#xff1b;對于任意一個對象&#xff0c;都能夠調用它的任意屬性和方法&#xff1b;這種動態獲取信息以及動態調用對象方法的功能稱為Java語言的反射機制。反射讓Java成為了一門動…

AI產品的上層建筑:提示詞工程、RAG與Agent

上節課我們拆解了 AI 產品的基礎設施建設&#xff0c;這節課我們聊聊上層建筑。這部分是產品經理日常工作的重頭戲&#xff0c;包含提示詞、RAG 和 Agent 構建。 用 AI 客服產品舉例&#xff0c;這三者的作用是這樣的&#xff1a; 提示詞能讓客服很有禮貌。比如它會說&#x…

藍橋杯刷題記錄【并查集001】(2024)

主要內容&#xff1a;并查集 并查集 并查集的題目感覺大部分都是模板題&#xff0c;上板子&#xff01;&#xff01; class UnionFind:def __init__(self, n):self.pa list(range(n))self.size [1]*n self.cnt ndef find(self, x):if self.pa[x] ! x:self.pa[x] self.fi…

海外SD-WAN專線網絡部署成本分析

作為支撐企業國際業務的重要基石&#xff0c;海外SD-WAN專線以其獨特的成本優勢和技術特性&#xff0c;正成為企業構建高效穩定的全球網絡架構的首選方案。本文將從多維度解構海外SD-WAN專線部署的核心成本要素&#xff0c;為企業的全球化網絡布局提供戰略參考。 一、基礎資源投…

操作系統(二):實時系統介紹與實例分析

目錄 一.概念 1.1 分類 1.2 主要指標 二.實現原理 三.主流實時系統對比 一.概念 實時系統&#xff08;Real-Time System, RTS&#xff09;是一類以時間確定性為核心目標的計算機系統&#xff0c;其設計需確保在嚴格的時間約束內完成任務響應。 1.1 分類 根據時間約束的嚴…

Golang的消息中間件選型

# Golang的消息中間件選型 消息中間件的作用 消息中間件是一種用于分布式系統中應用程序之間進行通信的基礎架構工具&#xff0c;它能夠有效地解耦發送者和接收者&#xff0c;并提供高可用性和可靠性的消息傳遞機制。在Golang應用程序中&#xff0c;選擇適合的消息中間件對于構…

大模型中的參數規模與顯卡匹配

在大模型訓練和推理中&#xff0c;顯卡&#xff08;GPU/TPU&#xff09;的選擇與模型參數量緊密相關&#xff0c;需綜合考慮顯存、計算能力和成本。以下是不同規模模型與硬件的匹配關系及優化策略&#xff1a; 一、參數規模與顯卡匹配參考表 模型參數量訓練階段推薦顯卡推理階…

帶頭結點 的單鏈表插入方法(頭插法與尾插法)

帶頭結點的單鏈表插入方法&#xff08;頭插法與尾插法&#xff09; 在單鏈表的操作中&#xff0c;插入是最常見的操作之一&#xff0c;本文介紹 帶頭結點的單鏈表 如何實現 后插法 和 前插法&#xff08;包括 插入法 和 后插數據交換法&#xff09;&#xff0c;并提供完整的 C …

Prometheus的工作流程

Prometheus 是一個開源的監控和告警系統&#xff0c;專為監控分布式系統而設計。它的工作流程主要包括以下幾個關鍵步驟&#xff1a; 1. 數據采集 (Scraping) 目標發現 (Service Discovery)&#xff1a; Prometheus 自動或手動配置監控目標&#xff0c;通過 DNS、Kubernetes、…

軟件工程面試題(二十二)

1、常用的設計模式有哪些&#xff1f;并寫出一段程序代碼 Factory(工廠模式)&#xff0c;Adapter(適配器模式)&#xff0c;Singleton(單例模式)&#xff0c;State(狀態模式)&#xff0c;Observer(觀察者模式) 等。 單例模式 public class Singleton{ private static Singleton …

【Pandas】pandas DataFrame select_dtypes

Pandas2.2 DataFrame Attributes and underlying data 方法描述DataFrame.index用于獲取 DataFrame 的行索引DataFrame.columns用于獲取 DataFrame 的列標簽DataFrame.dtypes用于獲取 DataFrame 中每一列的數據類型DataFrame.info([verbose, buf, max_cols, …])用于提供 Dat…

如何利用ATECLOUD測試平臺的芯片測試解決方案實現4644芯片的測試?

作為多通道 DC-DC 電源管理芯片的代表產品&#xff0c;4644 憑借 95% 以上的轉換效率、1% 的輸出精度及多重保護機制&#xff0c;廣泛應用于航天航空&#xff08;衛星電源系統&#xff09;、醫療設備&#xff08;MRI 梯度功放&#xff09;、工業控制&#xff08;伺服驅動單元&a…