目录
本篇文章来分享一下Socket编程,主要了解TCP编程、UDP编程。
1.Socket编程
1)Socket编程基于TCP/UDP协议的网络编程,是程序员写代码实现设备通信的具体步骤;
2)所有网络通信都是服务端+客户端模式:服务端是提供数据/服务的一方,客户端是请求数据/服务的一方。
2.TCP编程
2.1.具体步骤
TCP是C/S架构,核心:有连接、双向交互。
✅ 服务端(被动方:等待客户端连接,永远先启动)【4步】
【1】①创建服务端的通讯对象/监听对象 → 准备好通信管道;
【2】②启动监听对象,绑定IP地址+端口号 → 服务端开始在指定端口等待客户端连接;
【5】③阻塞等待,接受客户端的连接请求 → TCP 的核心步骤,和客户端完成「三次握手」,建立可靠连接;
【6】④数据交换:收发数据(本质是网络流的读写) → 客户端发数据,服务端收;服务端发数据,客户端收,双向通信;
✅ 客户端(主动方:主动连接服务端,后启动)【3 步】
【3】①创建客户端的通讯对象 → 准备好通信管道;
【4】②调用连接方法,连接指定的服务器(IP +端口) → 向服务端发起连接请求,完成三次握手;
【7】③数据交换:收发数据(网络流的读写) → 和服务端互相传输数据;
2.2.TCP编程步骤核心特点
1)必须先启动服务端,再启动客户端,否则客户端连接失败;
2)建立连接后,服务端和客户端是一对一的专属通信,稳定可靠;
3)所有步骤不可逆,必须按顺序执行。
2.3.注意
1)TCP 传输完成后,服务端和客户端还需要执行「关闭连接」,完成四次挥手。
2)标注的数字(如【1】)是执行顺序,绝对不能颠倒!
2.4.实现TCP编程
使用本地测试,服务端是控制台程序,本地启动后,Unity客户端连127.0.0.1即可通信,完全免费、无网络限制、稳定。
1)新建控制台程序


