You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Winsock2的蓝牙OBEX OPP文件发送实现问题咨询

蓝牙OBEX对象推送(OPP)发送图片的Winsock2正确实现

问题说明

需通过Windows的Winsock2实现蓝牙OBEX对象推送(OPP)向手机发送图片,已设置RemoteEndPoint.serviceClassId = OBEXObjectPushServiceClass_UUID;,但当前代码直接调用send发送文件路径,无法完成文件传输,需修正send的调用逻辑,遵循OBEX协议完成文件发送。

核心错误点

  • 错误的发送内容:直接发送文件路径字符串,OBEX协议要求发送符合规范的协议帧,而非纯文本路径
  • 缺少文件读取逻辑:未读取本地图片的二进制内容,无法传输实际文件数据
  • 缺失OBEX协议流程:没有实现OBEX的连接握手、文件元数据传输、分块发送、结束请求等必要步骤

修正后的完整代码

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 
// 蓝牙OBEX文件发送实现
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <string>

#include <WinSock2.h>
#include <bthsdpdef.h>
#include <bluetoothapis.h>
#include <ws2bth.h>

#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "Bthprops.lib")

#define DEFAULT_BUFLEN 1024

// OBEX操作码定义
const uint8_t OBEX_OP_CONNECT = 0x80;
const uint8_t OBEX_OP_PUT = 0x02;
const uint8_t OBEX_OP_PUT_FINAL = 0x82;
const uint8_t OBEX_OP_DISCONNECT = 0x81;

// 构建OBEX连接请求帧
std::vector<uint8_t> BuildObexConnectRequest() {
    std::vector<uint8_t> frame;
    frame.push_back(OBEX_OP_CONNECT); // 操作码
    frame.push_back(0x00); frame.push_back(0x07); // 帧长度(大端)
    frame.push_back(0x10); frame.push_back(0x00); // OBEX版本 1.0
    frame.push_back(0x00); frame.push_back(0x00); // 最大数据包长度
    return frame;
}

// 构建OBEX PUT请求头(包含文件元数据)
std::vector<uint8_t> BuildObexPutHeader(const std::string& fileName, uint64_t fileSize) {
    std::vector<uint8_t> frame;
    frame.push_back(OBEX_OP_PUT); // 操作码(后续分块用0x02,最后一块用0x82)
    // 先预留长度位置,后续填充
    frame.push_back(0x00); frame.push_back(0x00);

    // 添加名称头(0x01)
    uint8_t nameLen = (uint8_t)fileName.size() + 1; // 包含终止符
    frame.push_back(0x01);
    frame.push_back((nameLen >> 8) & 0xFF); frame.push_back(nameLen & 0xFF);
    frame.insert(frame.end(), fileName.begin(), fileName.end());
    frame.push_back(0x00); // 字符串终止符

    // 添加长度头(0xC3)
    frame.push_back(0xC3);
    frame.push_back(0x00); frame.push_back(0x08); // 长度字段占8字节
    for (int i = 7; i >= 0; --i) {
        frame.push_back((fileSize >> (i * 8)) & 0xFF);
    }

    // 计算实际帧长度并填充
    uint16_t frameLen = (uint16_t)frame.size();
    frame[1] = (frameLen >> 8) & 0xFF;
    frame[2] = frameLen & 0xFF;

    return frame;
}

