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.mzph.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…