Unity中实现TCP通信

在Unity中实现TCP通信

概述

本篇博客讲述了如何在unity中实现简单的TCP通信功能,包含服务器端与客户端,并配有简易的交互界面,用来显示一些状态信息以及接收和发送的数据。

服务器

首先在服务器端初始化了一个套接字(Socket),并为它绑定上服务器的ip地址与端口号,随后通过创建子线程来监听客户端的连接情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

//建立tcp通信链接
private void ClickConnect()
{
try
{
int _port = Convert.ToInt32(inputPort); //获取端口号
string _ip = inputIp; //获取ip地址

Debug.Log(" ip 地址是 :" + _ip);
Debug.Log(" 端口号是 :" + _port);

clickConnectBtn = true; //点击了监听按钮

info = "ip地址是 : " + _ip + "端口号是 : " + _port;

//点击开始监听时 在服务端创建一个负责监听IP和端口号的Socket
socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse(_ip);
IPEndPoint point = new IPEndPoint(ip, _port); //创建对象端口

socketWatch.Bind(point); //绑定端口号

Debug.Log("监听成功!");
info = "监听成功";

socketWatch.Listen(10); //设置监听,最大同时连接10台

//创建监听线程
Thread thread = new Thread(Listen);
thread.IsBackground = true;
thread.Start(socketWatch);
}
catch { }
}

其中监听线程中主要是调用Accept进行阻塞,等待客户端的连接,在连接建立后再创建新的子线程来完成数据的发送和接收功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

/// <summary>
/// 等待客户端的连接 并且创建与之通信的Socket
/// </summary>
void Listen(object o)
{
try
{
Socket socketWatch = o as Socket;
while (true)
{
socketSend = socketWatch.Accept(); //等待接收客户端连接

Debug.Log(socketSend.RemoteEndPoint.ToString() + ":" + "连接成功!");
info = socketSend.RemoteEndPoint.ToString() + " 连接成功!";

Thread r_thread = new Thread(Received); //开启一个新线程,执行接收消息方法
r_thread.IsBackground = true;
r_thread.Start(socketSend);

Thread s_thread = new Thread(SendMessage); //开启一个新线程,执行发送消息方法
s_thread.IsBackground = true;
s_thread.Start(socketSend);
}
}
catch { }
}

接收消息的子线程将客户端发送来的数据存入字节数组,然后将其转换为字符串并进行显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

/// <summary>
/// 服务器端不停的接收客户端发来的消息
/// </summary>
void Received(object o)
{
try
{
Socket socketSend = o as Socket;
while (true)
{
byte[] buffer = new byte[1024 * 6]; //客户端连接服务器成功后,服务器接收客户端发送的消息
int len = socketSend.Receive(buffer); //实际接收到的有效字节数
if (len == 0)
{
break;
}
string str = Encoding.UTF8.GetString(buffer, 0, len);

Debug.Log("接收到的消息:" + socketSend.RemoteEndPoint + ":" + str);
recMes = str;

recTimes ++;
info = "接收到一次数据,接收次数为:" + recTimes;
Debug.Log("接收数据次数:" + recTimes);
}
}
catch { }
}

发送消息的子线程实现了从交互界面中获取从键盘输入的发送数据,然后将其转换为字节数组,并通过Send函数发送给客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

void SendMessage(object o)
{
try
{
Socket socketSend = o as Socket;
while (true)
{
if(isSendData)
{
isSendData = false;

byte[] sendByte = Encoding.UTF8.GetBytes(inputMessage);

Debug.Log("发送的数据为 :" + inputMessage);
Debug.Log("发送的数据字节长度 :" + sendByte.Length);

socketSend.Send(sendByte);
}
}
}
catch { }
}

最后在程序退出的时候关闭socket并释放相应的资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