int __cdecl main(int argc, char **argv)
{
    std::cout << "******************************************************\r\n";

    int iResult = 0;
    const char* RemoteMAC = "38:2D:E8:B9:FA:EB";
    BTH_ADDR RemoteBtAddr = 0;
    // 将MAC地址转换为BTH_ADDR
    if (BluetoothConvertStringToAddressA((char*)RemoteMAC, &RemoteBtAddr) != ERROR_SUCCESS) {
        std::cout << "MAC地址转换失败\r\n";
        return 1;
    }

    // 初始化WinSock
    WSADATA WSAData = { 0 };
    WORD wVersionRequested = MAKEWORD(2, 2);
    if ((iResult = WSAStartup(wVersionRequested, &WSAData)) != 0) {
        std::cout << "WSAStartup失败: " << iResult << "\r\n";
        return 1;
    }

    // 创建蓝牙Socket
    SOCKET LocalSocket = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
    if (LocalSocket == INVALID_SOCKET) {
        std::cout << "socket创建失败: " << WSAGetLastError() << "\r\n";
        WSACleanup();
        return 1;
    }

    // 配置远程端点
    SOCKADDR_BTH RemoteEndPoint = {0};
    RemoteEndPoint.addressFamily = AF_BTH;
    RemoteEndPoint.btAddr = RemoteBtAddr;
    RemoteEndPoint.serviceClassId = OBEXObjectPushServiceClass_UUID;
    RemoteEndPoint.port = BT_PORT_ANY; // 让系统自动查找OPP服务端口

    // 连接远程设备
    if ((iResult = connect(LocalSocket, (SOCKADDR*)&RemoteEndPoint, sizeof(RemoteEndPoint))) == SOCKET_ERROR) {
        std::cout << "connect失败: " << WSAGetLastError() << "\r\n";
        closesocket(LocalSocket);
        WSACleanup();
        return 1;
    }
    std::cout << "连接成功\r\n";

    // 发送OBEX连接请求
    auto connectFrame = BuildObexConnectRequest();
    iResult = send(LocalSocket, (const char*)connectFrame.data(), connectFrame.size(), 0);
    if (iResult == SOCKET_ERROR) {
        std::cout << "发送连接请求失败: " << WSAGetLastError() << "\r\n";
        closesocket(LocalSocket);
        WSACleanup();
        return 1;
    }
    std::cout << "连接请求发送成功\r\n";

    // 接收连接响应
    char recvBuf[DEFAULT_BUFLEN] = {0};
    iResult = recv(LocalSocket, recvBuf, DEFAULT_BUFLEN, 0);
    if (iResult == SOCKET_ERROR) {
        std::cout << "接收连接响应失败: " << WSAGetLastError() << "\r\n";
        closesocket(LocalSocket);
        WSACleanup();
        return 1;
    }
    // 检查响应是否成功(操作码最高位为1表示成功响应)
    if ((recvBuf[0] & 0x80) == 0) {
        std::cout << "连接响应失败\r\n";
        closesocket(LocalSocket);
        WSACleanup();
        return 1;
    }
    std::cout << "OBEX连接建立成功\r\n";

    // 准备要发送的文件
    std::string filePath = "D:/OneDrive/016.jpg";
    std::ifstream file(filePath, std::ios::binary | std::ios::ate);
    if (!file.is_open()) {
        std::cout << "文件打开失败\r\n";
        closesocket(LocalSocket);
        WSACleanup();
        return 1;
    }
    uint64_t fileSize = file.tellg();
    file.seekg(0, std::ios::beg);
    std::vector<uint8_t> fileData((size_t)fileSize);
    file.read((char*)fileData.data(), fileSize);
    file.close();

    // 提取文件名
    size_t slashPos = filePath.find_last_of("/\\");
    std::string fileName = (slashPos != std::string::npos) ? filePath.substr(slashPos + 1) : filePath;

    // 发送PUT请求头
    auto putHeader = BuildObexPutHeader(fileName, fileSize);
    iResult = send(LocalSocket, (const char*)putHeader.data(), putHeader.size(), 0);
    if (iResult == SOCKET_ERROR) {
        std::cout << "发送PUT头失败: " << WSAGetLastError() << "\r\n";
        closesocket(LocalSocket);
        WSACleanup();
        return 1;
    }
    std::cout << "PUT头发送成功\r\n";

    // 分块发送文件数据
    size_t bytesSent = 0;
    const size_t chunkSize = DEFAULT_BUFLEN - 3; // 预留OBEX帧头空间
    while (bytesSent < fileSize) {
        size_t currentChunk = std::min(chunkSize, fileSize - bytesSent);
        std::vector<uint8_t> dataFrame;

        // 判断是否是最后一块
        if (bytesSent + currentChunk == fileSize) {
            dataFrame.push_back(OBEX_OP_PUT_FINAL);
        } else {
            dataFrame.push_back(OBEX_OP_PUT);
        }

        // 设置帧长度
        uint16_t frameLen = (uint16_t)(currentChunk + 3);
        dataFrame.push_back((frameLen >> 8) & 0xFF);
        dataFrame.push_back(frameLen & 0xFF);

        // 添加文件数据
        dataFrame.insert(dataFrame.end(), fileData.begin() + bytesSent, fileData.begin() + bytesSent + currentChunk);

        // 发送数据块
        iResult = send(LocalSocket, (const char*)dataFrame.data(), dataFrame.size(), 0);
        if (iResult == SOCKET_ERROR) {
            std::cout << "发送数据块失败: " << WSAGetLastError() << "\r\n";
            closesocket(LocalSocket);
            WSACleanup();
            return 1;
        }

        // 接收响应
        iResult = recv(LocalSocket, recvBuf, DEFAULT_BUFLEN, 0);
        if (iResult == SOCKET_ERROR || (recvBuf[0] & 0x80) == 0) {
            std::cout << "数据块响应失败\r\n";
            closesocket(LocalSocket);
            WSACleanup();
            return 1;
        }

        bytesSent += currentChunk;
        std::cout << "已发送: " << bytesSent << "/" << fileSize << " 字节\r\n";
    }

    // 发送断开连接请求
    std::vector<uint8_t> disconnectFrame;
    disconnectFrame.push_back(OBEX_OP_DISCONNECT);
    disconnectFrame.push_back(0x00); disconnectFrame.push_back(0x03); // 帧长度
    iResult = send(LocalSocket, (const char*)disconnectFrame.data(), disconnectFrame.size(), 0);
    if (iResult == SOCKET_ERROR) {
        std::cout << "发送断开请求失败: " << WSAGetLastError() << "\r\n";
    }

    // 关闭Socket
    closesocket(LocalSocket);
    WSACleanup();

    std::cout << "END: 文件发送完成!\r\n";
    std::getchar();

    return 0;
}

关键修正说明

  • OBEX协议实现:添加了OBEX连接请求、PUT请求头、分块数据发送、断开请求的完整流程,符合OPP协议规范
  • 文件读取:以二进制模式读取图片文件内容,准备好要传输的二进制数据
  • 分块传输:将大文件拆分为多个数据块发送,避免单次发送数据过大
  • 响应处理:每次发送后接收设备的响应,确保传输过程的可靠性
  • MAC地址转换:正确将字符串格式的MAC地址转换为BTH_ADDR类型,避免手动输入地址错误

内容的提问来源于stack exchange,提问作者Stacey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 13:07:03