2)实现服务端
cs
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace UnityTCPServer
{
/// <summary>
/// TCP服务端
/// </summary>
class TCPServer
{
static void Main(string[] args)
{
Console.WriteLine("TCP服务端:本地控制台服务端");
Console.WriteLine("服务端已启动,监听端口:9999");
Console.WriteLine("等待Unity客户端连接...\n");
//【1】①创建监听对象
TcpListener server = new TcpListener(IPAddress.Any, 9999);
try
{
//【2】②启动监听
server.Start();
//【5】③阻塞等待客户端连接 (控制台不怕阻塞)
Socket socket = server.AcceptSocket();
Console.WriteLine($"[{DateTime.Now}]客户端连接成功!客户端IP:{socket.RemoteEndPoint}");
if (socket.Connected)
{
//【6】④数据交换:服务端【先发后收】
NetworkStream stream = new NetworkStream(socket);
StreamReader reader = new StreamReader(stream, Encoding.UTF8);
StreamWriter writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = false };
//第一步:服务端发送消息给服务端
Console.WriteLine("\n请输入要发送给Unity客户端的信息:");
string sendMsg = Console.ReadLine();
writer.WriteLine(sendMsg);
//StreamWriter有缓冲区,写入的数据会先存到缓冲区,不会立刻发送到网络,只有调用Flush()才会强制把缓冲区的所有数据发送出去,不写的话,数据发不出去,服务端就永远收不到消息
writer.Flush();//清空缓冲区,必须写,否则数据发不出去
Console.WriteLine($"[{DateTime.Now}]服务端已发送:{sendMsg}");
//第二步:服务端接收服务端的消息
string recvMsg = reader.ReadLine();
Console.WriteLine($"[{DateTime.Now}]服务端收到客户端消息:{recvMsg}");
//关闭资源
writer.Close();
reader.Close();
stream.Close();
socket.Close();
}
}
catch (Exception ex)
{
Console.WriteLine($"[{DateTime.Now}]服务端异常:{ex.Message}" );
}
finally
{
server.Stop();
Console.WriteLine($"\n[{DateTime.Now}]服务端已关闭,按任意键退出...");
Console.ReadKey();
}
}
}
}
3)实现客户端
cs
using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace SocketProgram
{
/// <summary>
/// TCP客户端
/// </summary>
public class TCPClientPanel : MonoBehaviour
{
private Button connectBtn;
private TMP_Text tipContent;
//服务器配置(本地测试:本地控制台服务端)
private string serverIp = "127.0.0.1";//"tcpbin.com";
private int serverPort = 9999;//4242;
//TCP核心对象
private TcpClient tcpClient;
private NetworkStream netStream;
private StreamReader reader;
private StreamWriter writer;
private void Awake()
{
//绑定UI组件
tipContent = transform.Find("TipContent").GetComponent<TMP_Text>();
connectBtn = transform.Find("ConnectBtn").GetComponent<Button>();
connectBtn.onClick.AddListener(OnConnectBtnClicked);
tipContent.text = "点击按钮连接TCP服务端";
}
private async void OnConnectBtnClicked()
{
connectBtn.interactable = false;//防止重复点击
tipContent.text = "正在连接服务端...";
await ConnectAndCommunicate();//异步执行,不会卡死Unity
connectBtn.interactable = true;
}
/// <summary>
/// 异步TCP连接+数据交互(核心关键)
/// </summary>
/// <returns></returns>
private async Task ConnectAndCommunicate()
{
try
{
//【3】①创建客户端对象
tcpClient = new TcpClient();
//【4】②异步连接服务器(无阻塞)
//控制台的reader.ReadLine()、writer.WriteLine()都是同步阻塞方法
//Unity是单线程渲染,一旦执行这些方法,整个Unity编辑器会卡死,直到有数据/连接
//使用异步方法(async/await)可以解决该问题
await tcpClient.ConnectAsync(serverIp, serverPort);
if (tcpClient.Connected)
{
tipContent.text = $"[{DateTime.Now}]连接服务端成功!等待接收消息...";
Socket socket = tcpClient.Client;
netStream = new NetworkStream(socket);
reader = new StreamReader(netStream, Encoding.UTF8);
writer = new StreamWriter(netStream, Encoding.UTF8) { AutoFlush = false };
//【7】③数据交换:客户端【先收后发】
//第一步:客户端接收服务端的消息
string recvMsg = await reader.ReadLineAsync();
tipContent.text += $"\n[{DateTime.Now}]收到服务端消息:{recvMsg}";
//第二步:客户端发送消息给服务端
string sendMsg = $"[{DateTime.Now}]Unity客户端收到消息啦!已回复";
await writer.WriteLineAsync(sendMsg);
writer.Flush();//必须刷新缓冲区,数据才会发出去
tipContent.text += $"\n[{DateTime.Now}]已发送消息给服务端:{sendMsg}";
}
}
catch (Exception ex)
{
tipContent.text = $"[{DateTime.Now}]连接/通信失败:{ex.Message}";
}
finally
{
//关闭所有资源,防止内存泄漏
CloseAll();
tipContent.text += $"\n\n[{DateTime.Now}]连接已关闭,可重新点击连接";
}
}
/// <summary>
/// 关闭TCP所有资源的方法
/// </summary>
private void CloseAll()
{
if (writer != null) writer.Close();
if (reader != null) reader.Close();
if (netStream != null) netStream.Close();
tcpClient?.Close();
tcpClient = null;
}
/// <summary>
/// Unity退出时关闭连接
/// </summary>
private void OnDestroy()
{
CloseAll();
}
}
}
2.5.测试TCP编程
1)先启动TCP服务端,若是不先启动,会无法连接


2)在客户端点击"连接服务端"


3)在服务端输入要给客户端发送的信息,如:Hi,我是服务端!客户端收到信息并回复后,服务端和客户端的连接断开。


