宝塔UDP服务器部署记录,unityClient,pythonServer

server/2025/1/25 3:32:55/

在这里插入图片描述

最近项目接到新需求,需要用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 threadingclass UDPServer:def __init__(self, host='0.0.0.0', port=7789):self.host = hostself.port = portprint(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 = Truedef start(self):try:self.sock.bind((self.host, self.port))print("Server started successfully")except Exception as e:print(f"Server start failed: {e}")returncleanup_thread = threading.Thread(target=self.cleanup_inactive_clients)cleanup_thread.daemon = Truecleanup_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()returnelif command_type == "Disconnect":if addr in self.clients:del self.clients[addr]print(f"Client {addr} disconnected")returnself.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 = Falseself.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";  // 服务器IPprivate 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";  // 服务器IPprivate 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安全组宝塔防火墙设置是否开放了端口号!(给自己提个醒)


http://www.ppmy.cn/server/161201.html

相关文章

分布式光纤应变监测是一种高精度、分布式的监测技术

一、土木工程领域 桥梁结构健康监测 主跨应变监测&#xff1a;在大跨度桥梁的主跨部分&#xff0c;如悬索桥的主缆、斜拉桥的斜拉索和主梁&#xff0c;分布式光纤应变传感器可以沿着这些关键结构部件进行铺设。通过实时监测应变情况&#xff0c;能够精确捕捉到车辆荷载、风荷…

通过Python编程语言实现“机器学习”小项目教程案例

1. Python与机器学习概述 1.1 Python语言特点 Python是一种广泛使用的高级编程语言&#xff0c;具有简洁、易读、易学的特点&#xff0c;这使得它成为初学者和专业人士的首选语言之一。 简洁性&#xff1a;Python的语法简洁明了&#xff0c;减少了代码量&#xff0c;提高了开…

skynet 源码阅读 -- 核心概念服务 skynet_context

本文从 Skynet 源码层面深入解读 服务&#xff08;Service&#xff09; 的创建流程。从最基础的概念出发&#xff0c;逐步深入 skynet_context_new 函数、相关数据结构&#xff08;skynet_context, skynet_module, message_queue 等&#xff09;&#xff0c;并通过流程图、结构…

用Python绘制一只懒羊羊

目录 一、准备工作 二、Turtle库简介 三、绘制懒羊羊的步骤 1. 导入Turtle库并设置画布 2. 绘制头部 3. 绘制眼睛 4. 绘制嘴巴 5. 绘制身体 6. 绘制四肢 7. 完成绘制 五、运行代码与结果展示 六、总结 在这个趣味盎然的技术实践中,我们将使用Python和Turtle图形…

Android Studio安装配置

一、注意事项 想做安卓app和开发板通信&#xff0c;踩了大坑&#xff0c;Android 开发不是下载了就能直接开发的&#xff0c;对于新手需要注意的如下&#xff1a; 1、Android Studio版本&#xff0c;根据自己的Android Studio版本对应决定了你所兼容的AGP&#xff08;Android…

2024 开源社年度报告:拥抱开源新生活

2024 年&#xff0c;开源社步入了 10 周年。10 这个数字不论在十进制还是二进制中都代表着一次进位&#xff0c;而「进化」也成为了开源社 2024 的关键词。 在这一年&#xff0c;我们迭代了开源社的使命愿景 —— 10 年前我们写下开源社的愿景&#xff1a;立足中国、贡献全球&a…

【嵌入式】总结——Linux驱动开发(三)

鸽了半年&#xff0c;几乎全忘了&#xff0c;幸亏前面还有两篇总结。出于快速体验嵌入式linux的目的&#xff0c;本篇与前两篇一样&#xff0c;重点在于使用、快速体验&#xff0c;uboot、linux、根文件系统不作深入理解&#xff0c;能用就行。 重新梳理一下脉络&#xff0c;本…

营销2.0时代的挑战与开源AI智能名片2+1链动模式S2B2C商城小程序源码的解决方案

摘要&#xff1a;本文旨在探讨营销2.0时代企业在客户管理方面的挑战&#xff0c;并提出开源AI智能名片21链动模式S2B2C商城小程序源码作为解决方案。营销2.0虽然强调客户导向&#xff0c;但在实际操作中&#xff0c;企业往往无差别地对待所有客户&#xff0c;导致客户忠诚度下降…