news 2026/4/15 14:54:39

【实战】C/C++ 实现 PC 热点(手动开启)+ 手机 UDP 自动发现 + TCP 通信全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【实战】C/C++ 实现 PC 热点(手动开启)+ 手机 UDP 自动发现 + TCP 通信全流程

一、场景背景

在本地数据交互场景中,手动开启 PC 热点后,需实现手机连接热点后无需手动配置 IP,通过 UDP 广播自动发现 PC、建立 TCP 稳定通信。本文聚焦 “手动开热点” 场景,剥离热点创建代码,强化 UDP 自动发现与 TCP 通信逻辑,代码全程添加详细注释,便于理解和二次开发。

二、核心原理

  1. 手动热点基础:用户手动在 PC 端开启热点(Windows/macOS/Linux 均可),PC 作为热点网关会分配固定网段 IP(Windows 默认192.168.137.1);
  2. UDP 广播发现:手机连接热点后发送DISCOVER_PC广播包,PC 监听 UDP 端口并回复 TCP 服务端口,解决 “找 IP / 端口” 问题;
  3. TCP 可靠通信:手机解析 UDP 回复后建立 TCP 连接,配合心跳机制避免假连接,断开后自动重连;
  4. 全流程解耦:代码仅负责通信逻辑,热点由用户手动开启,降低代码复杂度和权限依赖。

三、环境准备

1. 硬件要求

  • PC:带无线网卡,手动开启热点(以 Windows 为例:设置→网络和 Internet→移动热点→开启);
  • 手机:Android/iOS(本文以 Android 为例),连接 PC 热点。

2. 开发环境

  • PC 端:Visual Studio 2019+(C/C++11)、Windows Socket 2.0;
  • 手机端:Android Studio(Kotlin)、Android 7.0+;
  • 依赖:PC 端链接ws2_32.lib,Android 端配置网络权限。

四、PC 端实现(C/C++)

核心说明

PC 端仅实现 “UDP 广播监听 + TCP 服务端”,热点由用户手动开启(Windows 手动开热点后,网关 IP 默认192.168.137.1,需确认自身热点 IP 并修改代码)。

cpp

运行

