Ubuntu 20.04下C++ RFCOMM蓝牙监听socket未监听问题排查
问题描述
在Ubuntu 20.04系统上使用VSCode编写C++代码,实现可接收蓝牙连接请求的RFCOMM监听程序。已调用listen函数且返回成功,但通过getsockopt的SO_ACCEPTCONN选项检测时,显示socket未处于监听状态。已确认蓝牙适配器正常、本地MAC地址正确,寻求问题原因及解决办法。
代码实现
#include <iostream> #include <bluetooth/bluetooth.h> #include <bluetooth/rfcomm.h> #include <unistd.h> #include <cstring> #include <cstdio> using namespace std; int main() { struct sockaddr_rc loc_addr = { 0 }, rem_addr = { 0 }; char buf[1024] = { 0 }; int s; int client, bytes_read; socklen_t opt = sizeof(rem_addr); s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); loc_addr.rc_family = AF_BLUETOOTH; str2ba("12:34:56:78:90:12", &loc_addr.rc_bdaddr); loc_addr.rc_channel = (uint8_t) 1; if (s == -1) { perror("socket"); return 1; } if (bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr)) == -1) { perror("bind"); return 1; } if (listen(s, 3) == -1) { perror("listen"); return 1; } else { cout << "listening" << endl; } struct sockaddr_rc name = { 0 }; socklen_t len = sizeof(name); if (getsockname(s, (struct sockaddr *)&name, &len) == -1) { perror("getsockname"); return 1; } char addr[18] = { 0 }; ba2str(&name.rc_bdaddr, addr); cout << "Local address: " << addr << endl; cout << "Local port: " << (int)name.rc_channel << endl; // 第二次调用getsockname,未检查返回值 getsockname(s, (struct sockaddr *)&name, &opt); ba2str( &name.rc_bdaddr, buf ); cout << "local mac adress: " << buf << endl; // 检测监听状态 int val; socklen_t len2 = sizeof(val); if (getsockopt(s, SOL_SOCKET, SO_ACCEPTCONN, &val, &len2) == -1) { perror("getsockopt"); return 1; } else if(val) { printf("getsockopt() succeeded: val=%d, len2=%d\n", val, len2); } else if(getsockopt(s, SOL_SOCKET, SO_ACCEPTCONN, &val, &len2) == 0) { printf("socket %d is not listening\n", s); } opt = sizeof(rem_addr); client = accept(s, (struct sockaddr *)&rem_addr, &opt); ba2str( &rem_addr.rc_bdaddr, buf ); if(client < 0){ cout << "error accepting connection" << endl; } else { cout << "accepted connection from " << buf << endl; } memset(buf, 0, sizeof(buf)); // 读取客户端数据 bytes_read = read(client, buf, sizeof(buf)); if( bytes_read > 0 ) { printf("received [%s]\n", buf); } // 关闭连接 close(client); close(s); return 0; }
程序运行输出
listening Local address: 12:34:56:78:90:12 Local port: 1 local mac adress: 12:34:56:78:90:12 socket 3 is not listening
问题原因分析
SO_ACCEPTCONN选项对RFCOMM套接字的兼容性问题SO_ACCEPTCONN是POSIX标准中针对TCP套接字设计的选项,用于检测套接字是否处于监听状态。但蓝牙RFCOMM套接字属于AF_BLUETOOTH地址族,部分Linux系统的蓝牙套接字实现并未支持该选项,导致即使listen调用成功,getsockopt返回的val仍为0,出现误判。代码逻辑冗余且错误
在监听状态检测部分,当第一次getsockopt返回val=0时,重复调用getsockopt属于冗余操作,且逻辑分支设计混乱。实际上只需一次调用即可判断状态,重复调用不会改变结果。未检查第二次
getsockname的返回值
代码中第二次调用getsockname时未校验返回结果,若该调用失败可能导致变量异常,但这并非监听状态误判的直接原因。
解决方法
忽略
SO_ACCEPTCONN的检测结果,以listen返回值为准
只要listen函数返回成功(未触发perror("listen")),即可认定套接字已进入监听状态,无需依赖SO_ACCEPTCONN的检测结果。使用系统工具验证监听状态
可以通过ss命令查看蓝牙端口的监听状态,确认RFCOMM端口是否正常监听:ss -a --bluetooth若输出中包含
LISTEN状态的rfcomm端口,说明监听已成功。修正代码逻辑
简化监听状态检测的代码,移除冗余的getsockopt调用:int val; socklen_t len2 = sizeof(val); if (getsockopt(s, SOL_SOCKET, SO_ACCEPTCONN, &val, &len2) == -1) { perror("getsockopt"); return 1; } if (val) { printf("socket %d is listening\n", s); } else { printf("SO_ACCEPTCONN not supported, but listen succeeded\n"); // 此处可直接执行accept逻辑,无需额外判断 }修复代码中的换行符问题
将代码中所有非标准换行符替换为\n,避免输出格式混乱。
内容的提问来源于stack exchange,提问作者MGThegathering

