一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情。
目标
实现一个服务端,接受来自任意客户端的连接。并维护该连接。
对于连接传入的数据,都将其转发到所有的其他连接上。
想法
通过简单的多路复用技术实现相关的操作。避免线程的开销,以及线程带来的通信问题。由单一线程处理所有的连接。
select 是内核为我们实现好的最基础的轮询工具。
其效果相当于不断询问遍历询问每个socket是否准备好,以及是否有异常等事件发生。
一些可能的问题
TCP 连接被关闭的处理
- 自行关闭
- 不可抗力的关闭
实现
依据上面的逻辑,实现主要逻辑如下:
主要逻辑:
while (true) {
select(setLength, &fds, nullptr, nullptr, nullptr);
if (FD_ISSET(server, &fds)) { // 1
//2
} else { // 3
// 4
// 5
}
- 如果 server 准备好了那么就读取一个 socket
- 如果是监听用的 socket 的事件,获取一个新的连接
- 其他的 socket 的事件
- 主要处理三种事件,获得的消息,socket正常断开连接,强制断开连接
- 对于接受的数据,转发到处来源的所有socket,也就是这里有最多的可能发生的情况,尤其是上面说到的问题处理异常或者正常关闭的客户端会在recv调用中得到-1或者0的则断开连接,并移除集合,否则处理来自它的消息,转发到除他自身以外的客户端
更具体的一些内容:
对于整个连接数组,每次监听前重新生成:
FD_ZERO(&fds);
int setLength = 1;
FD_SET(server, &fds);
for (const auto &item: clients) {
cout << item << ' ';
if (item > 0) {
FD_SET(item, &fds);
setLength++;
}
}
cout << endl;
处理socket
auto read = recv(client, buf.data(), (int) buf.max_size(), 0);
if (read == 0 or read == -1) { // 连接已经被关闭
//
closesocket(client);
client = 0;// 标记
} else {
// 处理得到的数据
for (const auto &peer: clients) {
if (peer != 0 and peer != client) {
// 转发
}
}