在D语言中如何从socket获取输入并转换为UTF-8编码字符串
实现步骤
你当前使用的是D语言标准库的套接字API,要实现UTF-8编码的SQL请求字节转字符串,按如下逻辑补全代码即可:
- 第一步:补全
Socket.select后的事件分发逻辑,区分新连接接入、已连接套接字可读两类事件 - 第二步:为每个连接单独维护接收缓冲区,解决TCP流的粘包问题
- 第三步:按提前约定的请求边界规则提取完整请求字节,验证编码合法性后转为字符串
注意:TCP是无消息边界的流协议,你需要提前和客户端约定请求边界规则,常用两种方案:
- 长度前缀方案:每个请求前加2/4字节的大端整数,标识后续SQL请求的字节长度
- 分隔符方案:用客户端不会在SQL中使用的特殊字符(如
\0、;\n)作为请求结束标识
补全后的代码示例
import std.socket; import std.stdio; import std.algorithm; import std.array; import std.utf; int start(){ ushort port = 61888; auto listener = new TcpSocket(); assert(listener.isAlive); listener.blocking = false; listener.bind(new InternetAddress(port)); listener.listen(10); writefln("Listening on port %d.", port); enum MAX_CONNECTIONS = 60; auto socketSet = new SocketSet(MAX_CONNECTIONS + 1); Socket[] reads; // 每个连接独立的接收缓冲区,解决粘包 ubyte[][Socket] recvBuffers; while (true) { socketSet.add(listener); foreach (sock; reads) socketSet.add(sock); // 阻塞等待IO事件 Socket.select(socketSet, null, null); // 处理新连接 if (socketSet.isSet(listener)) { auto newSock = listener.accept(); newSock.blocking = false; if (reads.length < MAX_CONNECTIONS) { reads ~= newSock; recvBuffers[newSock] = []; } socketSet.remove(listener); } // 处理已连接套接字的可读事件 foreach (i, sock; reads) { if (!socketSet.isSet(sock)) continue; ubyte[4096] recvBuf; auto recvLen = sock.receive(recvBuf[]); // 连接关闭或出错 if (recvLen <= 0) { sock.close(); recvBuffers.remove(sock); reads = reads.remove(i); continue; } // 追加新接收的字节到对应连接的缓冲区 recvBuffers[sock] ~= recvBuf[0..recvLen]; // 示例采用【2字节大端长度前缀】的边界规则,你可以根据自己的协议修改 while (recvBuffers[sock].length >= 2) { // 读取前2字节的请求长度 ushort reqByteLen = (cast(ushort)recvBuffers[sock][0] << 8) | recvBuffers[sock][1]; // 缓冲数据不足完整请求,等待下一轮接收 if (recvBuffers[sock].length < 2 + reqByteLen) break; // 提取完整请求字节 auto reqBytes = recvBuffers[sock][2 .. 2 + reqByteLen]; // 剩余字节放回缓冲 recvBuffers[sock] = recvBuffers[sock][2 + reqByteLen .. $]; // 验证UTF-8编码合法性后转字符串 try { validate(cast(char[])reqBytes); string sqlStr = cast(string)reqBytes.idup; // 此处可添加你的SQL处理逻辑 writefln("接收到SQL请求:%s", sqlStr); } catch (UTFException e) { writefln("非法UTF-8编码,断开客户端 %s", sock.remoteAddress.toString()); sock.close(); recvBuffers.remove(sock); reads = reads.remove(i); break; } } } // 清空套接字集合,准备下一轮循环 socketSet.reset(); } return 0; }
边界规则调整说明
如果你采用分隔符方案(比如约定\0为请求结束符),只需要把上述代码中提取完整请求的逻辑替换为如下内容即可:
// 查找分隔符位置 auto delimiterPos = recvBuffers[sock].countUntil(0); while (delimiterPos != -1) { // 提取分隔符前的字节作为完整请求 auto reqBytes = recvBuffers[sock][0 .. delimiterPos]; // 剩余字节跳过分隔符后放回缓冲 recvBuffers[sock] = recvBuffers[sock][delimiterPos + 1 .. $]; // 后续转UTF-8字符串的逻辑和上述一致 // ... // 查找下一个分隔符 delimiterPos = recvBuffers[sock].countUntil(0); }
内容的提问来源于stack exchange,提问作者Tannenfels
相关产品推荐
相关产品推荐

