宝塔UDP服务器部署记录,unityClient,pythonServer
最近项目接到新需求,需要用Unity 客户端(发送端)控制另一台 Unity 客户端(接收端),中间用UDP服务器做数据中转。
先测试一下连通性,我用 Python 搞了个服务器 demo。
在正式开发之前,先做一个连通性测试,于是用python写UDP服务器demo,并部署到宝塔面板上。
代码
server.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import sys
import subprocess
import time
import threading
class UDPServer:
def __init__(self, host='0.0.0.0', port=7789):
self.host = host
self.port = port
print(f"Server initializing on {host}:{port}")
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.clients = {}
self.running = True
def start(self):
try:
self.sock.bind((self.host, self.port))
print("Server started successfully")
except Exception as e:
print(f"Server start failed: {e}")
return
cleanup_thread = threading.Thread(target=self.cleanup_inactive_clients)
cleanup_thread.daemon = True
cleanup_thread.start()
while self.running:
try:
data, addr = self.sock.recvfrom(1024)
message = data.decode('utf-8')
self.handle_message(message, addr)
except Exception as e:
print(f"Error: {e}")
def handle_message(self, data, addr):
try:
commands = data.split('|')
command_type = commands[0]
if command_type == "Heartbeat":
self.clients[addr] = time.time()
return
elif command_type == "Disconnect":
if addr in self.clients:
del self.clients[addr]
print(f"Client {addr} disconnected")
return
self.clients[addr] = time.time()
if command_type == "CustomText":
if len(commands) > 1:
content = commands[1]
self.broadcast_message(f"CustomText|{content}", exclude_addr=addr)
self.sock.sendto("Message broadcasted".encode(), addr)
except Exception as e:
print(f"Message handling error: {e}")
def cleanup_inactive_clients(self):
while self.running:
try:
current_time = time.time()
inactive_clients = [
addr for addr, last_time in self.clients.items()
if current_time - last_time > 30
]
for addr in inactive_clients:
del self.clients[addr]
print(f"Removed inactive client: {addr}")
time.sleep(10)
except Exception as e:
print(f"Cleanup error: {e}")
def broadcast_message(self, message, exclude_addr=None):
for client_addr in list(self.clients.keys()):
if client_addr != exclude_addr:
try:
self.sock.sendto(message.encode(), client_addr)
except Exception as e:
print(f"Broadcast error to {client_addr}: {e}")
del self.clients[client_addr]
def stop(self):
self.running = False
self.sock.close()
print("Server stopped")
if __name__ == "__main__":
server = UDPServer()
try:
server.start()
except KeyboardInterrupt:
server.stop()
unity发送端UDPController.cs
using UnityEngine;
using System;
using System.Text;
using System.Net.Sockets;
using System.Net;
using UnityEngine.UI;
public class UDPController : MonoBehaviour
{
public InputField inputField;
public Text messageLog;
public Button sendButton;
private UdpClient udp;
private string serverIP = "0.0.0.0"; // 服务器IP
private int serverPort = 7789; // 服务器端口
private string pendingMessage = null; // 待处理的消息
private float heartbeatInterval = 10f; // 心跳间隔
private float nextHeartbeatTime = 0f;
void Start()
{
InitializeUDP();
if (sendButton != null)
{
sendButton.onClick.AddListener(SendCustomText);
}
}
private void InitializeUDP()
{
try
{
udp = new UdpClient();
udp.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
udp.Client.Bind(new IPEndPoint(IPAddress.Any, 0));
udp.Connect(IPAddress.Parse(serverIP), serverPort);
LogMessage($"控制端初始化成功,本地端口: {((IPEndPoint)udp.Client.LocalEndPoint).Port}");
udp.BeginReceive(new AsyncCallback(OnReceived), udp);
}
catch (Exception e)
{
LogMessage($"初始化失败: {e.Message}");
}
}
public void SendCustomText()
{
if (string.IsNullOrEmpty(inputField.text)) return;
try
{
string message = $"CustomText|{inputField.text}";
byte[] sendBytes = Encoding.UTF8.GetBytes(message);
udp.Send(sendBytes, sendBytes.Length);
LogMessage($"已发送: {inputField.text}");
inputField.text = ""; // 清空输入框
}
catch (Exception e)
{
LogMessage($"发送失败: {e.Message}");
}
}
private void OnReceived(IAsyncResult result)
{
try
{
UdpClient socket = result.AsyncState as UdpClient;
IPEndPoint source = new IPEndPoint(IPAddress.Any, 0);
byte[] message = socket.EndReceive(result, ref source);
string receivedData = Encoding.UTF8.GetString(message);
pendingMessage = receivedData;
// 继续监听
socket.BeginReceive(new AsyncCallback(OnReceived), socket);
}
catch (Exception e)
{
Debug.LogError($"接收消息失败: {e.Message}");
}
}
private void Update()
{
if (pendingMessage != null)
{
LogMessage($"服务器响应: {pendingMessage}");
pendingMessage = null;
}
// 发送心跳
if (Time.time >= nextHeartbeatTime)
{
SendHeartbeat();
nextHeartbeatTime = Time.time + heartbeatInterval;
}
}
private void SendHeartbeat()
{
try
{
string message = "Heartbeat|";
byte[] sendBytes = Encoding.UTF8.GetBytes(message);
udp.Send(sendBytes, sendBytes.Length);
}
catch (Exception e)
{
Debug.LogError($"发送心跳失败: {e.Message}");
}
}
private void LogMessage(string message)
{
if (messageLog != null)
{
messageLog.text += $"\n{message}";
}
Debug.Log(message);
}
void OnDestroy()
{
if (udp != null)
{
try
{
string message = "Disconnect|";
byte[] sendBytes = Encoding.UTF8.GetBytes(message);
udp.Send(sendBytes, sendBytes.Length);
}
catch (Exception e)
{
Debug.LogError($"发送断开消息失败: {e.Message}");
}
finally
{
udp.Close();
udp.Dispose();
}
}
}
}
unity接收端UDPReceiver.cs
using UnityEngine;
using System;
using System.Text;
using System.Net.Sockets;
using System.Net;
using UnityEngine.UI;
using UnityEngine.Events;
[System.Serializable]
public class CustomTextEvent : UnityEvent<string> { }
public class UDPReceiver : MonoBehaviour
{
public Text messageLog;
public CustomTextEvent onCustomTextReceived; // Unity事件,用于处理接收到的文本
private UdpClient udp;
private string serverIP = "0.0.0.0"; // 服务器IP
private int serverPort = 7789; // 服务器端口
private string pendingMessage = null; // 待处理的消息
private string pendingContent = null; // 待处理的内容
private float heartbeatInterval = 10f;
private float nextHeartbeatTime = 0f;
void Start()
{
InitializeUDP();
if (onCustomTextReceived == null)
{
onCustomTextReceived = new CustomTextEvent();
}
}
private void InitializeUDP()
{
try
{
udp = new UdpClient();
udp.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
udp.Client.Bind(new IPEndPoint(IPAddress.Any, 0));
byte[] connectMessage = Encoding.UTF8.GetBytes("CustomText|Connected");
udp.Send(connectMessage, connectMessage.Length, serverIP, serverPort);
udp.BeginReceive(new AsyncCallback(OnReceived), udp);
LogMessage($"接收端初始化成功,本地端口: {((IPEndPoint)udp.Client.LocalEndPoint).Port}");
}
catch (Exception e)
{
LogMessage($"初始化失败: {e.Message}");
}
}
private void OnReceived(IAsyncResult result)
{
try
{
UdpClient socket = result.AsyncState as UdpClient;
IPEndPoint source = new IPEndPoint(IPAddress.Any, 0);
byte[] message = socket.EndReceive(result, ref source);
string receivedData = Encoding.UTF8.GetString(message);
pendingMessage = receivedData;
socket.BeginReceive(new AsyncCallback(OnReceived), socket);
}
catch (Exception e)
{
Debug.LogError($"接收消息失败: {e.Message}");
}
}
private void Update()
{
if (pendingMessage != null)
{
HandleMessage(pendingMessage);
pendingMessage = null;
}
if (pendingContent != null)
{
OnCustomTextReceived(pendingContent);
pendingContent = null;
}
// 发送心跳
if (Time.time >= nextHeartbeatTime)
{
SendHeartbeat();
nextHeartbeatTime = Time.time + heartbeatInterval;
}
}
private void HandleMessage(string data)
{
try
{
string[] parts = data.Split('|');
if (parts.Length < 2) return;
string messageType = parts[0];
string content = parts[1];
switch (messageType)
{
case "CustomText":
pendingContent = content;
break;
}
}
catch (Exception e)
{
LogMessage($"处理消息时出错: {e.Message}");
}
}
private void OnCustomTextReceived(string text)
{
LogMessage($"收到文本: {text}");
onCustomTextReceived?.Invoke(text); // 触发Unity事件
}
private void LogMessage(string message)
{
if (messageLog != null)
{
messageLog.text += $"\n{message}";
}
Debug.Log(message);
}
private void SendHeartbeat()
{
try
{
string message = "Heartbeat|";
byte[] sendBytes = Encoding.UTF8.GetBytes(message);
udp.Send(sendBytes, sendBytes.Length, serverIP, serverPort);
}
catch (Exception e)
{
Debug.LogError($"发送心跳失败: {e.Message}");
}
}
void OnDestroy()
{
if (udp != null)
{
try
{
string message = "Disconnect|";
byte[] sendBytes = Encoding.UTF8.GetBytes(message);
udp.Send(sendBytes, sendBytes.Length, serverIP, serverPort);
}
catch (Exception e)
{
Debug.LogError($"发送断开消息失败: {e.Message}");
}
finally
{
udp.Close();
udp.Dispose();
}
}
}
}
部署
宝塔面板–文件
创建项目文件夹
server.py上传(其他文件忽略,这是后来生成的)
宝塔面板–网站–python项目
需安装对应的python版本(3.10.14)
点击添加python项目。
项目路径选择刚才创建好的文件夹。
项目名称随意填。
运行文件选择目录内的server.py脚本
项目端口选择7789。(脚本里写的7789)
别忘了勾选放行端口!就是这个导致我后面调试了半小时…我怎么记得我勾选了呢?
python版本根据你开发的版本来。
框架选择python
由于这个demo不需要安装额外依赖,所以不填。
确认上述没问题后点击确定。
可以看到项目运行中
这时候还没结束,需要去开放安全组和对应的防火墙。
这台服务器是阿里云的,到ECS后台–安全组,添加入方向端口号7789,授权对象0.0.0.0
宝塔面板–安全这里也要检查下是否有7789的UDP协议的端口号!如果上面没勾选放行端口,这里就没有。
最后在本地客户端上进行验证,同时在服务器–设置–项目日志查看记录
调试命令
服务器上运行以下命令排查。
netstat -nulp | grep 7789
检查端口是否在监听
firewall-cmd --list-ports
检查服务器防火墙中的端口
用以下命令添加也可以,最好还是宝塔防火墙里直接开放也省事。
# 查看活动的防火墙区域
firewall-cmd --get-active-zones
# 为docker区域添加UDP端口
firewall-cmd --zone=docker --add-port=7789/udp --permanent
firewall-cmd --reload
# 验证端口是否添加成功
firewall-cmd --zone=docker --list-ports
写到最后
切记要检查ECS安全组
、宝塔防火墙
设置是否开放了端口号!(给自己提个醒)