// 头文件包含:Windows Socket核心头文件+基础工具头文件 #include <windows.h> #include <winsock2.h> #include <ws2tcpip.h> #include <iostream> #include <thread> #include <chrono> #include <string> #include <cstring> // 链接Windows Socket库,必须添加,否则编译报错 #pragma comment(lib, "ws2_32.lib") /************************ 全局配置参数(可根据需求修改) ************************/ #define UDP_DISCOVER_PORT 9999 // UDP广播监听端口(需与手机端一致) #define TCP_SERVICE_PORT 8888 // TCP通信端口 #define PC_HOTSPOT_IP "192.168.137.1" // 手动开热点后PC的网关IP(关键!需确认自身热点IP) #define BUF_SIZE 1024 // 数据缓冲区大小 #define HEARTBEAT_TIMEOUT 15 // 心跳超时时间(秒),超过该时间未收到心跳则断开 /************************ 全局变量(通信状态管理) ************************/ SOCKET g_tcpClientSocket = INVALID_SOCKET; // 保存已连接的手机客户端Socket bool g_isClientConnected = false; // 标记是否有手机客户端连接 time_t g_lastHeartbeatTime; // 最后一次收到心跳的时间戳 /** * @brief 初始化Windows Socket环境 * @return 成功返回true,失败返回false * @note Windows Socket必须先初始化才能使用,版本选择2.2(兼容所有Windows系统) */ bool InitWinsock() { WSADATA wsaData; // 存储WSA初始化信息 // MAKEWORD(2,2)表示使用Socket 2.2版本 int ret = WSAStartup(MAKEWORD(2, 2), &wsaData); if (ret != 0) { std::cerr << "[错误] WSAStartup初始化失败,错误码:" << ret << std::endl; return false; } std::cout << "[信息] WSAStartup初始化成功" << std::endl; return true; } /** * @brief UDP广播监听线程函数 * @note 持续监听UDP广播端口,收到手机的DISCOVER_PC请求后,回复TCP服务端口 */ void UDPDiscoverThreadFunc() { // 1. 创建UDP套接字:AF_INET=IPv4,SOCK_DGRAM=UDP,IPPROTO_UDP=UDP协议 SOCKET udpSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (udpSocket == INVALID_SOCKET) { std::cerr << "[错误] 创建UDP套接字失败,错误码:" << WSAGetLastError() << std::endl; return; } // 2. 设置UDP套接字为广播模式(允许接收广播包) BOOL bBroadcast = TRUE; int optRet = setsockopt(udpSocket, SOL_SOCKET, SO_BROADCAST, (char*)&bBroadcast, sizeof(bBroadcast)); if (optRet == SOCKET_ERROR) { std::cerr << "[错误] 设置UDP广播模式失败,错误码:" << WSAGetLastError() << std::endl; closesocket(udpSocket); // 失败则关闭套接字 return; } // 3. 绑定UDP端口(监听所有网卡的UDP_DISCOVER_PORT端口) sockaddr_in udpServerAddr; // UDP服务端地址结构体 memset(&udpServerAddr, 0, sizeof(udpServerAddr)); // 初始化内存 udpServerAddr.sin_family = AF_INET; // IPv4协议 udpServerAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡(包括热点网卡) udpServerAddr.sin_port = htons(UDP_DISCOVER_PORT); // 端口转换为网络字节序(必须) if (bind(udpSocket, (sockaddr*)&udpServerAddr, sizeof(udpServerAddr)) == SOCKET_ERROR) { std::cerr << "[错误] UDP端口绑定失败,错误码:" << WSAGetLastError() << std::endl; closesocket(udpSocket); return; } std::cout << "[信息] UDP广播监听已启动,端口:" << UDP_DISCOVER_PORT << std::endl; // 4. 循环接收UDP广播包 char recvBuf[BUF_SIZE] = {0}; // 接收缓冲区 sockaddr_in clientAddr; // 发送广播的客户端(手机)地址 int clientAddrLen = sizeof(clientAddr); // 客户端地址长度 while (true) { // 无限循环监听,直到程序退出 // 接收UDP包:recvfrom用于UDP,可获取发送方地址 int recvLen = recvfrom( udpSocket, // 监听的UDP套接字 recvBuf, // 接收数据缓冲区 BUF_SIZE, // 缓冲区大小 0, // 标志位(0=默认) (sockaddr*)&clientAddr, // 输出:发送方地址 &clientAddrLen // 输入输出:地址长度 ); // 接收失败处理 if (recvLen <= 0) { std::cerr << "[错误] UDP接收数据失败,错误码:" << WSAGetLastError() << std::endl; memset(recvBuf, 0, BUF_SIZE); // 清空缓冲区 continue; // 继续监听下一个包 } // 解析接收的消息 std::string recvMsg(recvBuf, recvLen); std::cout << "[信息] 收到UDP广播(来自:" << inet_ntoa(clientAddr.sin_addr) << "):" << recvMsg << std::endl; // 5. 识别手机的发现请求,回复TCP端口 if (recvMsg == "DISCOVER_PC") { std::string tcpPortStr = std::to_string(TCP_SERVICE_PORT); // 转换为字符串 // 发送回复:sendto用于UDP,指定发送到手机的地址 int sendLen = sendto( udpSocket, tcpPortStr.c_str(), tcpPortStr.length(), 0, (sockaddr*)&clientAddr, clientAddrLen ); if (sendLen == SOCKET_ERROR) { std::cerr << "[错误] UDP回复TCP端口失败,错误码:" << WSAGetLastError() << std::endl; } else { std::cout << "[信息] 已回复手机TCP端口:" << TCP_SERVICE_PORT << std::endl; } } memset(recvBuf, 0, BUF_SIZE); // 清空缓冲区,准备下一次接收 } // 理论上不会执行到这里,除非循环退出 closesocket(udpSocket); } /** * @brief 心跳检测线程函数 * @note 每隔1秒检查一次心跳,超过HEARTBEAT_TIMEOUT未收到心跳则断开客户端 */ void HeartbeatCheckThreadFunc() { while (true) { // 仅当有客户端连接时才检查心跳 if (g_isClientConnected) { time_t currentTime = time(NULL); // 获取当前时间戳 // 计算与最后一次心跳的时间差 double timeDiff = difftime(currentTime, g_lastHeartbeatTime); if (timeDiff > HEARTBEAT_TIMEOUT) { std::cerr << "[警告] 客户端心跳超时(" << HEARTBEAT_TIMEOUT << "秒),断开连接" << std::endl; // 关闭客户端Socket closesocket(g_tcpClientSocket); g_tcpClientSocket = INVALID_SOCKET; g_isClientConnected = false; // 标记为未连接 } } std::this_thread::sleep_for(std::chrono::seconds(1)); // 每秒检查一次 } } /** * @brief 处理单个TCP客户端连接(手机) * @param clientSocket 已连接的客户端Socket * @note 负责接收手机消息、处理心跳、回复数据 */ void HandleTCPClientFunc(SOCKET clientSocket) { char recvBuf[BUF_SIZE] = {0}; // 接收缓冲区 // 更新心跳时间(连接成功即视为一次心跳) g_lastHeartbeatTime = time(NULL); g_isClientConnected = true; // 标记为已连接 // 获取客户端IP地址(调试用) sockaddr_in clientAddr; int clientAddrLen = sizeof(clientAddr); getpeername(clientSocket, (sockaddr*)&clientAddr, &clientAddrLen); std::cout << "[信息] 手机TCP连接成功(IP:" << inet_ntoa(clientAddr.sin_addr) << ")" << std::endl; // 循环接收客户端消息 while (g_isClientConnected) { // 接收TCP数据:recv用于TCP,无发送方地址(已建立连接) int recvLen = recv(clientSocket, recvBuf, BUF_SIZE, 0); // 接收失败/客户端断开 if (recvLen <= 0) { std::cerr << "[错误] TCP接收数据失败/客户端断开,错误码:" << WSAGetLastError() << std::endl; closesocket(clientSocket); // 关闭Socket g_tcpClientSocket = INVALID_SOCKET; // 重置全局Socket g_isClientConnected = false; // 标记为未连接 break; // 退出循环 } // 解析接收的消息 std::string recvMsg(recvBuf, recvLen); // 处理心跳包(手机发送的PING) if (recvMsg == "PING") { g_lastHeartbeatTime = time(NULL); // 更新心跳时间 // 回复PONG确认 send(clientSocket, "PONG", 4, 0); std::cout << "[信息] 收到心跳包(PING),回复PONG" << std::endl; } else { // 处理业务消息(非心跳) std::cout << "[信息] 收到手机消息:" << recvMsg << std::endl; // 回复客户端(示例:拼接确认消息) std::string replyMsg = "PC已接收:" + recvMsg; send(clientSocket, replyMsg.c_str(), replyMsg.length(), 0); } memset(recvBuf, 0, BUF_SIZE); // 清空缓冲区 } } /** * @brief 启动TCP服务端 * @note 监听PC热点IP的TCP_SERVICE_PORT端口,接收手机连接 */ void StartTCPServer() { // 1. 创建TCP套接字:AF_INET=IPv4,SOCK_STREAM=TCP,IPPROTO_TCP=TCP协议 SOCKET tcpListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (tcpListenSocket == INVALID_SOCKET) { std::cerr << "[错误] 创建TCP监听套接字失败,错误码:" << WSAGetLastError() << std::endl; return; } // 2. 绑定TCP端口到PC热点IP sockaddr_in tcpServerAddr; memset(&tcpServerAddr, 0, sizeof(tcpServerAddr)); tcpServerAddr.sin_family = AF_INET; // 转换热点IP为网络字节序(必须) inet_pton(AF_INET, PC_HOTSPOT_IP, &tcpServerAddr.sin_addr); tcpServerAddr.sin_port = htons(TCP_SERVICE_PORT); // 端口转换为网络字节序 if (bind(tcpListenSocket, (sockaddr*)&tcpServerAddr, sizeof(tcpServerAddr)) == SOCKET_ERROR) { std::cerr << "[错误] TCP端口绑定失败,错误码:" << WSAGetLastError() << std::endl; closesocket(tcpListenSocket); return; } // 3. 开始监听(SOMAXCONN=系统最大连接数) if (listen(tcpListenSocket, SOMAXCONN) == SOCKET_ERROR) { std::cerr << "[错误] TCP监听失败,错误码:" << WSAGetLastError() << std::endl; closesocket(tcpListenSocket); return; } std::cout << "[信息] TCP服务端已启动,监听地址:" << PC_HOTSPOT_IP << ":" << TCP_SERVICE_PORT << std::endl; // 4. 启动心跳检测线程(分离线程,后台运行) std::thread heartbeatThread(HeartbeatCheckThreadFunc); heartbeatThread.detach(); // 5. 循环接收客户端连接 while (true) { // accept阻塞等待客户端连接,成功返回新的Socket(用于与该客户端通信) SOCKET newClientSocket = accept(tcpListenSocket, NULL, NULL); if (newClientSocket == INVALID_SOCKET) { std::cerr << "[错误] 接受TCP连接失败,错误码:" << WSAGetLastError() << std::endl; continue; } // 如果已有客户端连接,关闭旧连接(单客户端模式,可修改为多客户端) if (g_isClientConnected) { std::cerr << "[警告] 已有客户端连接,断开旧连接" << std::endl; closesocket(g_tcpClientSocket); } // 保存新客户端Socket,并启动线程处理通信 g_tcpClientSocket = newClientSocket; std::thread clientThread(HandleTCPClientFunc, newClientSocket); clientThread.detach(); // 分离线程,避免主线程阻塞 } // 理论上不会执行到这里 closesocket(tcpListenSocket); } /** * @brief 主函数(程序入口) * @return 0=成功,-1=失败 * @note 流程:初始化Socket→启动UDP监听线程→启动TCP服务端 */ int main() { std::cout << "[信息] 程序启动,等待手动开启PC热点..." << std::endl; std::cout << "[提示] 请先手动开启PC热点,确认热点IP为:" << PC_HOTSPOT_IP << std::endl; std::cout << "[提示] 按任意键继续..." << std::endl; system("pause"); // 暂停,等待用户手动开启热点 // 1. 初始化Windows Socket if (!InitWinsock()) { std::cerr << "[错误] Socket初始化失败,程序退出" << std::endl; system("pause"); return -1; } // 2. 启动UDP广播监听线程(后台运行) std::thread udpDiscoverThread(UDPDiscoverThreadFunc); udpDiscoverThread.detach(); // 3. 启动TCP服务端(阻塞运行) StartTCPServer(); // 4. 清理Socket环境(理论上不会执行到这里) WSACleanup(); std::cout << "[信息] 程序退出" << std::endl; system("pause"); return 0; }

