自学内容网 自学内容网

宝塔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安全组宝塔防火墙设置是否开放了端口号!(给自己提个醒)


原文地址:https://blog.csdn.net/weixin_43935971/article/details/145278641

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!