private void OnDisable()
{
Debug.Log("begin OnDisable()");

if (clickConnectBtn)
{
try
{
socketWatch.Shutdown(SocketShutdown.Both); //禁用Socket的发送和接收功能
socketWatch.Close(); //关闭Socket连接并释放所有相关资源

socketSend.Shutdown(SocketShutdown.Both); //禁用Socket的发送和接收功能
socketSend.Close(); //关闭Socket连接并释放所有相关资源
}
catch (Exception e)
{
Debug.Log(e.Message);
}
}

Debug.Log("end OnDisable()");
}

服务器端交互界面中显示了当前TCP通信的一些状态信息,从客户端接收到的数据,要发送给客户端的数据,服务器的ip地址与端口号,监听与数据发送的按钮。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

//交互界面
void OnGUI()
{
GUI.color = Color.black;

GUI.Label(new Rect(65, 10, 80, 20), "状态信息");

GUI.Label(new Rect(155, 10, 80, 70), info);

GUI.Label(new Rect(65, 80, 80, 20), "接收到消息:");

GUI.Label(new Rect(155, 80, 80, 20), recMes);

GUI.Label(new Rect(65, 120, 80, 20), "发送的消息:");

inputMessage = GUI.TextField(new Rect(155, 120, 100, 20), inputMessage, 20);

GUI.Label(new Rect(65, 160, 80, 20), "本机ip地址:");

inputIp = GUI.TextField(new Rect(155, 160, 100, 20), inputIp, 20);

GUI.Label(new Rect(65, 200, 80, 20), "本机端口号:");

inputPort = GUI.TextField(new Rect(155, 200, 100, 20), inputPort, 20);

if (GUI.Button(new Rect(65, 240, 60, 20), "开始监听"))
{
ClickConnect();
}

if (GUI.Button(new Rect(65, 280, 60, 20), "发送数据"))
{
isSendData = true;
}
}

客户端

在客户端实现的内容和服务器端类似,首先根据服务器端的ip地址和设置的端口号与服务器建立链接,并创建子线程进行数据的发送和接收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void ClickConnect()
{
try
{
int _port = Convert.ToInt32(inputPort); //获取端口号
string _ip = inputIp; //获取ip地址

//创建客户端Socket,获得远程ip和端口号
socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse(_ip);
IPEndPoint point = new IPEndPoint(ip, _port);

socketSend.Connect(point);
Debug.Log("连接成功 , " + " ip = " + ip + " port = " + _port);
staInfo = ip + ":" + _port + " 连接成功";

Thread r_thread = new Thread(Received); //开启新的线程,不停的接收服务器发来的消息
r_thread.IsBackground = true;
r_thread.Start();

Thread s_thread = new Thread(SendMessage); //开启新的线程,不停的给服务器发送消息
s_thread.IsBackground = true;
s_thread.Start();
}
catch (Exception)
{
Debug.Log("IP或者端口号错误......");
staInfo = "IP或者端口号错误......";
}
}

接收消息的子线程和服务器端类似,将接收到的字节数组转换为字符串并进行显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

void Received()
{
while (true)
{
try
{
byte[] buffer = new byte[1024 * 6];
//实际接收到的有效字节数
int len = socketSend.Receive(buffer);
if (len == 0)
{
break;
}

recMes = Encoding.UTF8.GetString(buffer, 0, len);

Debug.Log("客户端接收到的数据 : " + recMes);

recTimes ++;
staInfo = "接收到一次数据,接收次数为 :" + recTimes;
Debug.Log("接收次数为:" + recTimes);
}
catch { }
}
}

而发送消息的子线程通过点击客户端界面中的按钮进行触发,发送编辑好的信息至服务器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

void SendMessage()
{
try
{
while (true)
{
if (clickSend) //如果点击了发送按钮
{
clickSend = false;
string msg = inputMes;
byte[] buffer = new byte[1024 * 6];
buffer = Encoding.UTF8.GetBytes(msg);
socketSend.Send(buffer);
}
}
}
catch { }
}

