TCP 聊天室服务器

196 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情

目标

实现一个服务端,接受来自任意客户端的连接。并维护该连接。

对于连接传入的数据,都将其转发到所有的其他连接上。

想法

通过简单的多路复用技术实现相关的操作。避免线程的开销,以及线程带来的通信问题。由单一线程处理所有的连接。

image.png

select 是内核为我们实现好的最基础的轮询工具。

其效果相当于不断询问遍历询问每个socket是否准备好,以及是否有异常等事件发生。

一些可能的问题

TCP 连接被关闭的处理

  • 自行关闭
  • 不可抗力的关闭

实现

依据上面的逻辑,实现主要逻辑如下:

主要逻辑:

    while (true) {

        select(setLength, &fds, nullptr, nullptr, nullptr);

        if (FD_ISSET(server, &fds)) { // 1

   //2

        } else { // 3
      // 4

      // 5

}
  1. 如果 server 准备好了那么就读取一个 socket
  2. 如果是监听用的 socket 的事件,获取一个新的连接
  3. 其他的 socket 的事件
  4. 主要处理三种事件,获得的消息,socket正常断开连接,强制断开连接
  5. 对于接受的数据,转发到处来源的所有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) {
                                // 转发
                            }
                        }