【实战】C/C++ 实现 PC 热点(手动开启)+ 手机 UDP 自动发现 + TCP 通信全流程
摘要
在物联网和移动应用开发中,本地设备间的无线通信是一个常见需求。本文详细介绍了一种基于PC热点的手动开启方案,结合UDP广播自动发现和TCP可靠通信的技术实现。通过解耦热点创建和通信逻辑,我们降低了代码复杂度和权限依赖,实现了手机连接PC热点后无需手动配置IP即可自动建立稳定通信的完整解决方案。文章包含详细的原理分析、完整的C/C++和Android/Kotlin代码实现,以及实际的部署测试步骤。
一、场景背景
1.1 应用场景
在多种实际场景中,我们需要实现PC与手机之间的本地数据交互:
- 文件传输:快速将手机照片、视频传输到PC,或从PC下载文件到手机
- 远程控制:手机作为遥控器控制PC上的媒体播放、PPT演示等
- 数据同步:游戏存档、笔记、联系人等数据的双向同步
- 调试工具:移动开发者通过手机远程调试PC上的服务
- 物联网控制:手机通过PC网关控制其他物联网设备
1.2 传统方案的局限性
传统的PC-手机通信方案通常存在以下问题:
- IP配置复杂:需要手动查看和输入IP地址,用户体验差
- 网络环境依赖:需要路由器或稳定的WiFi环境
- 权限限制:自动创建热点通常需要管理员权限
- 跨平台兼容性:不同操作系统热点API差异大
1.3 本文方案的优势
本文提出的方案具有以下特点:
- 免配置发现:手机连接热点后自动发现PC,无需手动输入IP
- 环境独立:只需PC和手机,无需路由器等额外设备
- 权限友好:热点由用户手动开启,程序无需特殊权限
- 跨平台:核心通信逻辑与操作系统解耦
- 稳定可靠:UDP发现+TCP通信+心跳保活三重保障
二、核心原理
2.1 系统架构设计
┌─────────────────┐ UDP广播发现 ┌─────────────────┐
│ 手机端 │◄─────────────────►│ PC端 │
│ │ TCP通信连接 │ │
│ Android/iOS │◄─────────────────►│ Windows/macOS │
└─────────────────┘ └─────────────────┘
│ │
│ 连接PC热点 │ 手动开启热点
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ 手机无线网卡 │ │ PC无线网卡 │
└─────────────────┘ └─────────────────┘
2.2 UDP广播发现机制
2.2.1 广播原理
UDP广播允许设备向同一局域网内的所有设备发送数据包,而不需要知道具体的目标IP地址。广播地址255.255.255.255表示局域网内的所有设备。
工作流程:
- 手机连接PC热点后,获得一个私有IP地址(如192.168.137.100)
- 手机向广播地址
255.255.255.255:9999发送DISCOVER_PC消息 - PC监听UDP端口9999,收到广播后获取手机的IP地址
- PC回复TCP服务端口号(8888)到手机的具体IP地址
- 手机收到回复后,使用该端口建立TCP连接
2.2.2 广播包格式设计
cpp
// 发现请求包
struct DiscoverRequest {
char magic[4]; // 魔术字:"DISC"
uint16_t version; // 协议版本:1
char device_type; // 设备类型:'P'=手机, 'C'=PC
// 可扩展:设备ID、能力字段等
};
// 发现响应包
struct DiscoverResponse {
char magic[4]; // 魔术字:"RESP"
uint16_t tcp_port; // TCP服务端口
uint32_t ip_addr; // PC的IP地址(网络字节序)
// 可扩展:服务类型、加密信息等
};
2.3 TCP通信机制
2.3.1 连接建立与维护
TCP提供可靠的、面向连接的通信服务。在我们的方案中:
- 三次握手:手机主动发起TCP连接,PC接受连接
- 流量控制:通过滑动窗口机制避免接收方缓冲区溢出
- 拥塞控制:TCP内置的拥塞避免算法保证网络稳定
2.3.2 心跳保活机制
为防止连接假死,我们实现了应用层心跳机制:
cpp
// 心跳协议设计
手机端:每隔5秒发送"PING" → PC端:收到"PING"立即回复"PONG"
超时机制:PC端15秒未收到心跳则断开连接
2.4 网络地址转换(NAT)考虑
由于PC作为热点网关,通常具有NAT功能。在我们的方案中:
- PC端:使用热点网关IP(如192.168.137.1)
- 手机端:获得私有IP(如192.168.137.x)
- 通信路径:手机 ↔ PC(网关) ↔ PC应用
三、环境准备
3.1 硬件环境配置
3.1.1 PC端要求
| 项目 | 最低要求 | 推荐配置 |
|---|---|---|
| 操作系统 | Windows 8.1 / macOS 10.13 / Ubuntu 18.04 | Windows 10 / macOS 11 / Ubuntu 20.04 |
| 无线网卡 | 支持AP模式的802.11n网卡 | 支持802.11ac的5GHz网卡 |
| 处理器 | 双核1.6GHz | 四核2.0GHz或更高 |
| 内存 | 4GB | 8GB或更高 |
3.1.2 手机端要求
| 项目 | 最低要求 | 推荐配置 |
|---|---|---|
| 操作系统 | Android 7.0 / iOS 11 | Android 10 / iOS 14 |
| Wi-Fi | 支持802.11n | 支持802.11ac |
| 存储空间 | 50MB可用空间 | 100MB或更多 |
3.2 开发环境搭建
3.2.1 PC端开发环境
Windows平台(本文主要示例):
- 安装Visual Studio 2019或更高版本
- 安装C++桌面开发工作负载
- 配置Windows SDK(版本10.0.17763.0或更高)
macOS平台:
bash
# 安装Xcode和命令行工具
xcode-select --install
# 安装Homebrew(如果未安装)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 安装编译工具链
brew install cmake pkg-config
Linux平台(Ubuntu):
bash
# 安装编译工具和库
sudo apt-get update
sudo apt-get install build-essential cmake pkg-config
sudo apt-get install libssl-dev zlib1g-dev
3.2.2 手机端开发环境
Android开发环境:
- 安装Android Studio 4.0或更高版本
- 配置JDK 11或更高版本
- 安装Android SDK Platform 30或更高
- 安装Android NDK(用于JNI开发,可选)
iOS开发环境:
- 安装Xcode 12或更高版本
- 配置Swift或Objective-C开发环境
3.3 依赖库配置
3.3.1 PC端依赖
Windows Socket 2.0:
- 系统自带,通过
#pragma comment(lib, "ws2_32.lib")链接 - 支持IPv4和IPv6(本文使用IPv4简化实现)
可选增强库:
cpp
// OpenSSL - 加密通信
#pragma comment(lib, "libssl.lib")
#pragma comment(lib, "libcrypto.lib")
// Google Protocol Buffers - 高效序列化
#pragma comment(lib, "libprotobuf.lib")
// Boost.Asio - 异步网络编程
#pragma comment(lib, "boost_system.lib")
#pragma comment(lib, "boost_thread.lib")
3.3.2 Android端依赖
Gradle配置:
gradle
dependencies {
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
// 网络请求库(可选)
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
// 协程支持
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
// 序列化库(可选)
implementation 'com.google.code.gson:gson:2.8.8'
}
四、PC端实现(C/C++)
4.1 项目结构设计
PC_Server_Project/
├── src/
│ ├── main.cpp # 程序入口
│ ├── network/
│ │ ├── udp_discover.cpp # UDP发现服务
│ │ ├── tcp_server.cpp # TCP服务器
│ │ └── heartbeat.cpp # 心跳检测
│ ├── utils/
│ │ ├── logger.cpp # 日志系统
│ │ └── config.cpp # 配置管理
│ └── protocol/
│ ├── message.cpp # 消息协议
│ └── parser.cpp # 协议解析
├── include/ # 头文件
├── third_party/ # 第三方库
└── CMakeLists.txt # 构建配置
4.2 详细代码实现与解析
4.2.1 主程序入口
cpp
// main.cpp - 程序主入口和整体控制流程
#include <iostream>
#include <thread>
#include <atomic>
#include <csignal>
#include "network/udp_discover.h"
#include "network/tcp_server.h"
#include "utils/logger.h"
#include "utils/config.h"
// 全局信号标志,用于优雅退出
std::atomic<bool> g_running{true};
// 信号处理函数
void signal_handler(int signal) {
Logger::getInstance().log(LogLevel::INFO, "收到退出信号,正在关闭服务...");
g_running = false;
}
int main(int argc, char* argv[]) {
// 初始化日志系统
Logger::getInstance().init("pc_server.log", LogLevel::DEBUG);
// 注册信号处理器
std::signal(SIGINT, signal_handler); // Ctrl+C
std::signal(SIGTERM, signal_handler); // 终止信号
Logger::getInstance().log(LogLevel::INFO,
"========================================");
Logger::getInstance().log(LogLevel::INFO,
"PC热点通信服务器 v1.0 启动");
Logger::getInstance().log(LogLevel::INFO,
"========================================");
// 1. 加载配置文件
ConfigManager& config = ConfigManager::getInstance();
if (!config.load("config.ini")) {
Logger::getInstance().log(LogLevel::ERROR,
"配置文件加载失败,使用默认配置");
config.setDefault();
}
// 2. 显示配置信息
std::cout << "\n当前配置信息:" << std::endl;
std::cout << "热点IP: " << config.getHotspotIP() << std::endl;
std::cout << "UDP发现端口: " << config.getUDPDiscoverPort() << std::endl;
std::cout << "TCP服务端口: " << config.getTCPServicePort() << std::endl;
std::cout << "心跳超时: " << config.getHeartbeatTimeout() << "秒" << std::endl;
std::cout << "最大客户端数: " << config.getMaxClients() << std::endl;
// 3. 等待用户开启热点
std::cout << "\n请按以下步骤操作:" << std::endl;
std::cout << "1. 手动开启PC热点(设置->网络和Internet->移动热点)" << std::endl;
std::cout << "2. 确认热点IP是否为: " << config.getHotspotIP() << std::endl;
std::cout << "3. 按Enter键继续..." << std::endl;
std::cin.get();
// 4. 初始化Windows Socket
if (!NetworkManager::init()) {
Logger::getInstance().log(LogLevel::ERROR, "网络初始化失败");
return -1;
}
// 5. 启动UDP发现服务
UDPDiscoverServer udpServer;
if (!udpServer.start(config.getUDPDiscoverPort())) {
Logger::getInstance().log(LogLevel::ERROR, "UDP服务启动失败");
NetworkManager::cleanup();
return -1;
}
// 6. 启动TCP服务器
TCPServer tcpServer;
if (!tcpServer.start(config.getHotspotIP(), config.getTCPServicePort())) {
Logger::getInstance().log(LogLevel::ERROR, "TCP服务启动失败");
udpServer.stop();
NetworkManager::cleanup();
return -1;
}
// 7. 启动心跳检测服务
HeartbeatManager heartbeatManager;
heartbeatManager.start(config.getHeartbeatTimeout());
Logger::getInstance().log(LogLevel::INFO,
"所有服务已启动,等待手机连接...");
Logger::getInstance().log(LogLevel::INFO,
"按Ctrl+C退出程序");
// 8. 主循环(等待退出信号)
while (g_running) {
std::this_thread::sleep_for(std::chrono::seconds(1));
// 显示当前连接状态
static int counter = 0;
if (++counter % 10 == 0) { // 每10秒显示一次状态
tcpServer.printStatus();
}
}
// 9. 优雅关闭所有服务
Logger::getInstance().log(LogLevel::INFO, "正在关闭服务...");
heartbeatManager.stop();
tcpServer.stop();
udpServer.stop();
NetworkManager::cleanup();
Logger::getInstance().log(LogLevel::INFO, "服务器已安全退出");
return 0;
}
4.2.2 UDP发现服务详细实现
cpp
// udp_discover.cpp - UDP广播发现服务完整实现
#include "udp_discover.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <cstring>
#include <vector>
#include <algorithm>
// UDP发现服务器类实现
class UDPDiscoverServer::Impl {
public:
Impl() : m_udpSocket(INVALID_SOCKET), m_running(false) {}
bool start(uint16_t port) {
// 1. 创建UDP套接字
m_udpSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (m_udpSocket == INVALID_SOCKET) {
Logger::getInstance().log(LogLevel::ERROR,
"创建UDP套接字失败: " + std::to_string(WSAGetLastError()));
return false;
}
// 2. 设置SO_REUSEADDR选项(允许快速重启)
int reuse = 1;
if (setsockopt(m_udpSocket, SOL_SOCKET, SO_REUSEADDR,
(char*)&reuse, sizeof(reuse)) == SOCKET_ERROR) {
Logger::getInstance().log(LogLevel::WARNING,
"设置SO_REUSEADDR失败,但不影响功能");
}
// 3. 设置广播权限
int broadcast = 1;
if (setsockopt(m_udpSocket, SOL_SOCKET, SO_BROADCAST,
(char*)&broadcast, sizeof(broadcast)) == SOCKET_ERROR) {
Logger::getInstance().log(LogLevel::ERROR,
"设置广播权限失败: " + std::to_string(WSAGetLastError()));
closesocket(m_udpSocket);
return false;
}
// 4. 设置非阻塞模式(可选)
u_long mode = 1; // 1=非阻塞,0=阻塞
if (ioctlsocket(m_udpSocket, FIONBIO, &mode) == SOCKET_ERROR) {
Logger::getInstance().log(LogLevel::WARNING,
"设置非阻塞模式失败,使用阻塞模式");
}
// 5. 绑定到指定端口
sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网络接口
serverAddr.sin_port = htons(port);
if (bind(m_udpSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
Logger::getInstance().log(LogLevel::ERROR,
"绑定UDP端口失败: " + std::to_string(WSAGetLastError()));
closesocket(m_udpSocket);
return false;
}
// 6. 获取本地IP地址(用于日志)
char hostname[256];
gethostname(hostname, sizeof(hostname));
struct hostent* host = gethostbyname(hostname);
if (host) {
for (int i = 0; host->h_addr_list[i]; i++) {
struct in_addr addr;
memcpy(&addr, host->h_addr_list[i], sizeof(struct in_addr));
Logger::getInstance().log(LogLevel::INFO,
"服务器IP地址: " + std::string(inet_ntoa(addr)));
}
}
// 7. 启动接收线程
m_running = true;
m_receiveThread = std::thread(&UDPDiscoverServer::Impl::receiveLoop, this);
m_cleanupThread = std::thread(&UDPDiscoverServer::Impl::cleanupLoop, this);
Logger::getInstance().log(LogLevel::INFO,
"UDP发现服务已启动,监听端口: " + std::to_string(port));
return true;
}
void stop() {
m_running = false;
// 等待线程结束
if (m_receiveThread.joinable()) {
m_receiveThread.join();
}
if (m_cleanupThread.joinable()) {
m_cleanupThread.join();
}
// 关闭套接字
if (m_udpSocket != INVALID_SOCKET) {
closesocket(m_udpSocket);
m_udpSocket = INVALID_SOCKET;
}
Logger::getInstance().log(LogLevel::INFO, "UDP发现服务已停止");
}
private:
// 客户端发现记录结构
struct ClientRecord {
std::string ip;
std::chrono::steady_clock::time_point lastSeen;
uint16_t tcpPort;
bool responded;
};
void receiveLoop() {
const int BUFFER_SIZE = 1024;
char buffer[BUFFER_SIZE];
sockaddr_in clientAddr;
int clientAddrLen = sizeof(clientAddr);
while (m_running) {
// 接收UDP数据包
memset(buffer, 0, BUFFER_SIZE);
int recvLen = recvfrom(m_udpSocket, buffer, BUFFER_SIZE, 0,
(sockaddr*)&clientAddr, &clientAddrLen);
if (recvLen <= 0) {
// 非阻塞模式下,没有数据时继续循环
if (WSAGetLastError() == WSAEWOULDBLOCK) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
}
Logger::getInstance().log(LogLevel::ERROR,
"接收UDP数据失败: " + std::to_string(WSAGetLastError()));
continue;
}
// 处理接收到的数据
processPacket(buffer, recvLen, clientAddr);
}
}
void processPacket(const char* data, int length, const sockaddr_in& clientAddr) {
std::string clientIP = inet_ntoa(clientAddr.sin_addr);
std::string message(data, length);
Logger::getInstance().log(LogLevel::DEBUG,
"收到UDP包[来自" + clientIP + "]: " + message);
// 加锁保护客户端记录
std::lock_guard<std::mutex> lock(m_clientsMutex);
// 查找或创建客户端记录
auto it = std::find_if(m_clients.begin(), m_clients.end(),
[&clientIP](const ClientRecord& record) {
return record.ip == clientIP;
});
if (it == m_clients.end()) {
// 新客户端
ClientRecord newClient;
newClient.ip = clientIP;
newClient.lastSeen = std::chrono::steady_clock::now();
newClient.responded = false;
m_clients.push_back(newClient);
it = m_clients.end() - 1;
Logger::getInstance().log(LogLevel::INFO,
"新客户端发现: " + clientIP);
} else {
// 更新现有客户端时间戳
it->lastSeen = std::chrono::steady_clock::now();
}
// 处理不同类型的消息
if (message == "DISCOVER_PC") {
handleDiscoverRequest(clientAddr, *it);
} else if (message.find("HELLO") == 0) {
handleHelloMessage(message, clientAddr, *it);
} else {
Logger::getInstance().log(LogLevel::WARNING,
"未知UDP消息格式: " + message);
}
}
void handleDiscoverRequest(const sockaddr_in& clientAddr, ClientRecord& client) {
// 获取TCP服务器端口(从配置中)
uint16_t tcpPort = ConfigManager::getInstance().getTCPServicePort();
// 构造响应消息
std::string response = std::to_string(tcpPort);
// 发送响应
int sent = sendto(m_udpSocket, response.c_str(), response.length(), 0,
(const sockaddr*)&clientAddr, sizeof(clientAddr));
if (sent == SOCKET_ERROR) {
Logger::getInstance().log(LogLevel::ERROR,
"发送UDP响应失败: " + std::to_string(WSAGetLastError()));
} else {
client.responded = true;
client.tcpPort = tcpPort;
Logger::getInstance().log(LogLevel::INFO,
"已回复客户端 " + client.ip + " TCP端口: " + std::to_string(tcpPort));
}
}
void handleHelloMessage(const std::string& message,
const sockaddr_in& clientAddr,
ClientRecord& client) {
// 解析HELLO消息,格式: "HELLO|设备名称|设备类型|版本"
std::vector<std::string> parts;
size_t start = 0, end = 0;
while ((end = message.find('|', start)) != std::string::npos) {
parts.push_back(message.substr(start, end - start));
start = end + 1;
}
parts.push_back(message.substr(start));
if (parts.size() >= 4) {
Logger::getInstance().log(LogLevel::INFO,
"设备信息 - IP: " + client.ip +
", 名称: " + parts[1] +
", 类型: " + parts[2] +
", 版本: " + parts[3]);
}
// 发送确认响应
std::string ack = "ACK_HELLO|" + parts[1];
sendto(m_udpSocket, ack.c_str(), ack.length(), 0,
(const sockaddr*)&clientAddr, sizeof(clientAddr));
}
void cleanupLoop() {
while (m_running) {
std::this_thread::sleep_for(std::chrono::seconds(30));
// 清理超时的客户端记录
std::lock_guard<std::mutex> lock(m_clientsMutex);
auto now = std::chrono::steady_clock::now();
auto it = m_clients.begin();
while (it != m_clients.end()) {
auto duration = std::chrono::duration_cast<std::chrono::seconds>(
now - it->lastSeen);
if (duration.count() > 300) { // 5分钟超时
Logger::getInstance().log(LogLevel::INFO,
"清理超时客户端: " + it->ip);
it = m_clients.erase(it);
} else {
++it;
}
}
}
}
SOCKET m_udpSocket;
std::atomic<bool> m_running;
std::thread m_receiveThread;
std::thread m_cleanupThread;
std::vector<ClientRecord> m_clients;
std::mutex m_clientsMutex;
};
// UDPDiscoverServer公共接口实现
UDPDiscoverServer::UDPDiscoverServer() : m_impl(new Impl()) {}
UDPDiscoverServer::~UDPDiscoverServer() { delete m_impl; }
bool UDPDiscoverServer::start(uint16_t port) {
return m_impl->start(port);
}
void UDPDiscoverServer::stop() {
m_impl->stop();
}
4.2.3 TCP服务器详细实现
cpp
// tcp_server.cpp - TCP服务器完整实现
#include "tcp_server.h"
#include <iostream>
#include <thread>
#include <vector>
#include <memory>
#include <queue>
#include <condition_variable>
#include <atomic>
// 线程安全的队列模板
template<typename T>
class ThreadSafeQueue {
public:
void push(const T& value) {
std::lock_guard<std::mutex> lock(m_mutex);
m_queue.push(value);
m_cond.notify_one();
}
bool try_pop(T& value) {
std::lock_guard<std::mutex> lock(m_mutex);
if (m_queue.empty()) return false;
value = std::move(m_queue.front());
m_queue.pop();
return true;
}
bool empty() const {
std::lock_guard<std::mutex> lock(m_mutex);
return m_queue.empty();
}
size_t size() const {
std::lock_guard<std::mutex> lock(m_mutex);
return m_queue.size();
}
void wait_and_pop(T& value) {
std::unique_lock<std::mutex> lock(m_mutex);
m_cond.wait(lock, [this] { return !m_queue.empty(); });
value = std::move(m_queue.front());
m_queue.pop();
}
private:
mutable std::mutex m_mutex;
std::queue<T> m_queue;
std::condition_variable m_cond;
};
// TCP客户端会话类
class TCPClientSession {
public:
TCPClientSession(SOCKET socket, const std::string& clientIP, uint16_t clientPort)
: m_socket(socket), m_clientIP(clientIP), m_clientPort(clientPort),
m_running(false), m_lastActivity(std::chrono::steady_clock::now()) {
// 设置Socket选项
int timeout = 5000; // 5秒超时
setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
setsockopt(m_socket, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));
}
~TCPClientSession() {
stop();
}
void start() {
m_running = true;
m_receiveThread = std::thread(&TCPClientSession::receiveLoop, this);
m_sendThread = std::thread(&TCPClientSession::sendLoop, this);
Logger::getInstance().log(LogLevel::INFO,
"客户端会话启动: " + m_clientIP + ":" + std::to_string(m_clientPort));
}
void stop() {
m_running = false;
// 关闭Socket以唤醒阻塞的线程
if (m_socket != INVALID_SOCKET) {
shutdown(m_socket, SD_BOTH);
closesocket(m_socket);
m_socket = INVALID_SOCKET;
}
// 等待线程结束
if (m_receiveThread.joinable()) m_receiveThread.join();
if (m_sendThread.joinable()) m_sendThread.join();
Logger::getInstance().log(LogLevel::INFO,
"客户端会话结束: " + m_clientIP);
}
void sendMessage(const std::string& message) {
m_sendQueue.push(message);
}
std::string getClientInfo() const {
return m_clientIP + ":" + std::to_string(m_clientPort);
}
std::chrono::steady_clock::time_point getLastActivity() const {
return m_lastActivity;
}
bool isConnected() const {
return m_running && m_socket != INVALID_SOCKET;
}
private:
void receiveLoop() {
const int BUFFER_SIZE = 4096;
char buffer[BUFFER_SIZE];
while (m_running && m_socket != INVALID_SOCKET) {
memset(buffer, 0, BUFFER_SIZE);
// 接收数据
int recvLen = recv(m_socket, buffer, BUFFER_SIZE - 1, 0);
if (recvLen > 0) {
// 成功收到数据
buffer[recvLen] = '\0';
m_lastActivity = std::chrono::steady_clock::now();
// 处理接收到的消息
processReceivedData(buffer, recvLen);
} else if (recvLen == 0) {
// 连接关闭
Logger::getInstance().log(LogLevel::INFO,
"客户端主动关闭连接: " + m_clientIP);
break;
} else {
// 接收错误
int error = WSAGetLastError();
if (error != WSAETIMEDOUT && error != WSAEWOULDBLOCK) {
Logger::getInstance().log(LogLevel::ERROR,
"接收数据错误: " + std::to_string(error));
break;
}
}
}
m_running = false;
}
void processReceivedData(const char* data, int length) {
std::string message(data, length);
// 心跳包处理
if (message == "PING") {
m_sendQueue.push("PONG");
Logger::getInstance().log(LogLevel::DEBUG,
"收到心跳包,回复PONG");
return;
}
// JSON消息格式检查
if (message.find("{") == 0 && message.find("}") != std::string::npos) {
processJsonMessage(message);
} else {
// 普通文本消息
Logger::getInstance().log(LogLevel::INFO,
"收到消息[" + m_clientIP + "]: " + message);
// 回显消息(示例)
std::string echo = "ECHO: " + message;
m_sendQueue.push(echo);
}
}
void processJsonMessage(const std::string& json) {
try {
// 这里可以集成JSON解析库,如nlohmann/json
// 示例:解析消息类型
if (json.find("\"type\":\"file_info\"") != std::string::npos) {
// 处理文件信息
Logger::getInstance().log(LogLevel::INFO,
"收到文件信息: " + json);
// 确认接收
std::string ack = "{\"status\":\"ok\",\"message\":\"file_info_received\"}";
m_sendQueue.push(ack);
} else if (json.find("\"type\":\"command\"") != std::string::npos) {
// 处理命令
Logger::getInstance().log(LogLevel::INFO,
"收到命令: " + json);
// 执行命令并回复结果
std::string result = "{\"status\":\"ok\",\"result\":\"command_executed\"}";
m_sendQueue.push(result);
}
} catch (...) {
Logger::getInstance().log(LogLevel::ERROR,
"JSON消息解析失败: " + json);
}
}
void sendLoop() {
while (m_running && m_socket != INVALID_SOCKET) {
std::string message;
m_sendQueue.wait_and_pop(message);
if (!m_running) break;
// 发送消息
int sent = 0;
int total = message.length();
const char* buffer = message.c_str();
while (sent < total && m_running) {
int ret = send(m_socket, buffer + sent, total - sent, 0);
if (ret <= 0) {
int error = WSAGetLastError();
if (error != WSAETIMEDOUT) {
Logger::getInstance().log(LogLevel::ERROR,
"发送数据失败: " + std::to_string(error));
m_running = false;
break;
}
} else {
sent += ret;
}
}
if (sent == total) {
Logger::getInstance().log(LogLevel::DEBUG,
"发送消息成功: " + std::to_string(sent) + " bytes");
}
}
}
SOCKET m_socket;
std::string m_clientIP;
uint16_t m_clientPort;
std::atomic<bool> m_running;
std::chrono::steady_clock::time_point m_lastActivity;
std::thread m_receiveThread;
std::thread m_sendThread;
ThreadSafeQueue<std::string> m_sendQueue;
};
// TCP服务器实现类
class TCPServer::Impl {
public:
Impl() : m_listenSocket(INVALID_SOCKET), m_running(false) {}
bool start(const std::string& ip, uint16_t port) {
// 1. 创建监听Socket
m_listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (m_listenSocket == INVALID_SOCKET) {
Logger::getInstance().log(LogLevel::ERROR,
"创建TCP Socket失败: " + std::to_string(WSAGetLastError()));
return false;
}
// 2. 设置Socket选项
int reuse = 1;
setsockopt(m_listenSocket, SOL_SOCKET, SO_REUSEADDR,
(char*)&reuse, sizeof(reuse));
// 3. 绑定地址和端口
sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
inet_pton(AF_INET, ip.c_str(), &serverAddr.sin_addr);
serverAddr.sin_port = htons(port);
if (bind(m_listenSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
Logger::getInstance().log(LogLevel::ERROR,
"绑定TCP地址失败: " + std::to_string(WSAGetLastError()));
closesocket(m_listenSocket);
return false;
}
// 4. 开始监听
if (listen(m_listenSocket, SOMAXCONN) == SOCKET_ERROR) {
Logger::getInstance().log(LogLevel::ERROR,
"监听TCP端口失败: " + std::to_string(WSAGetLastError()));
closesocket(m_listenSocket);
return false;
}
// 5. 启动接收线程
m_running = true;
m_acceptThread = std::thread(&TCPServer::Impl::acceptLoop, this);
Logger::getInstance().log(LogLevel::INFO,
"TCP服务器已启动: " + ip + ":" + std::to_string(port));
return true;
}
void stop() {
m_running = false;
// 关闭监听Socket以唤醒accept
if (m_listenSocket != INVALID_SOCKET) {
shutdown(m_listenSocket, SD_BOTH);
closesocket(m_listenSocket);
m_listenSocket = INVALID_SOCKET;
}
// 等待接收线程结束
if (m_acceptThread.joinable()) {
m_acceptThread.join();
}
// 关闭所有客户端会话
{
std::lock_guard<std::mutex> lock(m_clientsMutex);
for (auto& session : m_clients) {
session->stop();
}
m_clients.clear();
}
Logger::getInstance().log(LogLevel::INFO, "TCP服务器已停止");
}
void printStatus() {
std::lock_guard<std::mutex> lock(m_clientsMutex);
Logger::getInstance().log(LogLevel::INFO,
"TCP服务器状态 - 客户端数量: " + std::to_string(m_clients.size()));
for (const auto& session : m_clients) {
auto now = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(
now - session->getLastActivity());
Logger::getInstance().log(LogLevel::INFO,
" 客户端: " + session->getClientInfo() +
", 活跃: " + std::to_string(duration.count()) + "秒前");
}
}
void broadcastMessage(const std::string& message) {
std::lock_guard<std::mutex> lock(m_clientsMutex);
for (auto& session : m_clients) {
if (session->isConnected()) {
session->sendMessage(message);
}
}
}
private:
void acceptLoop() {
while (m_running) {
sockaddr_in clientAddr;
int clientAddrLen = sizeof(clientAddr);
// 接受新的客户端连接
SOCKET clientSocket = accept(m_listenSocket,
(sockaddr*)&clientAddr,
&clientAddrLen);
if (clientSocket == INVALID_SOCKET) {
if (m_running) {
Logger::getInstance().log(LogLevel::ERROR,
"接受连接失败: " + std::to_string(WSAGetLastError()));
}
continue;
}
// 获取客户端信息
std::string clientIP = inet_ntoa(clientAddr.sin_addr);
uint16_t clientPort = ntohs(clientAddr.sin_port);
// 创建客户端会话
auto session = std::make_shared<TCPClientSession>(
clientSocket, clientIP, clientPort);
// 添加到客户端列表
{
std::lock_guard<std::mutex> lock(m_clientsMutex);
// 清理已断开的会话
auto it = m_clients.begin();
while (it != m_clients.end()) {
if (!(*it)->isConnected()) {
it = m_clients.erase(it);
} else {
++it;
}
}
// 检查最大连接数
if (m_clients.size() >= ConfigManager::getInstance().getMaxClients()) {
Logger::getInstance().log(LogLevel::WARNING,
"达到最大客户端数,拒绝新连接: " + clientIP);
closesocket(clientSocket);
continue;
}
m_clients.push_back(session);
}
// 启动会话
session->start();
Logger::getInstance().log(LogLevel::INFO,
"新客户端连接: " + clientIP + ":" + std::to_string(clientPort) +
", 当前连接数: " + std::to_string(m_clients.size()));
// 发送欢迎消息
std::string welcome = "欢迎连接到PC热点通信服务器!";
session->sendMessage(welcome);
}
}
SOCKET m_listenSocket;
std::atomic<bool> m_running;
std::thread m_acceptThread;
std::vector<std::shared_ptr<TCPClientSession>> m_clients;
std::mutex m_clientsMutex;
};
// TCPServer公共接口实现
TCPServer::TCPServer() : m_impl(new Impl()) {}
TCPServer::~TCPServer() { delete m_impl; }
bool TCPServer::start(const std::string& ip, uint16_t port) {
return m_impl->start(ip, port);
}
void TCPServer::stop() {
m_impl->stop();
}
void TCPServer::printStatus() {
m_impl->printStatus();
}
void TCPServer::broadcastMessage(const std::string& message) {
m_impl->broadcastMessage(message);
}
4.2.4 心跳管理模块
cpp
// heartbeat.cpp - 心跳检测管理
#include "heartbeat.h"
#include <algorithm>
#include <chrono>
class HeartbeatManager::Impl {
public:
Impl() : m_running(false) {}
void start(int timeoutSeconds) {
m_timeoutSeconds = timeoutSeconds;
m_running = true;
m_checkThread = std::thread(&HeartbeatManager::Impl::checkLoop, this);
Logger::getInstance().log(LogLevel::INFO,
"心跳检测已启动,超时时间: " + std::to_string(timeoutSeconds) + "秒");
}
void stop() {
m_running = false;
if (m_checkThread.joinable()) {
m_checkThread.join();
}
Logger::getInstance().log(LogLevel::INFO, "心跳检测已停止");
}
void updateClientActivity(const std::string& clientId) {
std::lock_guard<std::mutex> lock(m_clientsMutex);
m_clients[clientId] = std::chrono::steady_clock::now();
}
void removeClient(const std::string& clientId) {
std::lock_guard<std::mutex> lock(m_clientsMutex);
m_clients.erase(clientId);
}
private:
void checkLoop() {
while (m_running) {
std::this_thread::sleep_for(std::chrono::seconds(1));
auto now = std::chrono::steady_clock::now();
std::lock_guard<std::mutex> lock(m_clientsMutex);
auto it = m_clients.begin();
while (it != m_clients.end()) {
auto duration = std::chrono::duration_cast<std::chrono::seconds>(
now - it->second);
if (duration.count() > m_timeoutSeconds) {
// 客户端超时
Logger::getInstance().log(LogLevel::WARNING,
"客户端心跳超时: " + it->first);
// 这里可以触发断开连接逻辑
// 注意:在实际TCP连接管理中应该处理
it = m_clients.erase(it);
} else {
++it;
}
}
}
}
std::atomic<bool> m_running;
std::thread m_checkThread;
int m_timeoutSeconds;
std::unordered_map<std::string,
std::chrono::steady_clock::time_point> m_clients;
std::mutex m_clientsMutex;
};
HeartbeatManager::HeartbeatManager() : m_impl(new Impl()) {}
HeartbeatManager::~HeartbeatManager() { delete m_impl; }
void HeartbeatManager::start(int timeoutSeconds) {
m_impl->start(timeoutSeconds);
}
void HeartbeatManager::stop() {
m_impl->stop();
}
void HeartbeatManager::updateClientActivity(const std::string& clientId) {
m_impl->updateClientActivity(clientId);
}
void HeartbeatManager::removeClient(const std::string& clientId) {
m_impl->removeClient(clientId);
}
4.3 配置文件设计
ini
; config.ini - 配置文件示例
[network]
; 热点IP地址(手动开启热点后确认)
hotspot_ip = 192.168.137.1
; UDP发现端口
udp_discover_port = 9999
; TCP服务端口
tcp_service_port = 8888
; 心跳超时时间(秒)
heartbeat_timeout = 15
; 最大客户端连接数
max_clients = 10
[logging]
; 日志级别: DEBUG, INFO, WARNING, ERROR, FATAL
log_level = INFO
; 日志文件路径
log_file = pc_server.log
; 是否输出到控制台
console_output = true
[security]
; 是否启用消息加密
enable_encryption = false
; AES加密密钥(256位)
aes_key = 0123456789ABCDEF0123456789ABCDEF
[advanced]
; 接收缓冲区大小(字节)
receive_buffer_size = 4096
; 发送缓冲区大小(字节)
send_buffer_size = 4096
; TCP连接超时(毫秒)
tcp_connect_timeout = 5000
; 是否启用Nagle算法
tcp_nodelay = true
4.4 构建配置文件(CMake)
cmake
# CMakeLists.txt - 构建配置
cmake_minimum_required(VERSION 3.15)
project(PC_Hotspot_Server)
# 设置C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 在Windows上链接Winsock库
if(WIN32)
set(PLATFORM_LIBS ws2_32)
add_definitions(-DWIN32_LEAN_AND_MEAN)
endif()
# 包含目录
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/third_party
)
# 源文件
set(SOURCES
src/main.cpp
src/network/udp_discover.cpp
src/network/tcp_server.cpp
src/network/heartbeat.cpp
src/utils/logger.cpp
src/utils/config.cpp
src/protocol/message.cpp
src/protocol/parser.cpp
)
# 创建可执行文件
add_executable(pc_hotspot_server ${SOURCES})
# 链接库
target_link_libraries(pc_hotspot_server ${PLATFORM_LIBS})
# 安装目标
install(TARGETS pc_hotspot_server
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
# 安装配置文件
install(FILES config.ini DESTINATION etc)
五、手机端实现(Android/Kotlin)
5.1 Android项目结构
AndroidHotspotClient/
├── app/
│ ├── src/main/
│ │ ├── java/com/example/hotspotclient/
│ │ │ ├── MainActivity.kt
│ │ │ ├── network/
│ │ │ │ ├── PCHotspotClient.kt
│ │ │ │ ├── UdpDiscover.kt
│ │ │ │ └── TcpConnection.kt
│ │ │ ├── ui/
│ │ │ │ ├── ConnectionFragment.kt
│ │ │ │ ├── MessageFragment.kt
│ │ │ │ └── FileTransferFragment.kt
│ │ │ └── utils/
│ │ │ ├── Logger.kt
│ │ │ └── PreferenceHelper.kt
│ │ ├── res/
│ │ └── AndroidManifest.xml
│ └── build.gradle
└── build.gradle
5.2 完整的Android实现
5.2.1 AndroidManifest.xml配置
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hotspotclient">
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<!-- 文件传输相关权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Android 10+ 需要额外的文件权限 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
android:minSdkVersion="30" />
<!-- 前台服务权限(保持后台连接) -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HotspotClient"
android:usesCleartextTraffic="true"> <!-- 允许HTTP明文传输 -->
<!-- 主Activity -->
<activity
android:name=".MainActivity"
android:exported="true"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 文件选择Activity -->
<activity
android:name=".ui.FilePickerActivity"
android:exported="false" />
<!-- 连接服务(后台保持连接) -->
<service
android:name=".network.ConnectionService"
android:exported="false"
android:foregroundServiceType="dataSync" />
<!-- 文件传输服务 -->
<service
android:name=".network.FileTransferService"
android:exported="false" />
</application>
</manifest>
5.2.2 增强的PCHotspotClient类
kotlin
// PCHotspotClient.kt - 增强的网络客户端
package com.example.hotspotclient.network
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.wifi.WifiManager
import android.os.Build
import android.util.Log
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import java.io.*
import java.net.*
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
/**
* PC热点通信客户端(增强版)
* 支持:
* 1. UDP自动发现
* 2. TCP可靠通信
* 3. 心跳保活
* 4. 自动重连
* 5. 文件传输
* 6. 消息加密
*/
class PCHotspotClient(private val context: Context) {
companion object {
private const val TAG = "PCHotspotClient"
private const val UDP_DISCOVER_PORT = 9999
private const val DEFAULT_TCP_PORT = 8888
private const val BROADCAST_ADDRESS = "255.255.255.255"
private const val DISCOVER_MAGIC = "DISC"
private const val RESPONSE_MAGIC = "RESP"
// 心跳配置
private const val HEARTBEAT_INTERVAL = 5000L // 5秒
private const val HEARTBEAT_TIMEOUT = 15000L // 15秒
private const val RECONNECT_DELAY = 3000L // 3秒
// 消息类型
const val MSG_TYPE_TEXT = 1
const val MSG_TYPE_FILE_INFO = 2
const val MSG_TYPE_COMMAND = 3
const val MSG_TYPE_HEARTBEAT = 4
}
// 协程相关
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private var heartbeatJob: Job? = null
private var receiveJob: Job? = null
private var discoverJob: Job? = null
// 网络连接
private var tcpSocket: Socket? = null
private var outputStream: OutputStream? = null
private var inputStream: InputStream? = null
private var currentTcpPort = DEFAULT_TCP_PORT
private var pcHotspotIP = "192.168.137.1"
// 状态管理
private val isConnected = AtomicBoolean(false)
private val isDiscovering = AtomicBoolean(false)
private val isReconnecting = AtomicBoolean(false)
private var lastHeartbeatTime = System.currentTimeMillis()
private var connectionStartTime = 0L
// 数据流
private val _connectionState = MutableStateFlow<ConnectionState>(ConnectionState.DISCONNECTED)
val connectionState: StateFlow<ConnectionState> = _connectionState
private val _receivedMessages = Channel<NetworkMessage>(Channel.UNLIMITED)
val receivedMessages: Flow<NetworkMessage> = _receivedMessages.receiveAsFlow()
private val _fileTransferProgress = MutableStateFlow<FileTransferProgress?>(null)
val fileTransferProgress: StateFlow<FileTransferProgress?> = _fileTransferProgress
// 配置
private val preferences = context.getSharedPreferences("hotspot_config", Context.MODE_PRIVATE)
private var enableEncryption = false
private var aesKey: ByteArray? = null
// 枚举定义
enum class ConnectionState {
DISCONNECTED, // 未连接
DISCOVERING, // 正在发现PC
CONNECTING, // 正在连接PC
CONNECTED, // 已连接
TRANSFERRING, // 正在传输文件
ERROR // 连接错误
}
data class NetworkMessage(
val type: Int,
val content: String,
val timestamp: Long = System.currentTimeMillis(),
val sender: String = "PC"
)
data class FileTransferProgress(
val fileName: String,
val totalBytes: Long,
val transferredBytes: Long,
val progress: Float,
val isUpload: Boolean,
val state: TransferState
) {
enum class TransferState {
PREPARING,
TRANSFERRING,
COMPLETED,
FAILED,
CANCELLED
}
}
/**
* 启动通信流程
*/
fun start() {
if (isConnected.get()) {
Log.w(TAG, "客户端已启动,忽略重复调用")
return
}
scope.launch {
_connectionState.value = ConnectionState.DISCOVERING
loadConfig()
// 先尝试使用上次保存的PC IP和端口
val savedIP = preferences.getString("pc_ip", null)
val savedPort = preferences.getInt("pc_port", -1)
if (savedIP != null && savedPort != -1) {
Log.d(TAG, "使用保存的连接信息: $savedIP:$savedPort")
connectTCPServer(savedIP, savedPort, isFirstAttempt = false)
}
// 同时开始UDP发现
discoverPCByUDP()
}
}
/**
* UDP发现PC
*/
private suspend fun discoverPCByUDP() {
if (isDiscovering.getAndSet(true)) {
Log.d(TAG, "UDP发现已在运行中")
return
}
discoverJob = scope.launch {
var udpSocket: DatagramSocket? = null
try {
_connectionState.value = ConnectionState.DISCOVERING
// 创建UDP Socket
udpSocket = DatagramSocket()
udpSocket.broadcast = true
udpSocket.soTimeout = 5000 // 5秒超时
// 构造发现消息(增强协议)
val deviceInfo = getDeviceInfo()
val discoverMsg = buildDiscoverMessage(deviceInfo)
// 发送广播
val broadcastAddr = InetAddress.getByName(BROADCAST_ADDRESS)
val sendPacket = DatagramPacket(
discoverMsg, discoverMsg.size,
broadcastAddr, UDP_DISCOVER_PORT
)
udpSocket.send(sendPacket)
Log.d(TAG, "UDP发现广播已发送: ${deviceInfo.deviceName}")
// 接收响应(最多尝试3次)
repeat(3) { attempt ->
if (!isDiscovering.get()) return@repeat
try {
val recvBuffer = ByteArray(1024)
val recvPacket = DatagramPacket(recvBuffer, recvBuffer.size)
udpSocket.receive(recvPacket)
val response = parseDiscoverResponse(recvPacket.data, recvPacket.length)
if (response != null) {
val pcIP = recvPacket.address.hostAddress
val pcPort = response.tcpPort
Log.d(TAG, "发现PC: $pcIP:$pcPort, 设备: ${response.deviceName}")
// 保存连接信息
with(preferences.edit()) {
putString("pc_ip", pcIP)
putInt("pc_port", pcPort)
apply()
}
// 连接到PC
connectTCPServer(pcIP, pcPort, isFirstAttempt = true)
return@repeat
}
} catch (e: SocketTimeoutException) {
Log.d(TAG, "UDP发现超时 (尝试 ${attempt + 1}/3)")
}
}
// 所有尝试都失败
if (isDiscovering.get() && !isConnected.get()) {
Log.w(TAG, "UDP发现失败,尝试默认连接")
val savedIP = preferences.getString("pc_ip", pcHotspotIP)
val savedPort = preferences.getInt("pc_port", DEFAULT_TCP_PORT)
connectTCPServer(savedIP ?: pcHotspotIP, savedPort, isFirstAttempt = true)
}
} catch (e: Exception) {
Log.e(TAG, "UDP发现失败: ${e.message}", e)
_connectionState.value = ConnectionState.ERROR
} finally {
udpSocket?.close()
isDiscovering.set(false)
}
}
}
/**
* 连接TCP服务器
*/
private suspend fun connectTCPServer(ip: String, port: Int, isFirstAttempt: Boolean) {
if (isReconnecting.getAndSet(true) && !isFirstAttempt) {
Log.d(TAG, "已经在重连中,跳过")
return
}
scope.launch {
try {
_connectionState.value = ConnectionState.CONNECTING
// 停止UDP发现
discoverJob?.cancel()
isDiscovering.set(false)
// 关闭现有连接
disconnectInternal()
// 创建TCP连接
val socket = Socket()
socket.soTimeout = 5000 // 连接超时5秒
withContext(Dispatchers.IO) {
socket.connect(InetSocketAddress(ip, port), 5000)
}
// 连接成功
tcpSocket = socket
outputStream = socket.getOutputStream()
inputStream = socket.getInputStream()
currentTcpPort = port
pcHotspotIP = ip
isConnected.set(true)
connectionStartTime = System.currentTimeMillis()
lastHeartbeatTime = System.currentTimeMillis()
// 更新状态
_connectionState.value = ConnectionState.CONNECTED
isReconnecting.set(false)
Log.d(TAG, "TCP连接成功: $ip:$port")
// 发送连接确认消息
sendConnectionInfo()
// 启动心跳
startHeartbeat()
// 启动消息接收
startReceiving()
} catch (e: Exception) {
Log.e(TAG, "TCP连接失败: ${e.message}", e)
// 更新状态
_connectionState.value = ConnectionState.ERROR
isReconnecting.set(false)
// 延迟后重试
if (isFirstAttempt) {
delay(RECONNECT_DELAY)
discoverPCByUDP()
}
}
}
}
/**
* 启动心跳
*/
private fun startHeartbeat() {
heartbeatJob?.cancel()
heartbeatJob = scope.launch {
while (isConnected.get() && isActive) {
try {
// 检查心跳超时
val now = System.currentTimeMillis()
if (now - lastHeartbeatTime > HEARTBEAT_TIMEOUT) {
Log.w(TAG, "心跳超时,触发重连")
reconnect()
break
}
// 发送心跳
sendHeartbeat()
delay(HEARTBEAT_INTERVAL)
} catch (e: Exception) {
Log.e(TAG, "心跳发送失败: ${e.message}")
if (isConnected.get()) {
reconnect()
}
break
}
}
}
}
/**
* 启动消息接收
*/
private fun startReceiving() {
receiveJob?.cancel()
receiveJob = scope.launch {
val reader = BufferedReader(InputStreamReader(inputStream!!, StandardCharsets.UTF_8))
val buffer = CharArray(4096)
try {
while (isConnected.get() && isActive) {
val length = withContext(Dispatchers.IO) {
reader.read(buffer)
}
if (length == -1) {
Log.d(TAG, "连接已关闭")
reconnect()
break
}
val message = String(buffer, 0, length).trim()
if (message.isNotEmpty()) {
processReceivedMessage(message)
}
}
} catch (e: Exception) {
Log.e(TAG, "接收消息失败: ${e.message}")
if (isConnected.get()) {
reconnect()
}
}
}
}
/**
* 处理接收到的消息
*/
private suspend fun processReceivedMessage(message: String) {
// 更新心跳时间
if (message == "PONG") {
lastHeartbeatTime = System.currentTimeMillis()
Log.d(TAG, "收到心跳回复")
return
}
// 解密消息(如果启用加密)
val decryptedMessage = if (enableEncryption && aesKey != null) {
decryptMessage(message)
} else {
message
}
// 解析消息类型
val networkMessage = parseNetworkMessage(decryptedMessage)
// 发送到消息通道
_receivedMessages.send(networkMessage)
Log.d(TAG, "收到消息[${networkMessage.type}]: ${networkMessage.content}")
}
/**
* 发送文本消息
*/
fun sendTextMessage(text: String) {
scope.launch {
if (!isConnected.get()) {
Log.w(TAG, "未连接,无法发送消息")
return@launch
}
try {
val message = buildTextMessage(text)
sendRawMessage(message)
// 本地也显示发送的消息
val sentMessage = NetworkMessage(
type = MSG_TYPE_TEXT,
content = text,
sender = "我"
)
_receivedMessages.send(sentMessage)
} catch (e: Exception) {
Log.e(TAG, "发送消息失败: ${e.message}")
reconnect()
}
}
}
/**
* 发送文件
*/
fun sendFile(filePath: String, fileName: String? = null) {
scope.launch {
if (!isConnected.get()) {
Log.w(TAG, "未连接,无法发送文件")
return@launch
}
try {
_connectionState.value = ConnectionState.TRANSFERRING
val file = File(filePath)
if (!file.exists()) {
throw IOException("文件不存在: $filePath")
}
val actualFileName = fileName ?: file.name
val fileSize = file.length()
// 1. 发送文件信息
val fileInfo = buildFileInfoMessage(actualFileName, fileSize)
sendRawMessage(fileInfo)
// 2. 等待PC确认
delay(100) // 简单延迟,实际应该等待确认消息
// 3. 发送文件数据
_fileTransferProgress.value = FileTransferProgress(
fileName = actualFileName,
totalBytes = fileSize,
transferredBytes = 0,
progress = 0f,
isUpload = true,
state = FileTransferProgress.TransferState.TRANSFERRING
)
val buffer = ByteArray(4096)
var bytesSent = 0L
FileInputStream(file).use { fis ->
BufferedInputStream(fis).use { bis ->
while (bytesSent < fileSize) {
val read = bis.read(buffer)
if (read == -1) break
// 发送数据块
val chunk = buildFileChunkMessage(buffer, read)
sendRawMessage(chunk)
bytesSent += read
// 更新进度
val progress = bytesSent.toFloat() / fileSize
_fileTransferProgress.value = FileTransferProgress(
fileName = actualFileName,
totalBytes = fileSize,
transferredBytes = bytesSent,
progress = progress,
isUpload = true,
state = FileTransferProgress.TransferState.TRANSFERRING
)
// 控制发送速度
delay(10)
}
}
}
// 4. 发送完成标记
val endMarker = buildFileEndMessage(actualFileName, fileSize)
sendRawMessage(endMarker)
// 更新状态
_fileTransferProgress.value = FileTransferProgress(
fileName = actualFileName,
totalBytes = fileSize,
transferredBytes = fileSize,
progress = 1f,
isUpload = true,
state = FileTransferProgress.TransferState.COMPLETED
)
_connectionState.value = ConnectionState.CONNECTED
Log.d(TAG, "文件发送完成: $actualFileName ($fileSize bytes)")
} catch (e: Exception) {
Log.e(TAG, "文件发送失败: ${e.message}", e)
_fileTransferProgress.value = FileTransferProgress(
fileName = fileName ?: "",
totalBytes = 0,
transferredBytes = 0,
progress = 0f,
isUpload = true,
state = FileTransferProgress.TransferState.FAILED
)
_connectionState.value = ConnectionState.CONNECTED
reconnect()
}
}
}
/**
* 重新连接
*/
private fun reconnect() {
scope.launch {
if (isReconnecting.getAndSet(true)) {
return@launch
}
Log.d(TAG, "开始重新连接...")
_connectionState.value = ConnectionState.DISCONNECTED
// 断开现有连接
disconnectInternal()
// 延迟后重试
delay(RECONNECT_DELAY)
// 重新发现PC
isReconnecting.set(false)
discoverPCByUDP()
}
}
/**
* 断开连接
*/
fun disconnect() {
scope.launch {
disconnectInternal()
_connectionState.value = ConnectionState.DISCONNECTED
Log.d(TAG, "手动断开连接")
}
}
/**
* 内部断开连接
*/
private fun disconnectInternal() {
isConnected.set(false)
isDiscovering.set(false)
// 取消所有任务
heartbeatJob?.cancel()
receiveJob?.cancel()
discoverJob?.cancel()
// 关闭Socket
try {
tcpSocket?.close()
} catch (e: Exception) {
Log.e(TAG, "关闭Socket失败: ${e.message}")
}
tcpSocket = null
outputStream = null
inputStream = null
}
/**
* 加载配置
*/
private fun loadConfig() {
enableEncryption = preferences.getBoolean("enable_encryption", false)
val savedKey = preferences.getString("aes_key", null)
if (savedKey != null) {
aesKey = savedKey.toByteArray()
}
// 获取设备信息
val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiInfo = wifiManager.connectionInfo
pcHotspotIP = preferences.getString("pc_ip", "192.168.137.1") ?: "192.168.137.1"
}
/**
* 获取设备信息
*/
private fun getDeviceInfo(): DeviceInfo {
return DeviceInfo(
deviceName = Build.MODEL,
deviceType = "Android",
osVersion = Build.VERSION.RELEASE,
appVersion = "1.0.0"
)
}
// 以下为消息构建和解析的辅助方法...
data class DeviceInfo(
val deviceName: String,
val deviceType: String,
val osVersion: String,
val appVersion: String
)
data class DiscoverResponse(
val tcpPort: Int,
val deviceName: String,
val ipAddress: String
)
// 消息构建方法
private fun buildDiscoverMessage(deviceInfo: DeviceInfo): ByteArray {
val message = StringBuilder()
message.append(DISCOVER_MAGIC)
message.append("|")
message.append(deviceInfo.deviceName)
message.append("|")
message.append(deviceInfo.deviceType)
message.append("|")
message.append(deviceInfo.osVersion)
message.append("|")
message.append(deviceInfo.appVersion)
return message.toString().toByteArray()
}
private fun parseDiscoverResponse(data: ByteArray, length: Int): DiscoverResponse? {
val response = String(data, 0, length, StandardCharsets.UTF_8)
if (!response.startsWith(RESPONSE_MAGIC)) {
return null
}
val parts = response.split("|")
if (parts.size >= 3) {
return try {
DiscoverResponse(
tcpPort = parts[1].toInt(),
deviceName = parts[2],
ipAddress = parts.getOrElse(3) { "" }
)
} catch (e: NumberFormatException) {
null
}
}
return null
}
private fun buildTextMessage(text: String): String {
return "TEXT|$text"
}
private fun buildFileInfoMessage(fileName: String, fileSize: Long): String {
return "FILE_INFO|$fileName|$fileSize"
}
private fun parseNetworkMessage(message: String): NetworkMessage {
return when {
message.startsWith("TEXT|") -> {
val content = message.substringAfter("TEXT|")
NetworkMessage(MSG_TYPE_TEXT, content)
}
message.startsWith("FILE_INFO|") -> {
NetworkMessage(MSG_TYPE_FILE_INFO, message)
}
message == "PONG" -> {
NetworkMessage(MSG_TYPE_HEARTBEAT, message)
}
else -> {
NetworkMessage(MSG_TYPE_TEXT, message)
}
}
}
private fun sendRawMessage(message: String) {
val bytes = if (enableEncryption && aesKey != null) {
encryptMessage(message)
} else {
message.toByteArray(StandardCharsets.UTF_8)
}
outputStream?.write(bytes)
outputStream?.write('\n'.code) // 添加分隔符
outputStream?.flush()
}
private fun sendHeartbeat() {
sendRawMessage("PING")
}
private fun sendConnectionInfo() {
val deviceInfo = getDeviceInfo()
val info = "CONNECT|${deviceInfo.deviceName}|${deviceInfo.appVersion}"
sendRawMessage(info)
}
// 加密解密方法
private fun encryptMessage(message: String): ByteArray {
// 简化的AES加密实现
// 实际项目中应该使用更安全的密钥管理和加密方式
return message.toByteArray(StandardCharsets.UTF_8)
}
private fun decryptMessage(encrypted: String): String {
// 简化解密实现
return encrypted
}
}
5.2.3 MainActivity实现
kotlin
// MainActivity.kt - 主界面
package com.example.hotspotclient
import android.Manifest
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.example.hotspotclient.databinding.ActivityMainBinding
import com.example.hotspotclient.network.PCHotspotClient
import com.example.hotspotclient.ui.ConnectionFragment
import com.example.hotspotclient.ui.MessageFragment
import com.example.hotspotclient.ui.FileTransferFragment
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var hotspotClient: PCHotspotClient
// 权限请求
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val allGranted = permissions.all { it.value }
if (allGranted) {
startHotspotClient()
} else {
Toast.makeText(this, "需要权限才能使用全部功能", Toast.LENGTH_LONG).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 初始化热点客户端
hotspotClient = PCHotspotClient(this)
// 检查权限
checkPermissions()
// 设置底部导航
setupBottomNavigation()
// 监听连接状态
observeConnectionState()
}
private fun checkPermissions() {
val permissions = mutableListOf<String>()
// 网络权限
permissions.add(Manifest.permission.INTERNET)
permissions.add(Manifest.permission.ACCESS_NETWORK_STATE)
permissions.add(Manifest.permission.ACCESS_WIFI_STATE)
permissions.add(Manifest.permission.CHANGE_WIFI_MULTICAST_STATE)
// 存储权限(Android 10以下)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
// 检查是否已授予权限
val ungrantedPermissions = permissions.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}
if (ungrantedPermissions.isNotEmpty()) {
requestPermissionLauncher.launch(ungrantedPermissions.toTypedArray())
} else {
startHotspotClient()
}
}
private fun startHotspotClient() {
lifecycleScope.launch {
// 延迟启动,等待UI加载完成
kotlinx.coroutines.delay(1000)
hotspotClient.start()
}
}
private fun setupBottomNavigation() {
binding.bottomNavigation.setOnItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_connection -> {
replaceFragment(ConnectionFragment.newInstance(hotspotClient))
true
}
R.id.navigation_messages -> {
replaceFragment(MessageFragment.newInstance(hotspotClient))
true
}
R.id.navigation_files -> {
replaceFragment(FileTransferFragment.newInstance(hotspotClient))
true
}
else -> false
}
}
// 默认显示连接页面
replaceFragment(ConnectionFragment.newInstance(hotspotClient))
}
private fun replaceFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit()
}
private fun observeConnectionState() {
lifecycleScope.launch {
hotspotClient.connectionState.collect { state ->
when (state) {
PCHotspotClient.ConnectionState.DISCONNECTED -> {
updateConnectionStatus("未连接", R.color.red)
}
PCHotspotClient.ConnectionState.DISCOVERING -> {
updateConnectionStatus("正在发现PC...", R.color.orange)
}
PCHotspotClient.ConnectionState.CONNECTING -> {
updateConnectionStatus("正在连接...", R.color.orange)
}
PCHotspotClient.ConnectionState.CONNECTED -> {
updateConnectionStatus("已连接", R.color.green)
}
PCHotspotClient.ConnectionState.TRANSFERRING -> {
updateConnectionStatus("传输中...", R.color.blue)
}
PCHotspotClient.ConnectionState.ERROR -> {
updateConnectionStatus("连接错误", R.color.red)
}
}
}
}
}
private fun updateConnectionStatus(text: String, colorRes: Int) {
binding.connectionStatus.text = text
binding.connectionStatus.setTextColor(ContextCompat.getColor(this, colorRes))
}
override fun onDestroy() {
super.onDestroy()
hotspotClient.disconnect()
}
}
5.2.4 UI Fragment实现
kotlin
// ConnectionFragment.kt - 连接状态界面
package com.example.hotspotclient.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.example.hotspotclient.databinding.FragmentConnectionBinding
import com.example.hotspotclient.network.PCHotspotClient
import kotlinx.coroutines.flow.collect
class ConnectionFragment : Fragment() {
private var _binding: FragmentConnectionBinding? = null
private val binding get() = _binding!!
private lateinit var hotspotClient: PCHotspotClient
companion object {
fun newInstance(client: PCHotspotClient): ConnectionFragment {
val fragment = ConnectionFragment()
fragment.hotspotClient = client
return fragment
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentConnectionBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupUI()
observeConnectionState()
}
private fun setupUI() {
binding.btnReconnect.setOnClickListener {
hotspotClient.start()
}
binding.btnDisconnect.setOnClickListener {
hotspotClient.disconnect()
}
binding.btnTestConnection.setOnClickListener {
// 发送测试消息
hotspotClient.sendTextMessage("测试连接消息")
}
}
private fun observeConnectionState() {
lifecycleScope.launch {
hotspotClient.connectionState.collect { state ->
updateConnectionInfo(state)
}
}
}
private fun updateConnectionInfo(state: PCHotspotClient.ConnectionState) {
when (state) {
PCHotspotClient.ConnectionState.DISCONNECTED -> {
binding.tvConnectionStatus.text = "未连接"
binding.tvConnectionStatus.setTextColor(resources.getColor(android.R.color.holo_red_dark, null))
binding.btnReconnect.isEnabled = true
binding.btnDisconnect.isEnabled = false
}
PCHotspotClient.ConnectionState.DISCOVERING -> {
binding.tvConnectionStatus.text = "正在搜索PC..."
binding.tvConnectionStatus.setTextColor(resources.getColor(android.R.color.holo_orange_dark, null))
binding.btnReconnect.isEnabled = false
binding.btnDisconnect.isEnabled = true
}
PCHotspotClient.ConnectionState.CONNECTING -> {
binding.tvConnectionStatus.text = "正在连接..."
binding.tvConnectionStatus.setTextColor(resources.getColor(android.R.color.holo_orange_dark, null))
binding.btnReconnect.isEnabled = false
binding.btnDisconnect.isEnabled = true
}
PCHotspotClient.ConnectionState.CONNECTED -> {
binding.tvConnectionStatus.text = "已连接"
binding.tvConnectionStatus.setTextColor(resources.getColor(android.R.color.holo_green_dark, null))
binding.btnReconnect.isEnabled = false
binding.btnDisconnect.isEnabled = true
}
PCHotspotClient.ConnectionState.TRANSFERRING -> {
binding.tvConnectionStatus.text = "传输中..."
binding.tvConnectionStatus.setTextColor(resources.getColor(android.R.color.holo_blue_dark, null))
binding.btnReconnect.isEnabled = false
binding.btnDisconnect.isEnabled = false
}
PCHotspotClient.ConnectionState.ERROR -> {
binding.tvConnectionStatus.text = "连接错误"
binding.tvConnectionStatus.setTextColor(resources.getColor(android.R.color.holo_red_dark, null))
binding.btnReconnect.isEnabled = true
binding.btnDisconnect.isEnabled = false
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
5.3 Gradle构建配置
gradle
// app/build.gradle.kts
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.example.hotspotclient"
compileSdk = 34
defaultConfig {
applicationId = "com.example.hotspotclient"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
viewBinding = true
}
}
dependencies {
// 核心依赖
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.10.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
// 生命周期
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
// 导航组件
implementation("androidx.navigation:navigation-fragment-ktx:2.7.4")
implementation("androidx.navigation:navigation-ui-ktx:2.7.4")
// 协程
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
// 网络
implementation("com.squareup.okhttp3:okhttp:4.12.0")
// JSON解析
implementation("com.google.code.gson:gson:2.10.1")
// 权限请求
implementation("com.guolindev.permissionx:permissionx:1.7.1")
// 测试
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
六、部署与测试步骤
6.1 PC端部署详细步骤
6.1.1 Windows平台部署
-
环境准备
bash# 检查Visual Studio安装 # 确保已安装"C++桌面开发"工作负载 # 安装CMake(可选) choco install cmake --installargs 'ADD_CMAKE_TO_PATH=System' -
编译项目
bash# 方法1:使用Visual Studio IDE # 1. 打开Visual Studio # 2. 选择"打开项目或解决方案" # 3. 选择CMakeLists.txt文件 # 4. 选择目标平台(x64 Debug/Release) # 5. 点击"生成" -> "生成解决方案" # 方法2:使用CMake命令行 mkdir build cd build cmake .. -G "Visual Studio 17 2022" -A x64 cmake --build . --config Release -
防火墙配置
powershell# 以管理员身份运行PowerShell # 添加入站规则(UDP) New-NetFirewallRule -DisplayName "Hotspot UDP Discover" ` -Direction Inbound ` -Protocol UDP ` -LocalPort 9999 ` -Action Allow # 添加入站规则(TCP) New-NetFirewallRule -DisplayName "Hotspot TCP Service" ` -Direction Inbound ` -Protocol TCP ` -LocalPort 8888 ` -Action Allow -
创建启动脚本
batch@echo off REM start_server.bat echo 正在启动PC热点通信服务器... echo. echo 请确保已执行以下操作: echo 1. 手动开启PC热点 echo 2. 确认热点IP是否为192.168.137.1 echo 3. 手机已连接该热点 echo. pause REM 以管理员身份运行(需要处理UDP广播) if not "%1"=="admin" ( powershell -Command "Start-Process '%0' -Verb RunAs -ArgumentList 'admin'" exit /b ) cd /d "%~dp0" bin\pc_hotspot_server.exe pause
6.1.2 Linux平台部署
bash
#!/bin/bash
# install_server.sh
# 安装依赖
sudo apt-get update
sudo apt-get install -y build-essential cmake libssl-dev
# 编译
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
# 创建服务用户
sudo useradd -r -s /bin/false hotspot_server
# 安装到系统
sudo cp pc_hotspot_server /usr/local/bin/
sudo mkdir -p /etc/hotspot_server
sudo cp ../config.ini /etc/hotspot_server/
sudo chown -R hotspot_server:hotspot_server /etc/hotspot_server
# 创建systemd服务
cat << EOF | sudo tee /etc/systemd/system/hotspot-server.service
[Unit]
Description=PC Hotspot Communication Server
After=network.target
[Service]
Type=simple
User=hotspot_server
Group=hotspot_server
WorkingDirectory=/etc/hotspot_server
ExecStart=/usr/local/bin/pc_hotspot_server
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# 启动服务
sudo systemctl daemon-reload
sudo systemctl enable hotspot-server
sudo systemctl start hotspot-server
echo "安装完成!服务已启动"
echo "查看状态: sudo systemctl status hotspot-server"
echo "查看日志: sudo journalctl -u hotspot-server -f"
6.2 手机端部署详细步骤
6.2.1 编译和安装
-
导入项目到Android Studio
- 打开Android Studio
- 选择"Open" -> 选择项目目录
- 等待Gradle同步完成
-
配置签名
gradle// app/build.gradle.kts signingConfigs { create("release") { storeFile = file("hotspotclient.jks") storePassword = "your_password" keyAlias = "hotspotclient" keyPassword = "your_password" } } buildTypes { release { signingConfig = signingConfigs.getByName("release") // ... } } -
生成APK
- 选择Build -> Generate Signed Bundle / APK
- 选择APK
- 配置签名信息
- 选择Build Variant: release
- 点击Finish
6.2.2 无线调试安装(无需USB)
bash
# 1. 确保PC和手机在同一网络
# 2. 启用手机开发者选项和无线调试
# 3. 获取手机IP和端口
# 4. 使用adb无线连接
adb connect 192.168.1.100:5555
adb install app-release.apk
6.3 完整测试流程
6.3.1 第一阶段:基础连接测试
-
PC端操作
bash# 开启热点 # Windows: 设置 -> 网络和Internet -> 移动热点 -> 开 # 记录热点名称和密码 # 运行服务器 ./start_server.bat # 确认输出 [INFO] UDP广播监听已启动,端口:9999 [INFO] TCP服务端已启动,监听地址:192.168.137.1:8888 -
手机端操作
-
连接PC热点
-
打开应用
-
观察连接状态变化:
未连接 -> 正在发现PC... -> 正在连接... -> 已连接 -
点击"测试连接"按钮
-
-
预期结果
- PC控制台显示收到连接和消息
- 手机显示连接成功状态
- 测试消息正常收发
6.3.2 第二阶段:稳定性测试
-
断线重连测试
- 手机端关闭WiFi,等待5秒后重新打开
- 观察自动重连功能
- 预期:30秒内自动恢复连接
-
心跳测试
- 保持连接5分钟
- 使用Wireshark抓包验证心跳包
- 预期:每5秒发送一次PING,立即收到PONG回复
-
压力测试
bash# 使用多台手机同时连接 # 测试最大连接数限制 # 发送大量小消息(每秒10条)
6.3.3 第三阶段:功能测试
-
文件传输测试
- 选择1MB图片文件
- 点击发送
- 观察进度条和传输速度
- 验证文件完整性(MD5校验)
-
大文件测试
- 选择100MB视频文件
- 测试断点续传(如传输中断后恢复)
- 验证传输速度和内存使用
-
多任务测试
- 同时进行文件传输和消息聊天
- 测试UI响应性
- 验证数据不混乱
七、常见问题与解决方案
7.1 连接问题排查表
| 问题现象 | 可能原因 | 解决方案 | 验证方法 |
|---|---|---|---|
| UDP发现失败 | 1. 防火墙拦截 2. 未开启热点 3. 端口被占用 | 1. 检查防火墙规则 2. 确认热点已开启 3. 更换UDP端口 | `netstat -ano |
| TCP连接超时 | 1. IP地址错误 2. 端口错误 3. PC未监听 | 1. 确认热点IP 2. 检查TCP端口 3. 验证PC服务运行 | telnet 192.168.137.1 8888 |
| 频繁断开重连 | 1. 信号弱 2. 心跳超时 3. 系统休眠 | 1. 靠近PC 2. 调整心跳参数 3. 保持唤醒锁 | 监控信号强度RSSI |
| 传输速度慢 | 1. 网络干扰 2. 缓冲区小 3. 加密开销 | 1. 更换WiFi信道 2. 调整缓冲区 3. 禁用加密测试 | iperf网络测速 |
| 内存泄漏 | 1. 资源未释放 2. 循环引用 3. 大文件缓存 | 1. 完善析构函数 2. 使用弱引用 3. 流式传输 | Android Profiler |
7.2 高级调试技巧
7.2.1 PC端调试
cpp
// 启用详细日志
#define DEBUG_NETWORK 1
#ifdef DEBUG_NETWORK
#define NET_LOG(fmt, ...) printf("[NET] " fmt "\n", ##__VA_ARGS__)
#define NET_HEX_DUMP(data, len) hexDump(data, len)
#else
#define NET_LOG(fmt, ...)
#define NET_HEX_DUMP(data, len)
#endif
void hexDump(const void* data, size_t size) {
const unsigned char* bytes = (const unsigned char*)data;
for (size_t i = 0; i < size; i += 16) {
printf("%06zx: ", i);
for (size_t j = 0; j < 16; j++) {
if (i + j < size) {
printf("%02x ", bytes[i + j]);
} else {
printf(" ");
}
if (j == 7) printf(" ");
}
printf(" |");
for (size_t j = 0; j < 16 && i + j < size; j++) {
unsigned char c = bytes[i + j];
printf("%c", (c >= 32 && c < 127) ? c : '.');
}
printf("|\n");
}
}
7.2.2 Android端调试
kotlin
// 网络调试工具类
object NetworkDebugger {
private const val DEBUG = BuildConfig.DEBUG
fun logSocketInfo(socket: Socket) {
if (!DEBUG) return
Log.d("NetworkDebug", """
Socket信息:
Local: ${socket.localAddress}:${socket.localPort}
Remote: ${socket.inetAddress}:${socket.port}
Connected: ${socket.isConnected}
Closed: ${socket.isClosed}
Timeout: ${socket.soTimeout}
KeepAlive: ${socket.keepAlive}
TCP_NODELAY: ${socket.tcpNoDelay}
""".trimIndent())
}
fun logNetworkInfo(context: Context) {
val connectivityManager = context.getSystemService(
Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = connectivityManager.activeNetwork
val caps = connectivityManager.getNetworkCapabilities(network)
Log.d("NetworkDebug", """
网络信息:
有网络: ${caps != null}
WiFi: ${caps?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)}
移动网络: ${caps?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)}
带宽: ${caps?.linkDownstreamBandwidthKbps}Kbps
""".trimIndent())
}
fun startPacketCapture(context: Context) {
// 使用tcpdump或PCAPdroid进行抓包
// 需要root权限或VPNService
Log.d("NetworkDebug", "开始抓包,保存到: ${context.filesDir}/capture.pcap")
}
}
7.3 性能优化建议
-
网络层优化
cpp// 使用SO_RCVBUF和SO_SNDBUF调整缓冲区 int bufSize = 256 * 1024; // 256KB setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char*)&bufSize, sizeof(bufSize)); setsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char*)&bufSize, sizeof(bufSize)); // 禁用Nagle算法(减少延迟) int noDelay = 1; setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char*)&noDelay, sizeof(noDelay)); // 启用TCP快速打开(TCP Fast Open) #ifdef TCP_FASTOPEN int qlen = 5; setsockopt(socket, IPPROTO_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen)); #endif -
Android端优化
kotlin// 使用连接池复用连接 object ConnectionPool { private val pool = ConcurrentHashMap<String, Socket>() fun getConnection(host: String, port: Int): Socket { return pool.getOrPut("$host:$port") { Socket(host, port).apply { keepAlive = true soTimeout = 30000 } } } } // 使用OkHttp替代原生Socket(更好的连接管理) val okHttpClient = OkHttpClient.Builder() .connectTimeout(5, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .connectionPool(ConnectionPool(5, 5, TimeUnit.MINUTES)) .build() -
内存优化
cpp// 使用内存池减少碎片 class MemoryPool { private: struct Block { void* data; size_t size; bool used; }; std::vector<Block> blocks; public: void* allocate(size_t size) { // 查找空闲块或分配新块 // ... } void deallocate(void* ptr) { // 标记块为空闲 // ... } };
八、核心优化点(可选)
8.1 协议优化
8.1.1 二进制协议设计
cpp
// protocol.h - 优化的二进制协议
#pragma pack(push, 1)
struct MessageHeader {
uint32_t magic; // 魔术字 0x48505354 "HPST"
uint16_t version; // 协议版本
uint8_t type; // 消息类型
uint32_t length; // 数据长度
uint32_t checksum; // 校验和
uint64_t timestamp; // 时间戳
uint32_t sequence; // 序列号
};
enum MessageType {
MSG_DISCOVER_REQUEST = 1,
MSG_DISCOVER_RESPONSE,
MSG_HEARTBEAT,
MSG_TEXT,
MSG_FILE_INFO,
MSG_FILE_DATA,
MSG_FILE_END,
MSG_COMMAND,
MSG_ACK,
MSG_NACK
};
struct DiscoverRequest {
char device_name[32];
char device_type[16];
char os_version[16];
char app_version[16];
};
struct DiscoverResponse {
uint32_t ip_address;
uint16_t tcp_port;
char server_name[32];
};
struct FileInfo {
char file_name[256];
uint64_t file_size;
uint32_t chunk_size;
char md5[32];
};
#pragma pack(pop)
// 消息序列化/反序列化
class MessageSerializer {
public:
static std::vector<uint8_t> serialize(const MessageHeader& header,
const void* data = nullptr) {
std::vector<uint8_t> buffer(sizeof(MessageHeader));
memcpy(buffer.data(), &header, sizeof(header));
if (data && header.length > 0) {
size_t oldSize = buffer.size();
buffer.resize(oldSize + header.length);
memcpy(buffer.data() + oldSize, data, header.length);
}
// 计算校验和
uint32_t checksum = calculateChecksum(buffer.data() + 4,
buffer.size() - 4);
((MessageHeader*)buffer.data())->checksum = checksum;
return buffer;
}
static bool deserialize(const uint8_t* data, size_t length,
MessageHeader& header, std::vector<uint8_t>& payload) {
if (length < sizeof(MessageHeader)) return false;
memcpy(&header, data, sizeof(header));
// 验证魔术字
if (header.magic != 0x48505354) return false;
// 验证长度
if (length != sizeof(MessageHeader) + header.length) return false;
// 验证校验和
uint32_t calculated = calculateChecksum(data + 4, length - 4);
if (calculated != header.checksum) return false;
// 提取负载
if (header.length > 0) {
payload.resize(header.length);
memcpy(payload.data(), data + sizeof(header), header.length);
}
return true;
}
private:
static uint32_t calculateChecksum(const uint8_t* data, size_t length) {
uint32_t sum = 0;
for (size_t i = 0; i < length; i++) {
sum = (sum << 5) - sum + data[i];
}
return sum;
}
};
8.1.2 Protobuf协议定义
protobuf
// hotspot.proto
syntax = "proto3";
package hotspot;
message DeviceInfo {
string device_name = 1;
string device_type = 2;
string os_version = 3;
string app_version = 4;
string device_id = 5;
}
message DiscoverRequest {
DeviceInfo device = 1;
uint32 timestamp = 2;
bytes nonce = 3;
}
message DiscoverResponse {
string server_name = 1;
string server_version = 2;
uint32 ip_address = 3;
uint32 tcp_port = 4;
uint32 udp_port = 5;
repeated Service services = 6;
}
message Service {
string name = 1;
uint32 port = 2;
string description = 3;
}
message TextMessage {
string sender = 1;
string content = 2;
uint64 timestamp = 3;
string message_id = 4;
}
message FileTransfer {
string file_name = 1;
uint64 file_size = 2;
string file_hash = 3;
uint32 chunk_size = 4;
uint32 total_chunks = 5;
}
message FileChunk {
string transfer_id = 1;
uint32 chunk_index = 2;
bytes data = 3;
bool is_last = 4;
}
message Heartbeat {
uint64 timestamp = 1;
uint32 sequence = 2;
DeviceStatus status = 3;
}
message DeviceStatus {
float battery_level = 1;
NetworkInfo network = 2;
MemoryInfo memory = 3;
}
message NetworkInfo {
string ssid = 1;
int32 rssi = 2;
string ip_address = 3;
}
message MemoryInfo {
uint64 total_memory = 1;
uint64 free_memory = 2;
}
message HotspotMessage {
oneof payload {
DiscoverRequest discover_req = 1;
DiscoverResponse discover_resp = 2;
TextMessage text_msg = 3;
FileTransfer file_transfer = 4;
FileChunk file_chunk = 5;
Heartbeat heartbeat = 6;
}
}
8.2 安全增强
8.2.1 TLS加密通信
cpp
// tls_wrapper.h - TLS/SSL封装
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
class TLSSocket {
public:
TLSSocket() : ctx(nullptr), ssl(nullptr) {
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
}
~TLSSocket() {
if (ssl) {
SSL_shutdown(ssl);
SSL_free(ssl);
}
if (ctx) {
SSL_CTX_free(ctx);
}
}
bool initServer(const std::string& certFile,
const std::string& keyFile) {
ctx = SSL_CTX_new(TLS_server_method());
if (!ctx) return false;
// 加载证书和私钥
if (SSL_CTX_use_certificate_file(ctx, certFile.c_str(),
SSL_FILETYPE_PEM) <= 0) {
return false;
}
if (SSL_CTX_use_PrivateKey_file(ctx, keyFile.c_str(),
SSL_FILETYPE_PEM) <= 0) {
return false;
}
// 验证私钥
if (!SSL_CTX_check_private_key(ctx)) {
return false;
}
return true;
}
bool accept(SOCKET clientSocket) {
ssl = SSL_new(ctx);
if (!ssl) return false;
SSL_set_fd(ssl, clientSocket);
return SSL_accept(ssl) == 1;
}
int send(const void* buffer, size_t length) {
return SSL_write(ssl, buffer, length);
}
int receive(void* buffer, size_t length) {
return SSL_read(ssl, buffer, length);
}
private:
SSL_CTX* ctx;
SSL* ssl;
};
#endif
8.2.2 端到端加密
kotlin
// EndToEndEncryption.kt
object EndToEndEncryption {
// 使用ECDH进行密钥交换
fun generateKeyPair(): KeyPair {
val keyPairGenerator = KeyPairGenerator.getInstance("EC")
keyPairGenerator.initialize(256)
return keyPairGenerator.generateKeyPair()
}
fun generateSharedSecret(privateKey: PrivateKey,
publicKey: PublicKey): ByteArray {
val keyAgreement = KeyAgreement.getInstance("ECDH")
keyAgreement.init(privateKey)
keyAgreement.doPhase(publicKey, true)
return keyAgreement.generateSecret()
}
// 使用AES-GCM加密
fun encrypt(data: ByteArray, key: ByteArray): ByteArray {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val secretKey = SecretKeySpec(key, "AES")
val iv = ByteArray(12).apply {
SecureRandom().nextBytes(this)
}
val parameterSpec = GCMParameterSpec(128, iv)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec)
val cipherText = cipher.doFinal(data)
return iv + cipherText
}
fun decrypt(encrypted: ByteArray, key: ByteArray): ByteArray {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val secretKey = SecretKeySpec(key, "AES")
val iv = encrypted.copyOfRange(0, 12)
val cipherText = encrypted.copyOfRange(12, encrypted.size)
val parameterSpec = GCMParameterSpec(128, iv)
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec)
return cipher.doFinal(cipherText)
}
// 数字签名
fun sign(data: ByteArray, privateKey: PrivateKey): ByteArray {
val signature = Signature.getInstance("SHA256withECDSA")
signature.initSign(privateKey)
signature.update(data)
return signature.sign()
}
fun verify(data: ByteArray, signature: ByteArray,
publicKey: PublicKey): Boolean {
val verifier = Signature.getInstance("SHA256withECDSA")
verifier.initVerify(publicKey)
verifier.update(data)
return verifier.verify(signature)
}
}
8.3 多平台支持
8.3.1 跨平台网络库封装
cpp
// platform_network.h
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
typedef SOCKET socket_t;
#define INVALID_SOCKET_VALUE INVALID_SOCKET
#define SOCKET_ERROR_VALUE SOCKET_ERROR
inline void close_socket(socket_t sock) {
closesocket(sock);
}
inline int get_last_error() {
return WSAGetLastError();
}
inline void init_network() {
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
}
inline void cleanup_network() {
WSACleanup();
}
#else // Linux/macOS
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
typedef int socket_t;
#define INVALID_SOCKET_VALUE (-1)
#define SOCKET_ERROR_VALUE (-1)
inline void close_socket(socket_t sock) {
close(sock);
}
inline int get_last_error() {
return errno;
}
inline void init_network() {
// Linux/macOS不需要特殊初始化
}
inline void cleanup_network() {
// Linux/macOS不需要特殊清理
}
#endif
// 统一的Socket类
class PlatformSocket {
public:
PlatformSocket() : sock(INVALID_SOCKET_VALUE) {
init_network();
}
~PlatformSocket() {
close();
}
bool create(int domain, int type, int protocol) {
sock = socket(domain, type, protocol);
return sock != INVALID_SOCKET_VALUE;
}
bool bind(const std::string& ip, uint16_t port) {
sockaddr_in addr{};
addr.sin_family = AF_INET;
inet_pton(AF_INET, ip.c_str(), &addr.sin_addr);
addr.sin_port = htons(port);
return ::bind(sock, (sockaddr*)&addr, sizeof(addr)) != SOCKET_ERROR_VALUE;
}
bool connect(const std::string& ip, uint16_t port) {
sockaddr_in addr{};
addr.sin_family = AF_INET;
inet_pton(AF_INET, ip.c_str(), &addr.sin_addr);
addr.sin_port = htons(port);
return ::connect(sock, (sockaddr*)&addr, sizeof(addr)) != SOCKET_ERROR_VALUE;
}
void close() {
if (sock != INVALID_SOCKET_VALUE) {
close_socket(sock);
sock = INVALID_SOCKET_VALUE;
}
}
private:
socket_t sock;
};
九、总结
本文详细介绍了基于PC热点的手动开启方案,结合UDP广播自动发现和TCP可靠通信的完整实现。通过本文的指导,读者可以:
9.1 核心技术要点总结
-
架构设计:采用分层架构,分离网络层、协议层和应用层,提高代码可维护性。
-
自动发现机制:UDP广播+TCP连接的组合,解决了设备自动发现的难题。
-
可靠性保障:心跳机制、自动重连、超时处理等多重保障措施。
-
性能优化:缓冲区管理、连接复用、协议优化等提升传输效率。
-
安全考虑:支持TLS加密、端到端加密、数字签名等安全特性。
-
跨平台支持:通过抽象层实现Windows、Linux、macOS多平台支持。
9.2 实际应用价值
-
物联网设备通信:适用于智能家居、工业控制等场景。
-
移动开发调试:开发者无需配置网络即可调试设备。
-
文件快速传输:替代蓝牙,提供更快的文件传输速度。
-
游戏联机:局域网游戏的多设备通信。
-
教育演示:课堂教学中的设备互联演示。
9.3 未来扩展方向
-
P2P通信:引入NAT穿透技术,支持外网直连。
-
多播优化:使用组播替代广播,减少网络负担。
-
QUIC协议:采用HTTP/3的QUIC协议,提高连接建立速度。
-
WebRTC集成:支持浏览器端的直接通信。
-
AI优化:使用机器学习预测网络状况,动态调整参数。
9.4 代码获取与贡献
本文完整代码已开源,欢迎Star和贡献:
- GitHub仓库:https://github.com/example/pc-hotspot-communication
- 问题反馈:使用GitHub Issues
- 贡献指南:参考CONTRIBUTING.md
9.5 致谢
感谢所有为本文提供反馈和建议的开发者。特别感谢CSDN社区的技术支持,以及开源社区提供的优秀工具和库。
版权声明:本文采用CC BY-SA 4.0协议,转载请注明出处。
作者 :通信技术开发者
日期 :2025-12-16
版本 :v1.0
联系方式:example@email.com
扩展阅读:
相关项目:
希望本文对您的项目开发有所帮助,欢迎在评论区交流讨论!