最后同样也与服务器端类似,在程序关闭时,禁用Socket的数据发送和接收功能,并释放所有资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

private void OnDisable()
{
Debug.Log("begin OnDisable()");

if (socketSend.Connected)
{
try
{
socketSend.Shutdown(SocketShutdown.Both); //禁用Socket的发送和接收功能
socketSend.Close(); //关闭Socket连接并释放所有相关资源
}
catch (Exception e)
{
print(e.Message);
}
}

Debug.Log("end OnDisable()");
}

在客户端也创建了一个简易的交互界面,可以用来输入ip地址,建立通信链接按钮与发送数据按钮,显示接收到的数据和一些程序运行的状态信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

void OnGUI()
{
GUI.color = Color.black;

GUI.Label(new Rect(65, 10, 60, 20), "状态信息");

GUI.Label(new Rect(135, 10, 80, 60), staInfo);

GUI.Label(new Rect(65, 70, 50, 20), "服务器ip地址");

inputIp = GUI.TextField(new Rect(125, 70, 100, 20), inputIp, 20);

GUI.Label(new Rect(65, 110, 50, 20), "服务器端口");

inputPort = GUI.TextField(new Rect(125, 110, 100, 20), inputPort, 20);

GUI.Label(new Rect(65, 150, 80, 20), "接收到消息:");

GUI.Label(new Rect(155, 150, 80, 20), recMes);

GUI.Label(new Rect(65, 190, 80, 20), "发送的消息:");

inputMes = GUI.TextField(new Rect(155, 190, 100, 20), inputMes, 20);

if (GUI.Button(new Rect(65, 230, 60, 20), "开始连接"))
{
ClickConnect();
}

if (GUI.Button(new Rect(65, 270, 60, 20), "发送信息"))
{
clickSend = true;
}
}

需要注意一点的是在发送消息的输入框中键入中文时要将鼠标光标移出unity的场景,不然unity会自动切换到英文输入法,而无法成功输入中文,导致该问题的原因暂时还未找到。

完整代码

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using System.IO;