也可以使用免费公网TCP测试服务器,IP为tcpbin.com,端口为4242,该服务器的功能:发什么,它就原封不动给返回什么(回声服务器),适合测试TCP收发是否正常。
3.UDP编程
3.1.具体步骤
UDP无连接,步骤极简,核心:无连接、直接收发。
UDP没有服务端/客户端的严格区分,只有发送方/接收方,双方平等,无需建立连接。
✅ 接收方【2 步】
①创建接收方的通讯对象/监听对象 → 准备好通信管道,绑定IP +端口;
②数据交换:直接收发数据(网络流读写) → 无需等待连接,只要有数据过来,直接接收即可。
✅ 发送方【2 步】
①创建发送方的通讯对象 → 准备好通信管道;
②数据交换:直接收发数据 → 无需连接,直接把数据发送到目标IP +端口即可,发完即结束。
3.2.UDP编程步骤核心特点
1)无需建立连接,没有握手和挥手,代码极简;
2)发送方发完数据就完事,不管接收方是否在线、是否收到。
3.3.注意
UDP无连接,客户端可以先发数据,服务端后接收;或者服务端先发,客户端后接收。
3.4.实现UDP编程
使用本地测试,服务端是控制台程序,本地启动后,Unity客户端连127.0.0.1即可通信,完全免费、无网络限制、稳定。
1)新建控制台程序,步骤同TCP
2)实现服务端
cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace ConsoleUDPServer
{
/// <summary>
/// UDP服务端
/// </summary>
class UDPServer
{
static void Main(string[] args)
{
Console.WriteLine("UDP服务端:本地控制台服务端");
Console.WriteLine("服务端绑定端口:8888 (接收客户端消息)");
Console.WriteLine("客户端绑定端口:9999 (接收服务端消息)");
//UDP配置,端口分离
int serverListenPort = 8888;
string clientIP = "127.0.0.1";
int clientListenPort = 9999;
//①创建创建监听对象
//绑定8888端口,持续监听+发送
UdpClient udpServer = new UdpClient(serverListenPort);
IPEndPoint clientPoint = new IPEndPoint(IPAddress.Parse(clientIP), clientListenPort);
try
{
//②数据交换
//1)作为接收方,接收数据
//新开线程:持续监听8888端口,接收客户端发送的消息
System.Threading.Thread receiveThread = new System.Threading.Thread(() =>
{
//初始化在循环外,避免每次接收都重新创建,导致无法正确获取客户端地址
IPEndPoint remotePoint = new IPEndPoint(IPAddress.Any, 0);
while (true)
{
try
{
byte[] recvBytes = udpServer.Receive(ref remotePoint);
string recvInfo = Encoding.UTF8.GetString(recvBytes);
Console.WriteLine($"\n[{DateTime.Now}]【收到客户端消息】:{recvInfo}");
Console.WriteLine($"消息来源:{remotePoint}\n");
}
catch { }
}
});
receiveThread.IsBackground = true;
receiveThread.Start();
//2)作为发送方,发送数据
//主线程:持续输入内容,发送消息给客户端
while (true)
{
Console.WriteLine("\n请输入要发送给客户端的消息:");
string sendInfo = Console.ReadLine();
byte[] sendBytes = Encoding.UTF8.GetBytes(sendInfo);
udpServer.Send(sendBytes, sendBytes.Length, clientPoint);
Console.WriteLine($"[{DateTime.Now}]服务端已发送:{sendInfo} → 客户端({clientIP}:{clientListenPort})");
}
}
catch (Exception ex)
{
Console.WriteLine($"[{DateTime.Now}]发送失败:{ex.Message}");
}
finally
{
udpServer.Close();
Console.WriteLine($"[{DateTime.Now}]服务端已关闭");
}
}
}
}
3)实现客户端
cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace SocketProgram
{
/// <summary>
/// UDP客户端
/// </summary>
public class UDPClientPanel : MonoBehaviour
{
private TMP_Text tipContent;
private Button sendBtn;
private TMP_InputField inputMsg;
//UDP配置:端口分离,永不冲突
private int clientListenPort = 9999;//客户端自己的监听端口(收消息用)
private string serverIP = "127.0.0.1";
private int serverListenPort = 8888;//服务端的监听端口(给服务端发消息必须发这个端口)
private UdpClient udpClient_Receive;//专门用于【接收】的UDP对象
private UdpClient udpClient_Send;//专门用于【发送】的UDP对象
private void Awake()
{
tipContent = transform.Find("TipContent").GetComponent<TMP_Text>();
sendBtn = transform.Find("SendBtn").GetComponent<Button>();
inputMsg = transform.Find("SendInputField").GetComponent<TMP_InputField>();
sendBtn.onClick.AddListener(OnSendBtnClick);
StartReceive();
tipContent.text = $"UDP客户端就绪\n1.已监听端口:{clientListenPort},等待接收服务端消息\n2.输入内容点击发送:给服务端发消息";
}
private async void StartReceive()
{
await ReceiveUDPData();
}
/// <summary>
/// 异步接收UDP数据,持续监听
/// </summary>
private async Task ReceiveUDPData()
{
try
{
//1)作为接收方
//①创建监听对象
//绑定9999端口,固定监听,永不随机
udpClient_Receive = new UdpClient(clientListenPort);
//持续接收,直到手动停止
while (true)
{
//②数据交换
UdpReceiveResult result = await udpClient_Receive.ReceiveAsync();
string recvInfo = Encoding.UTF8.GetString(result.Buffer);
if (tipContent != null)
{
tipContent.text = $"[{DateTime.Now}]收到消息:{recvInfo}\n来源:{result.RemoteEndPoint}\n持续监听9999端口中...";
}
}
}
catch (SocketException ex)
{
if (tipContent != null)
{
if (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
{
tipContent.text = $"[{DateTime.Now}]接收失败:9999端口被占用,请关闭其他程序重试";
}
else
{
tipContent.text = $"[{DateTime.Now}]接收异常:{ex.Message}";
}
}
}
catch (Exception ex)
{
if (tipContent != null) tipContent.text = $"[{DateTime.Now}]接收失败:{ex.Message}";
}
finally
{
CloseAll();
}
}
private async void OnSendBtnClick()
{
if (string.IsNullOrEmpty(inputMsg?.text))
{
if (tipContent != null) tipContent.text = "请输入要发送的消息!";
return;
}
if (sendBtn != null) sendBtn.interactable = false;
string sendStr = inputMsg.text;
if (tipContent != null) tipContent.text = $"正在发送消息:{sendStr}";
await SendUDPData(sendStr);
if (sendBtn != null) sendBtn.interactable = true;
if (tipContent != null)
{
tipContent.text = $"[{DateTime.Now}]发送成功:{sendStr}\n发送至:{serverIP}:{serverListenPort}\n客户端可继续收发消息";
}
}
private async Task SendUDPData(string sendStr)
{
try
{
//2)作为发送方
//①创建监听对象
//发送时不要绑定9999端口,让系统随机分配发送端口
using (UdpClient sendClient = new UdpClient())
{
//②数据交换
byte[] sendBytes = Encoding.UTF8.GetBytes(sendStr);
IPEndPoint serverPoint = new IPEndPoint(IPAddress.Parse(serverIP), serverListenPort);
await sendClient.SendAsync(sendBytes, sendBytes.Length, serverPoint);
}
}
catch (Exception ex)
{
if (tipContent != null) tipContent.text = $"[{DateTime.Now}]发送失败:{ex.Message}";
}
}
/// <summary>
/// 安全关闭UDP资源,释放端口
/// </summary>
private void CloseAll()
{
udpClient_Receive?.Close();
udpClient_Send?.Close();
udpClient_Receive = null;
udpClient_Send = null;
}
/// <summary>
/// Unity退出/销毁时,安全关闭所有资源
/// </summary>
private void OnDestroy()
{
CloseAll();
//清空所有引用
tipContent = null;
sendBtn = null;
inputMsg = null;
}
}
}
3.5.测试UDP编程
1)启动服务端、客户端


2)客户端发数据,服务端接收


3)服务端发数据,客户端接收


双方可以反复发送,持续监听。
4.总结
TCP:面向连接、可靠、有序、慢
UDP:无连接、不可靠、无序、快
想继续了解TCP和UDP可以参考【一文了解】TCP协议和UDP协议
好了,本次的分享到这里就结束啦,希望对你有所帮助~