五、手机端实现(Android/Kotlin)

1. 权限配置(AndroidManifest.xml)

xml

<!-- 核心网络权限:允许应用访问网络 --> <uses-permission android:name="android.permission.INTERNET" /> <!-- 获取WiFi状态:确认是否连接到PC热点 --> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- 获取网络状态:判断网络是否可用 --> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 允许接收多播/广播包:UDP广播必需 --> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />

2. 核心代码(带详细注释)

kotlin

import android.os.Handler import android.os.Looper import java.net.* import java.io.OutputStreamWriter import java.io.BufferedReader import java.io.InputStreamReader import java.util.concurrent.Executors /** * PC热点通信客户端 * 核心流程:UDP广播发现PC → TCP建立连接 → 心跳保活 → 消息收发 → 断开自动重连 */ class PCHotspotClient { // 线程池:用于处理网络操作(避免阻塞UI线程) private val executor = Executors.newSingleThreadExecutor() // 主线程Handler:用于更新UI(Android网络操作不能在主线程,更新UI必须在主线程) private val mainHandler = Handler(Looper.getMainLooper()) /************************ 配置参数(与PC端保持一致) ************************/ // UDP广播端口(需与PC端UDP_DISCOVER_PORT一致) private val UDP_DISCOVER_PORT = 9999 // PC热点网关IP(手动开热点后确认的IP,如Windows默认192.168.137.1) private val PC_HOTSPOT_IP = "192.168.137.1" // TCP默认端口(UDP发现失败时兜底) private val DEFAULT_TCP_PORT = 8888 // 心跳间隔(秒) private val HEARTBEAT_INTERVAL = 5 * 1000L /************************ 通信状态变量 ************************/ // TCP Socket(与PC的连接) private var tcpSocket: Socket? = null // 输出流:向PC发送数据 private var outputWriter: OutputStreamWriter? = null // 标记是否正在重连(避免重复重连) private var isReconnecting = false /** * 启动流程:先UDP发现PC,再建立TCP连接 */ fun start() { executor.execute { discoverPCByUDP() } } /** * 步骤1:UDP广播发现PC * 逻辑:发送DISCOVER_PC广播 → 接收PC回复的TCP端口 → 调用TCP连接函数 */ private fun discoverPCByUDP() { var tcpPort = -1 // 存储PC回复的TCP端口 var udpSocket: DatagramSocket? = null try { // 1. 创建UDP Socket并设置为广播模式 udpSocket = DatagramSocket() udpSocket.broadcast = true // 允许发送广播包 udpSocket.soTimeout = 5000 // 设置超时:5秒未收到回复则视为失败 // 2. 构造广播消息:DISCOVER_PC(与PC端识别的关键词一致) val discoverMsg = "DISCOVER_PC".toByteArray() // 广播地址:255.255.255.255(局域网内所有设备都能收到) val broadcastAddress = InetAddress.getByName("255.255.255.255") // 构造UDP数据包:消息+广播地址+端口 val sendPacket = DatagramPacket( discoverMsg, discoverMsg.size, broadcastAddress, UDP_DISCOVER_PORT ) // 3. 发送广播包 udpSocket.send(sendPacket) log("UDP广播已发送:DISCOVER_PC") // 4. 接收PC的回复(TCP端口) val recvBuffer = ByteArray(1024) val recvPacket = DatagramPacket(recvBuffer, recvBuffer.size) udpSocket.receive(recvPacket) // 阻塞等待回复 // 解析回复的TCP端口 tcpPort = String(recvPacket.data, 0, recvPacket.length).toInt() log("UDP发现成功,PC的TCP端口:$tcpPort") } catch (e: SocketTimeoutException) { // 超时异常:使用默认端口 log("UDP发现超时,使用默认TCP端口:$DEFAULT_TCP_PORT") tcpPort = DEFAULT_TCP_PORT } catch (e: Exception) { // 其他异常:使用默认端口 log("UDP发现失败:${e.message},使用默认TCP端口:$DEFAULT_TCP_PORT") tcpPort = DEFAULT_TCP_PORT } finally { // 关闭UDP Socket(无论成功失败都要关闭) udpSocket?.close() } // 5. 调用TCP连接函数 if (tcpPort > 0) { connectTCPServer(tcpPort) } } /** * 步骤2:TCP连接PC服务端 * @param tcpPort PC的TCP服务端口 * 逻辑:尝试连接 → 成功则启动心跳和消息监听 → 失败则3秒后重连 */ private fun connectTCPServer(tcpPort: Int) { // 标记为正在重连(避免重复调用) isReconnecting = true while (isReconnecting) { try { // 1. 创建TCP Socket并连接PC tcpSocket = Socket(PC_HOTSPOT_IP, tcpPort) // 2. 获取输出流(用于发送数据) outputWriter = OutputStreamWriter(tcpSocket!!.getOutputStream()) // 3. 连接成功:更新状态+日志 log("TCP连接成功:$PC_HOTSPOT_IP:$tcpPort") isReconnecting = false // 停止重连循环 // 4. 启动心跳保活和消息监听 startHeartbeat() listenTCPPacket() } catch (e: Exception) { // 连接失败:日志+3秒后重试 log("TCP连接失败:${e.message},3秒后重试...") Thread.sleep(3000) // 休眠3秒 } } } /** * 步骤3:心跳保活 * 逻辑:每5秒发送一次PING → 失败则触发重连 */ private fun startHeartbeat() { executor.execute { while (tcpSocket?.isConnected == true && !isReconnecting) { try { // 发送心跳包:PING(与PC端识别的心跳关键词一致) outputWriter?.write("PING\n") outputWriter?.flush() // 强制刷新(确保数据发送) log("发送心跳包:PING") // 休眠5秒 Thread.sleep(HEARTBEAT_INTERVAL) } catch (e: Exception) { // 心跳发送失败:断开连接+触发重连 log("心跳发送失败:${e.message}") break } } // 心跳循环退出:触发重连 reconnect() } } /** * 步骤4:监听PC发送的TCP消息 * 逻辑:循环读取PC消息 → 心跳包(PONG)不处理 → 业务消息回调 */ private fun listenTCPPacket() { executor.execute { var reader: BufferedReader? = null try { // 1. 获取输入流(用于接收PC数据) reader = BufferedReader(InputStreamReader(tcpSocket!!.getInputStream())) var recvLine: String? // 2. 循环读取消息(阻塞直到收到数据/断开) while (tcpSocket?.isConnected == true && !isReconnecting && reader.readLine().also { recvLine = it } != null) { val finalLine = recvLine ?: continue // 主线程更新日志/UI mainHandler.post { // 区分心跳回复和业务消息 if (finalLine == "PONG") { log("收到心跳回复:PONG") } else { log("收到PC消息:$finalLine") // 此处可添加业务消息处理逻辑(如更新UI、解析数据等) } } } } catch (e: Exception) { log("消息监听失败:${e.message}") } finally { // 关闭输入流 reader?.close() // 触发重连 reconnect() } } } /** * 发送消息到PC * @param msg 要发送的字符串消息 * 注意:需在非UI线程执行,避免阻塞 */ fun sendMsgToPC(msg: String) { executor.execute { try { // 发送消息(末尾加换行符,避免粘包) outputWriter?.write("$msg\n") outputWriter?.flush() log("发送消息到PC:$msg") } catch (e: Exception) { log("发送消息失败:${e.message}") // 发送失败触发重连 reconnect() } } } /** * 断开连接+重连 * 逻辑:清理资源 → 重新执行UDP发现+TCP连接 */ private fun reconnect() { // 避免重复重连 if (isReconnecting) return // 1. 清理资源 try { outputWriter?.close() tcpSocket?.close() log("已清理TCP连接资源") } catch (e: Exception) { log("清理资源失败:${e.message}") } // 2. 重新启动发现流程 log("开始重新连接PC...") discoverPCByUDP() } /** * 停止所有通信 * 调用场景:APP退出、页面销毁 */ fun stop() { // 标记为停止重连 isReconnecting = false // 清理资源 executor.execute { try { outputWriter?.close() tcpSocket?.close() log("已停止所有通信") } catch (e: Exception) { log("停止通信失败:${e.message}") } } } /** * 日志输出(主线程) * @param msg 日志内容 */ private fun log(msg: String) { mainHandler.post { println("[PCHotspotClient] $msg") // 此处可替换为APP的日志框架(如Logcat) // Log.d("PCHotspotClient", msg) } } }

3. Activity 中调用示例(带注释)

kotlin

import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.Button import android.widget.EditText class MainActivity : AppCompatActivity() { // 声明PC热点客户端 private lateinit var pcClient: PCHotspotClient override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 1. 初始化客户端 pcClient = PCHotspotClient() // 2. 启动通信流程(UDP发现→TCP连接) pcClient.start() // 3. 发送消息按钮点击事件 val btnSend: Button = findViewById(R.id.btn_send) val etMsg: EditText = findViewById(R.id.et_msg) btnSend.setOnClickListener { val msg = etMsg.text.toString().trim() if (msg.isNotEmpty()) { pcClient.sendMsgToPC(msg) etMsg.setText("") // 清空输入框 } } } /** * 页面销毁时停止通信,避免内存泄漏 */ override fun onDestroy() { super.onDestroy() pcClient.stop() } }

六、部署与测试步骤

1. PC 端操作

  1. 手动开启热点:Windows 系统 → 设置 → 网络和 Internet → 移动热点 → 开启(记录热点 IP,默认192.168.137.1);
  2. 编译运行代码
    • 打开 Visual Studio,创建项目;
    • 修改PC_HOTSPOT_IP为实际热点 IP;
    • 项目属性→链接器→输入→附加依赖项添加ws2_32.lib
    • 管理员身份运行程序(避免端口绑定失败);
  3. 确认启动成功:控制台输出 “UDP 广播监听已启动”“TCP 服务端已启动”。

2. 手机端操作

  1. 连接 PC 热点:手机 WiFi 连接手动开启的 PC 热点(输入热点密码);
  2. 编译安装 APK:Android Studio 编译代码,安装到手机;
  3. 测试通信
    • 打开 APP,自动触发 UDP 发现→TCP 连接;
    • 输入消息点击发送,PC 控制台可看到消息;
    • 断开 WiFi 重新连接,APP 自动重连,验证稳定性。

七、常见问题与解决方案

问题现象可能原因解决方案
PC 端 UDP 绑定失败端口被占用 / 无管理员权限1. 更换 UDP 端口;2. 以管理员身份运行程序
手机 UDP 发现失败PC 防火墙拦截 / 未连热点1. Windows 防火墙放行9999/UDP8888/TCP;2. 确认手机已连接 PC 热点
TCP 连接超时PC 热点 IP 配置错误1.cmd执行ipconfig查看热点 IP;2. 修改代码中PC_HOTSPOT_IP为实际 IP
心跳超时频繁网络波动 / 间隔过短1. 调整HEARTBEAT_TIMEOUT为 20 秒;2. 靠近 PC 减少信号干扰
手机端发送消息失败TCP 连接已断开代码已内置自动重连,等待 3 秒后重试

八、核心优化点(可选)

  1. 多客户端支持:PC 端将单客户端改为多客户端(用数组 / 链表存储 Socket,IOCP 模型优化);
  2. 数据粘包处理:消息末尾加换行符(本文已实现),或添加消息头(长度 + 类型);
  3. 加密通信:TCP 消息添加 AES 加密,防止局域网内数据被监听;
  4. 热点 IP 自动识别:PC 端添加代码自动获取热点 IP(替代手动配置);
  5. UI 交互优化:手机端添加连接状态、消息列表展示,提升用户体验。

九、总结

本文聚焦 “手动开启 PC 热点” 场景,通过 UDP 广播实现设备自动发现,TCP 实现可靠通信,代码全程添加详细注释,兼顾可读性和实用性。核心优势:

  1. 无需手动配置 IP,手机连接热点即自动发现 PC;
  2. 心跳保活 + 自动重连,保障通信稳定性;
  3. 代码解耦,仅负责通信逻辑,热点由用户手动管理,降低权限依赖。

该方案可直接应用于本地数据交互、工业控制、移动调试等场景,也可基于此扩展外网通信(如 FRP 内网穿透)。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/14 10:30:33

R与Python数据库连接实战(高频问题+5种解决方案全收录)

第一章&#xff1a;R与Python数据库交互概述在数据科学和分析领域&#xff0c;R 与 Python 是两种广泛使用的编程语言。它们各自拥有强大的生态系统&#xff0c;支持从数据清洗、建模到可视化的完整流程。随着项目复杂度的提升&#xff0c;直接操作数据库成为常态&#xff0c;因…

作者头像 李华
网站建设 2026/4/12 8:59:18

Worker 线程中的 函数序列化 模式

工作者线程也可以利用函数序列化来初始化行内脚本。因为函数的toString()方法会返回函数代码的字符串&#xff0c;而函数可以在父上下文中定义但在子上下文中执行。什么是函数序列化&#xff1f;函数序列化是将函数&#xff08;包括其代码、闭包环境等信息&#xff09;转换为可…

作者头像 李华
网站建设 2026/4/12 6:03:52

Dify与Spring AI模型通信失败?90%的人都忽略了这4个配置细节

第一章&#xff1a;Dify 与 Spring AI 模型对接概述在现代企业级应用开发中&#xff0c;将 AI 能力集成到后端服务已成为提升智能化水平的关键路径。Dify 作为一款支持可视化编排和模型管理的 AI 应用开发平台&#xff0c;提供了标准化的 API 接口&#xff0c;便于与基于 Sprin…

作者头像 李华
网站建设 2026/4/11 17:45:19

Git下载TensorRT官方Demo并修改适配自定义模型

Git下载TensorRT官方Demo并修改适配自定义模型 在AI模型从实验室走向生产线的过程中&#xff0c;一个常见的困境是&#xff1a;训练时精度高达95%的图像分类模型&#xff0c;部署后推理速度却只有每秒5帧&#xff0c;根本无法满足实时视频流处理的需求。这正是许多工程师在边缘…

作者头像 李华
网站建设 2026/4/10 1:59:19

堆与优先级队列:算法高效利器

堆(heap)实际就是完全二叉树&#xff0c;但他的结点的值有两种趋势&#xff0c;一是从根节点的值到叶子节点的值从小到大称为小根堆&#xff0c;从根节点的值从大到小称为大根堆&#xff0c;否则不是堆。当堆中插入数据或删除数据时&#xff0c;有向上调整算法和向下调整算法。…

作者头像 李华