public class server : MonoBehaviour
{
private string info = "NULL"; //状态信息
private string recMes = "NULL"; //接收到的信息
private int recTimes = 0; //接收到的信息次数

private string inputIp = "10.175.229.183"; //ip地址
private string inputPort = "6000"; //端口值
private string inputMessage = "NULL"; //用以发送的信息

private Socket socketWatch; //用以监听的套接字
private Socket socketSend; //用以和客户端通信的套接字

private bool isSendData = false; //是否点击发送数据按钮
private bool clickConnectBtn = false; //是否点击监听按钮

void Start()
{

}

// Update is called once per frame
void Update()
{

}

//建立tcp通信链接
private void ClickConnect()
{
try
{
int _port = Convert.ToInt32(inputPort); //获取端口号
string _ip = inputIp; //获取ip地址

Debug.Log(" ip 地址是 :" + _ip);
Debug.Log(" 端口号是 :" + _port);

clickConnectBtn = true; //点击了监听按钮

info = "ip地址是 : " + _ip + "端口号是 : " + _port;

//点击开始监听时 在服务端创建一个负责监听IP和端口号的Socket
socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse(_ip);
IPEndPoint point = new IPEndPoint(ip, _port); //创建对象端口

socketWatch.Bind(point); //绑定端口号

Debug.Log("监听成功!");
info = "监听成功";

socketWatch.Listen(10); //设置监听,最大同时连接10台

//创建监听线程
Thread thread = new Thread(Listen);
thread.IsBackground = true;
thread.Start(socketWatch);
}
catch { }
}

/// <summary>
/// 等待客户端的连接 并且创建与之通信的Socket
/// </summary>
void Listen(object o)
{
try
{
Socket socketWatch = o as Socket;
while (true)
{
socketSend = socketWatch.Accept(); //等待接收客户端连接

Debug.Log(socketSend.RemoteEndPoint.ToString() + ":" + "连接成功!");
info = socketSend.RemoteEndPoint.ToString() + " 连接成功!";

Thread r_thread = new Thread(Received); //开启一个新线程,执行接收消息方法
r_thread.IsBackground = true;
r_thread.Start(socketSend);

Thread s_thread = new Thread(SendMessage); //开启一个新线程,执行发送消息方法
s_thread.IsBackground = true;
s_thread.Start(socketSend);
}
}
catch { }
}

/// <summary>
/// 服务器端不停的接收客户端发来的消息
/// </summary>
void Received(object o)
{
try
{
Socket socketSend = o as Socket;
while (true)
{
byte[] buffer = new byte[1024 * 6]; //客户端连接服务器成功后,服务器接收客户端发送的消息
int len = socketSend.Receive(buffer); //实际接收到的有效字节数
if (len == 0)
{
break;
}
string str = Encoding.UTF8.GetString(buffer, 0, len);

Debug.Log("接收到的消息:" + socketSend.RemoteEndPoint + ":" + str);
recMes = str;

recTimes ++;
info = "接收到一次数据,接收次数为:" + recTimes;
Debug.Log("接收数据次数:" + recTimes);
}
}
catch { }
}

/// <summary>
/// 服务器端不停的向客户端发送消息
/// </summary>
void SendMessage(object o)
{
try
{
Socket socketSend = o as Socket;
while (true)
{
if(isSendData)
{
isSendData = false;

byte[] sendByte = Encoding.UTF8.GetBytes(inputMessage);

Debug.Log("发送的数据为 :" + inputMessage);
Debug.Log("发送的数据字节长度 :" + sendByte.Length);

socketSend.Send(sendByte);
}
}
}
catch { }
}

private void OnDisable()
{
Debug.Log("begin OnDisable()");

if (clickConnectBtn)
{
try
{
socketWatch.Shutdown(SocketShutdown.Both); //禁用Socket的发送和接收功能
socketWatch.Close(); //关闭Socket连接并释放所有相关资源

socketSend.Shutdown(SocketShutdown.Both); //禁用Socket的发送和接收功能
socketSend.Close(); //关闭Socket连接并释放所有相关资源
}
catch (Exception e)
{
Debug.Log(e.Message);
}
}

Debug.Log("end OnDisable()");
}

//交互界面
void OnGUI()
{
GUI.color = Color.black;

GUI.Label(new Rect(65, 10, 80, 20), "状态信息");

GUI.Label(new Rect(155, 10, 80, 70), info);

GUI.Label(new Rect(65, 80, 80, 20), "接收到消息:");

GUI.Label(new Rect(155, 80, 80, 20), recMes);

GUI.Label(new Rect(65, 120, 80, 20), "发送的消息:");

inputMessage = GUI.TextField(new Rect(155, 120, 100, 20), inputMessage, 20);

GUI.Label(new Rect(65, 160, 80, 20), "本机ip地址:");

inputIp = GUI.TextField(new Rect(155, 160, 100, 20), inputIp, 20);

GUI.Label(new Rect(65, 200, 80, 20), "本机端口号:");

inputPort = GUI.TextField(new Rect(155, 200, 100, 20), inputPort, 20);

if (GUI.Button(new Rect(65, 240, 60, 20), "开始监听"))
{
ClickConnect();
}

if (GUI.Button(new Rect(65, 280, 60, 20), "发送数据"))
{
isSendData = true;
}
}
}


客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.IO;

using System.Threading;
using UnityEngine;
using UnityEngine.UI;

