基于Vuforia的AR红绿灯项目:客户端 TCP 通信实现

基于 Vuforia 的 AR 红绿灯项目:客户端 TCP 通信实现

项目简介

本项目是一个基于 Vuforia 的增强现实(AR)应用,旨在将服务端红绿灯的控制与显示进行 AR 化。通过集成 Vuforia SDK,用户可以将虚拟红绿灯叠加在现实世界的场景中。此外,本项目还实现了客户端与服务器之间的 TCP 双向通信,允许用户通过客户端控制真实或虚拟红绿灯的状态,并实现状态同步。

项目功能

  1. AR 红绿灯显示: 利用 Vuforia 的图像识别与追踪功能,在摄像头捕捉到的图像上叠加虚拟红绿灯模型。
  2. TCP 双向通信: 客户端(本项目)与服务器建立稳定的 TCP 连接,实现双向数据传输。
  3. 红绿灯控制: 客户端可以向服务器发送控制指令,改变红绿灯的状态(如切换红绿灯、调整倒计时)。
  4. 状态同步: 服务器接收到客户端的控制指令后,会更新红绿灯状态,并将更新后的状态信息发送回客户端,实现客户端与服务器之间的状态同步。
  5. 多设备支持: 支持不同设备作为客户端,获取本机IP并显示。

核心技术

  • Vuforia Engine: 用于图像识别、追踪和 AR 内容渲染。

  • C# 脚本编程: 使用 Unity 引擎和 C# 脚本实现项目逻辑。

  • TCP/IP 协议: 实现客户端与服务器之间的网络通信。

  • 多线程: 使用多线程处理网络连接、数据接收和发送,避免阻塞主线程。

视频演示

代码实现(客户端TcpServer.cs

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
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
   public string GetIOSIP()
{
string AddressIP = string.Empty;

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();
for (int i = 0; i < adapters.Length; i++)
{
if (adapters[i].Supports(NetworkInterfaceComponent.IPv4))
{
UnicastIPAddressInformationCollection uniCast = adapters[i].GetIPProperties().UnicastAddresses;
if (uniCast.Count > 0)
{
for (int j = 0; j < uniCast.Count; j++)
{
//得到IPv4的地址。 AddressFamily.InterNetwork指的是IPv4
if (uniCast[j].Address.AddressFamily == AddressFamily.InterNetwork)
{
AddressIP = uniCast[j].Address.ToString();
}
}
}
}
}
return AddressIP;
}


/// <summary>
/// 获取局域网的IP
/// </summary>
/// <returns></returns>
public string GetIOSIP2()
{
string AddressIP = string.Empty;
#if UNITY_IPHONE
NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces(); ;
foreach (NetworkInterface adapter in adapters)
{
if (adapter.Supports(NetworkInterfaceComponent.IPv4))
{
UnicastIPAddressInformationCollection uniCast = adapter.GetIPProperties().UnicastAddresses;
if (uniCast.Count > 0)
{
foreach (UnicastIPAddressInformation uni in uniCast)
{
//得到IPv4的地址。 AddressFamily.InterNetwork指的是IPv4
if (uni.Address.AddressFamily == AddressFamily.InterNetwork)
{
AddressIP = uni.Address.ToString();
}
}
}
}
}
#endif
#if UNITY_STANDALONE_WIN
///获取本地的IP地址
foreach (IPAddress _IPAddress in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
{
if (_IPAddress.AddressFamily.ToString() == "InterNetwork")
{
AddressIP = _IPAddress.ToString();
}
}
#endif
return AddressIP;
}


public string GetIOSIP3()
{
string ipAddress = "";
IPHostEntry host;
host = Dns.GetHostEntry(Dns.GetHostName());
foreach (IPAddress ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
ipAddress = ip.ToString();
break;
}
}
return ipAddress;
}



/// <summary>
/// 获取手机IP
/// </summary>
// IPV4
public static string GetAndIPv4()
{
string ipAddress = "";
try
{
IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
foreach (IPAddress ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
ipAddress = ip.ToString();
break;
}
}
}
catch (Exception e)
{
Debug.LogError("IP 获取失败");
}
return ipAddress;
}


/// <summary>
/// 获取电脑IP
/// </summary>

public static string GetPCIP()
{
try
{
string HostName = Dns.GetHostName(); //得到主机名
IPHostEntry IpEntry = Dns.GetHostEntry(HostName);
for (int i = 0; i < IpEntry.AddressList.Length; i++)
{
//从IP地址列表中筛选出IPv4类型的IP地址
//AddressFamily.InterNetwork表示此IP为IPv4,
//AddressFamily.InterNetworkV6表示此地址为IPv6类型
if (IpEntry.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
{
Debug.Log(IpEntry.AddressList[i].ToString());
return IpEntry.AddressList[i].ToString();
}
}
return "noIp";
}
catch
{
Debug.Log("ipGetFailed");
return ("ipGetFailed");
}
}

  • GetIOSIP3(): 获取本机IP地址。

  • Start(): 在脚本启动时,获取并显示设备的 IP 地址,然后调用 Connect() 函数。

TCP 连接

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
//建立tcp通信链接
private void Connect()
{
try
{
int _port = Convert.ToInt32(inputPort); //获取端口号
string _ip = inputIp; //获取ip地址

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

ifConnect = 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 { }
}

  • Connect(): 创建 Socket 对象,绑定 IP 地址和端口,开始监听客户端连接。

  • Listen(object o): 在新线程中循环监听客户端连接请求,接收到连接后创建新的 Socket 对象(socketSend)用于与该客户端通信,并启动接收和发送线程。

数据接收

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
/// <summary>
/// 服务器端不停的接收客户端发来的消息
/// </summary>

void Received(object o)
{
try
{
Socket socketSend = o as Socket;
while (true)
{
Thread.Sleep(1);//加延迟!!!#######
//byte[] messageBytes = new byte[1024 * 6]; //客户端连接服务器成功后,服务器接收客户端发送的消息
byte[] messageBytes = new byte[1024];
int len = socketSend.Receive(messageBytes); //实际接收到的有效字节数
if (len == 0)
{
break;
}

string str = Encoding.UTF8.GetString(messageBytes, 0, len);
Debug.Log("接收到的消息:" + socketSend.RemoteEndPoint + ":" + str);
recMes = str;
getNew = true;

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

}
}
catch { }
}
  • Received(object o): 在新线程中循环接收客户端发送的数据。接收到的数据存储在 messageBytes 字节数组中,然后使用 Encoding.UTF8.GetString() 方法将其转换为字符串。

  • Update(): 在主线程中,如果检测到 getNew 标志为 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
/// <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 { }
}

  • SendMessage(object o): 在新线程中循环检测 isSendData 标志,如果为 true,则将 inputMessage 字符串转换为字节数组,并发送给客户端。

资源释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    private void OnDisable()
{
Debug.Log("begin OnDisable()");

if (ifConnect)
{
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()");
}

}
  • OnDisable(): 当脚本被禁用或销毁时,关闭 Socket 连接并释放资源。

逻辑图

注意

本篇推文主要关注客户端 (TcpServer.cs) 的代码实现,由于该项目是服务端项目中的子部分,故服务端暂不开源。详情请查看本项目仓库Zhou1317fe5/Vuforia_ARTrafficLight


基于Vuforia的AR红绿灯项目:客户端 TCP 通信实现
https://blog.966677.xyz/2025/03/02/基于Vuforia的AR红绿灯项目/
作者
Zhou1317fe5
发布于
2025年3月2日
许可协议