基于TLSSocket的通信能力
场景描述
Socket进行数据传输的能力,支持TLSSocket能力。
场景一 :用户场景需要实现网上银行叫号及其他需要建立即时通信传输数据时,可通过TCP/TLSSocket方式加密传输数据。
场景二 :数据发送过程中 当发送方每次写入数据 < 套接字(Socket)缓冲区大小;接收方读取套接字(Socket)缓冲区数据不够及时,可能存在粘包/黏包情况,需要做对应的接收处理。
方案描述
场景一
用户场景需要实现网上银行叫号及其他需要建立即时通信传输数据时,可通过tcp/tlsSocket方式加密传输数据。
方案
调用网络模块socket中TLSSocket能力建立通信连接,有关能力可参考文档:@ohos.socket.TLSSocket。
主要步骤可参考如下:
1. 引入socket能力,创建一个TLSSocket连接。
2. connect连接绑定服务器 IP 和端口号,bind绑定本地网络ip地址。
3. 双向认证上传客户端 CA 证书及数字证书;单向认证上传客户端 CA 证书。
4. 订阅TLSSocket相关消息事件。
5. 发送数据。
6. TLSSocket连接使用完毕后,主动关闭。
核心代码
//获取本机ip地址信息
function getLocalIp(): string {
const ipInfo = wifiManager.getIpInfo()
console.log(tag + 'ipInfo : ' + JSON.stringify(ipInfo));
const localIp = int2ip(ipInfo.ipAddress)
console.log(tag + 'localHost : ' + localIp);
return localIp;
}
//从wifi获取地址格式转换
function int2ip(n: number): string {
return `${(n & 0xFF000000) >>> 24}.${(n & 0x00FF0000) >>> 16}.${(n & 0x0000FF00) >>> 8}.${(n & 0xFF)}`
}
class SocketInfo {
message: ArrayBuffer = new ArrayBuffer(1);
remoteInfo: socket.SocketRemoteInfo = {} as socket.SocketRemoteInfo;
}
export class TlsSocketTest {
host: string = 'test.com';
port: number = 443;
ca: string[] = [
"-----BEGIN CERTIFICATE-----\n... (certificate data) ...\n-----END CERTIFICATE-----"
];
async connectTest(): Promise<void> {
try {
console.debug(tag + 'connectTest');
// 本地IP
const localIp = getLocalIp();
// 网络是否正常
let net = connection.hasDefaultNetSync();
console.log(tag + 'net : ' + JSON.stringify(net));
console.log(tag + 'host : ' + this.host + ', port : ' + this.port);
// 服务器的域名解析,得到服务器IP
const handler = await connection.getDefaultNet();
const netAddressArray: connection.NetAddress[] = await handler.getAddressesByName(this.host);
console.log(tag + 'netAddressArray : ' + JSON.stringify(netAddressArray));
const serverIp = netAddressArray[0].address;
console.log(tag + 'serverIp : ' + serverIp);
// 创建TLSSocket
const tlsSocket = socket.constructTLSSocketInstance();
let tlsConnectOptions: socket.TLSConnectOptions = {
address: {
address: serverIp,
port: this.port
},
secureOptions: {
ca: this.ca,
}
};
// 绑定本地IP和Port
tlsSocket.bind({ address: localIp, family: 1, port: 0 }, (err:BusinessError) => {
if (err) {
console.log('bind fail');
return;
}
console.log('bind success');
});
tlsSocket.on('message', (msgFromServer: SocketInfo) => {
console.log(tag + 'on(message) : ' + JSON.stringify(msgFromServer));
let uint8Array = new Uint8Array(msgFromServer.message);
let textDecoder = util.TextDecoder.create();
let res = textDecoder.decodeWithStream(uint8Array);
console.log(tag + 'on(message) , res : ' + JSON.stringify(res));
})
// 注册连接成功事件
tlsSocket.on('connect', () => {
console.log(tag + 'on(connect)');
})
// 开始连接服务器
tlsSocket.connect(tlsConnectOptions).then(() => {
console.log(tag + 'tlsSocket connect success');
})
tlsSocket.send("hello zhtest").then(() => {
console.log(tag + 'tlsSocket send success');
})
tlsSocket.close().then(() => {
console.log(tag + 'tlsSocket close success');
})
} catch (err) {
console.log(tag + 'connectTest err : ' + JSON.stringify(err));
}
}
}
场景二
数据发送过程中 当发送方每次写入数据 < 套接字(Socket)缓冲区大小;接收方读取套接字(Socket)缓冲区数据不够及时,可能存在粘包/黏包情况,需要做对应的接收处理。
方案
1. 发送方和接收方规定固定大小的缓冲区,也就是发送和接收都使用固定大小的byte[]数组长度,当字符长度不够时使用空字符弥补。
2. 以特殊的字符结尾,比如以“\n”结尾,这样我们就知道结束字符,从而避免了粘包问题。
核心代码
1. 可根据当前服务端/客户端收发数据的实际情况,调用socket中提供setextraoptions能力设置较合适发送/接收数据的缓存区间大小做处理。
let tls: socket.TLSSocket = socket.constructTLSSocketInstance();
let bindAddr: socket.NetAddress = {
address: '192.168.xx.xxx',
port: 8080
}
tls.bind(bindAddr, (err: BusinessError) => {
if (err) {
console.log('bind fail');
return;
}
console.log('bind success');
});
//额外参数设置
let tcpExtraOptions: socket.TCPExtraOptions = {
receiveBufferSize: 1000, //接收数据的缓存大小
sendBufferSize: 1000, //发送数据的缓存大小
socketTimeout: 15000
}
tls.setExtraOptions(tcpExtraOptions, (err: BusinessError) => {
if (err) {
console.log('setExtraOptions fail');
return;
}
console.log('setExtraOptions success');
});
2. 客户端发送数据时携带特殊符号做区分处理。
// 创建TLSSocket
let tlsSocket = socket.constructTLSSocketInstance();
let tlsConnectOptions: socket.TLSConnectOptions = {
address: {
address: this.serverIp,
port: this.port
},
secureOptions: {}
};
// 绑定本地IP和Port
tlsSocket.bind({ address: localIp, family: 1, port: 0 })
.then(() => {
console.log(tag + 'tlsSocket bind success');
// 注册接收服务器响应事件
tlsSocket.on('message', (msgFromServer: SocketInfo) => {
console.log(tag + 'on(message) : ' + JSON.stringify(msgFromServer));
})
// 注册连接成功事件
tlsSocket.on('connect', () => {
console.log(tag + 'on(connect)');
})
// 开始连接服务器
tlsSocket.connect(tlsConnectOptions)
.then(() => {
console.log(tag + 'tlsSocket connect success');
tlsSocket.send("aaaaaccc \n").then(() => {
console.log(tag + 'msg1 send success');
}).catch((err: BusinessError) => {
console.log(tag + 'msg1 send err : ' + JSON.stringify(err));
});
tlsSocket.send("bbbbbbddd \n").then(() => {
console.log(tag + 'msg2 send success');
}).catch((err: BusinessError) => {
console.log(tag + 'msg2 send err : ' + JSON.stringify(err));
});
})
.catch((err: BusinessError) => {
console.log(tag + 'tlsSocket connect err : ' + JSON.stringify(err));
})
})
.catch((err: BusinessError) => {
console.log(tag + 'tlsSocket bind err : ' + JSON.stringify(err));
})
常见问题
Q:socket调用返回2301115原因及解决方式。
A : 可能是连接参数中超时时间设置短导致,可将timeout参数设置为15s,如不设置默认超时时间为5s。
Q:socket连接时on('error')错误码返回为-1原因。
A:TCP Socket返回-1错误码通常表示连接失败或发送数据失败。
这个错误码可能由:1、当前通信连接被拒绝;2、网络连接失败 3、连接或发送数据时超时导致。
Q:socket中ping命令方式实现情况。
A:为防止泛洪攻击,网络模块暂不直接提供ping能力接口,参考三方库中能力封装处理:https://github.com/dgibson/iputils/blob/master/ping.c。