public class client : MonoBehaviour
{
private string staInfo = "NULL"; //状态信息
private string inputIp = "10.175.229.183"; //输入ip地址
private string inputPort = "6000"; //输入端口号
public string inputMes = "NULL"; //发送的消息
private int recTimes = 0; //接收到信息的次数
private string recMes = "NULL"; //接收到的消息
private Socket socketSend; //客户端套接字,用来链接远端服务器
private bool clickSend = false; //是否点击发送按钮

void Start()
{

}

// Update is called once per frame
void Update()
{

}

//建立链接
private void ClickConnect()
{
try
{
int _port = Convert.ToInt32(inputPort); //获取端口号
string _ip = inputIp; //获取ip地址

//创建客户端Socket,获得远程ip和端口号
socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse(_ip);
IPEndPoint point = new IPEndPoint(ip, _port);

socketSend.Connect(point);
Debug.Log("连接成功 , " + " ip = " + ip + " port = " + _port);
staInfo = ip + ":" + _port + " 连接成功";

Thread r_thread = new Thread(Received); //开启新的线程,不停的接收服务器发来的消息
r_thread.IsBackground = true;
r_thread.Start();

Thread s_thread = new Thread(SendMessage); //开启新的线程,不停的给服务器发送消息
s_thread.IsBackground = true;
s_thread.Start();
}
catch (Exception)
{
Debug.Log("IP或者端口号错误......");
staInfo = "IP或者端口号错误......";
}
}

/// <summary>
/// 接收服务端返回的消息
/// </summary>
void Received()
{
while (true)
{
try
{
byte[] buffer = new byte[1024 * 6];
//实际接收到的有效字节数
int len = socketSend.Receive(buffer);
if (len == 0)
{
break;
}

recMes = Encoding.UTF8.GetString(buffer, 0, len);

Debug.Log("客户端接收到的数据 : " + recMes);

recTimes ++;
staInfo = "接收到一次数据,接收次数为 :" + recTimes;
Debug.Log("接收次数为:" + recTimes);
}
catch { }
}
}

/// <summary>
/// 向服务器发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void SendMessage()
{
try
{
while (true)
{
if (clickSend) //如果点击了发送按钮
{
clickSend = false;
string msg = inputMes;
byte[] buffer = new byte[1024 * 6];
buffer = Encoding.UTF8.GetBytes(msg);
socketSend.Send(buffer);
Debug.Log("发送的数据为:" + msg);
}
}
}
catch { }
}


private void OnDisable()
{
Debug.Log("begin OnDisable()");

if (socketSend.Connected)
{
try
{
socketSend.Shutdown(SocketShutdown.Both); //禁用Socket的发送和接收功能
socketSend.Close(); //关闭Socket连接并释放所有相关资源
}
catch (Exception e)
{
print(e.Message);
}
}

Debug.Log("end OnDisable()");
}

//用户界面
void OnGUI()
{
GUI.color = Color.black;

GUI.Label(new Rect(65, 10, 60, 20), "状态信息");

GUI.Label(new Rect(135, 10, 80, 60), staInfo);

GUI.Label(new Rect(65, 70, 50, 20), "服务器ip地址");

inputIp = GUI.TextField(new Rect(125, 70, 100, 20), inputIp, 20);

GUI.Label(new Rect(65, 110, 50, 20), "服务器端口");

inputPort = GUI.TextField(new Rect(125, 110, 100, 20), inputPort, 20);

GUI.Label(new Rect(65, 150, 80, 20), "接收到消息:");

GUI.Label(new Rect(155, 150, 80, 20), recMes);

GUI.Label(new Rect(65, 190, 80, 20), "发送的消息:");

inputMes = GUI.TextField(new Rect(155, 190, 100, 20), inputMes, 20);

if (GUI.Button(new Rect(65, 230, 60, 20), "开始连接"))
{
ClickConnect();
}

if (GUI.Button(new Rect(65, 270, 60, 20), "发送信息"))
{
clickSend = true;
}
}
}

Unity中实现TCP通信
https://blog.966677.xyz/2025/03/02/Unity中实现TCP通信/
作者
Zhou1317fe5
发布于
2025年3月2日
许可协议