目录

如何解决Socket bind失败问题

问题现象

  1. 使用TCPSocket\UDPSocket bind失败,报错:
    • 报错场景一:2301013 Permission denied。
    • 报错场景二:2301099 Address not available。
  2. bind绑定指定端口未报错,但是分配到一个随机端口。

背景知识

问题定位

分析结论

修改建议

  1. 报错2301013 Permission denied,建议客户端bind更大的端口,避免使用系统保留端口。
  2. 报错2301099 Address not available,可以使用0.0.0.0(IPv4通配符)或::(IPv6通配符)绑定本地所有地址:
    // socket绑定0.0.0.0作为本机IP地址,参考样例:
    bindAllAddress() {
      let udp: socket.UDPSocket = socket.constructUDPSocketInstance();
      let bindAddr: socket.NetAddress = {
        address: '0.0.0.0', // 本端地址
        port: 1234
      };
      udp.bind(bindAddr)
        .then(() => {
          console.info('bind success');
        })
        .catch((err: BusinessError) => {
          console.error('bind fail', err);
        });
    }

    如果是通过wifiManager.getIpInfo接口获取到的IpInfo.ipAddress地址,需要转换为IP常用格式。

    // 获取WiFi IPV4地址,参考样例:
    getWifiIp() {
      let localAddress = this.resolveIP(wifiManager.getIpInfo().ipAddress) as string;
      console.info(`localAddress: ${localAddress}`);
      console.info(`IP校验结果为: ${this.checkIp(localAddress)}`);
    }
    
    resolveIP(ip: number): string {
      if (ip < 0 || ip > 0xFFFFFFFF) {
        console.info('The number is not normal!');
      }
      return (ip >>> 24) + '.' + (ip >> 16 & 0xFF) + '.' + (ip >> 8 & 0xFF) + '.' + (ip & 0xFF);
    }
    
    checkIp(ip: string): boolean {
      let ipRegex = /^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$/;
      return ipRegex.test(ip);
    }
  3. 对于绑定指定端口失败,由于系统具备自动rebind机制随机分配端口,需要通过getLocalAddress接口自行判断是否绑定指定端口:
    bindWithoutRandomPort(address: socket.NetAddress) {
      let udp: socket.UDPSocket = socket.constructUDPSocketInstance();
      udp.bind(address, (error: BusinessError) => {
        if (error) {
          console.error(`:${JSON.stringify(error)}`);
          return;
        }
        if (!address.port) {
          console.info('应用绑定成功');
          return;
        }
        udp.getLocalAddress().then((localAddress: socket.NetAddress) => {
          console.info(`成功获取UDP Socket地址Address=${JSON.stringify(localAddress)}`);
          if (address.port === localAddress.port) {
            console.info('应用绑定的端口正确');
          } else {
            console.error(`应用重新绑定了另外的端口: ${localAddress.port}`);
            // ...
          }
        }).catch((err: Error) => {
          console.error(`获取UDP Socket地址失败: ${JSON.stringify(err)}`);
        });
      });
    }

完整示例参考如下:

需要在module.json5文件中申请ohos.permission.INTERNET和ohos.permission.GET_WIFI_INFO权限。

import { wifiManager } from '@kit.ConnectivityKit';
import { socket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct SocketBindExample {
  // 获取WiFi IPV4地址,参考样例:
  getWifiIp() {
    let localAddress = this.resolveIP(wifiManager.getIpInfo().ipAddress) as string;
    console.info(`localAddress: ${localAddress}`);
    console.info(`IP校验结果为: ${this.checkIp(localAddress)}`);
  }

  resolveIP(ip: number): string {
    if (ip < 0 || ip > 0xFFFFFFFF) {
      console.info('The number is not normal!');
    }
    return (ip >>> 24) + '.' + (ip >> 16 & 0xFF) + '.' + (ip >> 8 & 0xFF) + '.' + (ip & 0xFF);
  }

  checkIp(ip: string): boolean {
    let ipRegex = /^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$/;
    return ipRegex.test(ip);
  }

  // socket绑定0.0.0.0作为本机IP地址,参考样例:
  bindAllAddress() {
    let udp: socket.UDPSocket = socket.constructUDPSocketInstance();
    let bindAddr: socket.NetAddress = {
      address: '0.0.0.0', // 本端地址
      port: 1234
    };
    udp.bind(bindAddr)
      .then(() => {
        console.info('bind success');
      })
      .catch((err: BusinessError) => {
        console.error('bind fail', err);
      });
  }

  bindWithoutRandomPort(address: socket.NetAddress) {
    let udp: socket.UDPSocket = socket.constructUDPSocketInstance();
    udp.bind(address, (error: BusinessError) => {
      if (error) {
        console.error(`:${JSON.stringify(error)}`);
        return;
      }
      if (!address.port) {
        console.info('应用绑定成功');
        return;
      }
      udp.getLocalAddress().then((localAddress: socket.NetAddress) => {
        console.info(`成功获取UDP Socket地址Address=${JSON.stringify(localAddress)}`);
        if (address.port === localAddress.port) {
          console.info('应用绑定的端口正确');
        } else {
          console.error(`应用重新绑定了另外的端口: ${localAddress.port}`);
          // ...
        }
      }).catch((err: Error) => {
        console.error(`获取UDP Socket地址失败: ${JSON.stringify(err)}`);
      });
    });
  }

  build() {
    Column({ space: 16 }) {
      Button('获取WiFi分配的IP地址').onClick(() => {
        this.getWifiIp();
      });
      Button('通配符绑到所有本地地址').onClick(() => {
        this.bindAllAddress();
      });
      Button('绑定指定端口').onClick(() => {
        this.bindWithoutRandomPort({ address: '::', family: 2, port: 3000 });
      });
    }.width('100%').height('100%').justifyContent(FlexAlign.Center);
  }
}

常见FAQ

Q:通过Socket连接向IP地址为192.168.1.1的路由器发送数据,应如何绑定IP地址?

A:建议绑定本地回环地址127.0.0.1或者0.0.0.0。