2.实现简单的循环通信

文章目录

    • 客户端
    • 服务端
    • 需要修正的地方
        • [1. 服务端没有处理"客户端断开"的逻辑(最严重)](#1. 服务端没有处理“客户端断开”的逻辑(最严重))
        • [2. 客户端发送速度过快(刷屏)](#2. 客户端发送速度过快(刷屏))
        • [3. 客户端没有检查 `read()` 的返回值](#3. 客户端没有检查 read() 的返回值)
      • [✅ 修正后的服务端代码](#✅ 修正后的服务端代码)
      • [✅ 修正后的客户端代码](#✅ 修正后的客户端代码)
      • [📝 修正问题清单](#📝 修正问题清单)

客户端

cpp 复制代码
#include <arpa/inet.h>

#include <cstring>

#include <iostream>

#include <sys/socket.h>

#include <unistd.h>

using namespace std;

  

int main() {

  // 创建 TCP 套接字,返回文件描述符

  int fd = socket(AF_INET, SOCK_STREAM, 0);

  

  // 设置服务器地址结构体,初始化为全 0

  sockaddr_in addr{};

  // 设置地址族为 IPv4

  addr.sin_family = AF_INET;

  // 设置服务器 IP 为本地回环地址

  addr.sin_addr.s_addr = inet_addr("127.0.0.1");

  // 设置服务器端口 8888,转为网络字节序

  addr.sin_port = htons(8888);

  // 连接到服务器

  connect(fd, (sockaddr *)&addr, sizeof(addr));

  

  cout << "Connected to server" << endl;

  

  while (1) {

    // 向服务器发送消息

    const char *msg = "Hello from client";

    write(fd, msg, strlen(msg));

    cout << "Sent: " << msg << endl;

  

    // 读取服务器的回复消息

    char buf[64] = {};

    read(fd, buf, sizeof(buf));

    cout << "Received: " << buf << endl;

  }

  

  // 关闭套接字连接

  close(fd);

  return 0;

}

服务端

cpp 复制代码
#include <arpa/inet.h>

#include <cstring>

#include <iostream>

#include <sys/socket.h>

#include <unistd.h>

using namespace std;

  

int main() {

  // 1. 创建 TCP 套接字

  int fd = socket(AF_INET, SOCK_STREAM, 0);

  if (fd == -1) {

    cerr << "socket() failed" << endl;

    return -1;

  }

  

  // 2. 设置端口复用

  int opt = 1;

  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {

    cerr << "setsockopt() failed" << endl;

    close(fd);

    return -1;

  }

  

  // 3. 绑定地址和端口

  sockaddr_in addr{};

  addr.sin_family = AF_INET;

  addr.sin_addr.s_addr = htonl(INADDR_ANY);

  addr.sin_port = htons(8888);

  if (bind(fd, (sockaddr *)&addr, sizeof(addr)) == -1) {

    cerr << "bind() failed (port 8888 may be in use)" << endl;

    close(fd);

    return -1;

  }

  

  // 4. 开始监听

  if (listen(fd, 5) == -1) {

    cerr << "listen() failed" << endl;

    close(fd);

    return -1;

  }

  cout << "Server listening on port 8888..." << endl;

  

  // 5. 接受客户端连接

  sockaddr_in client_addr{};

  socklen_t len = sizeof(client_addr);

  // sleep(50); //调试代码使用,便于调试的使用使用 ss -lntp

  // 查看已经连接的客户端信息

  int cfd = accept(fd, (sockaddr *)&client_addr, &len);

  if (cfd == -1) {

    cerr << "accept() failed" << endl;

    close(fd);

    return -1;

  }

  

  // 6. 输出客户端信息(点分十进制格式)

  char ip_str[INET_ADDRSTRLEN];

  inet_ntop(AF_INET, &client_addr.sin_addr, ip_str, sizeof(ip_str));

  cout << "Client connected from " << ip_str << ":"

       << ntohs(client_addr.sin_port) << endl;

  

  while (1) {

    // 7. 读取客户端消息

    char buf[64] = {};

    ssize_t n = read(cfd, buf, sizeof(buf) - 1);

    if (n == -1) {

      cerr << "read() failed" << endl;

    } else if (n == 0) {

      cout << "Client closed connection" << endl;

    } else {

      buf[n] = '\0'; // 确保字符串终止

      cout << "Received: " << buf << endl;

    }

  

    // 8. 发送回复消息

    const char *msg = "Hello from server";

    ssize_t sent = write(cfd, msg, strlen(msg));

    if (sent == -1) {

      cerr << "write() failed" << endl;

    }

  }

  

  // 9. 关闭连接

  close(cfd);

  close(fd);

  return 0;

}
  • 服务端 :在 accept() 之后套了一个 while(1),意味着处理完一条消息后会继续等待下一条,而不会关闭连接。

  • 客户端 :在 connect() 之后套了一个 while(1),意味着发完一条并收到回复后,会立即发送下一条

这样,双方就形成了一个"客户端发送 -> 服务端接收并回复 -> 客户端接收并再发送"的无限循环

需要修正的地方

虽然功能实现了,但你的代码中存在几个严重的逻辑隐患,如果不修复,程序可能会崩溃或占满 CPU:

1. 服务端没有处理"客户端断开"的逻辑(最严重)

当客户端主动关闭连接(或崩溃)时,服务端的 read(cfd, ...) 会返回 0(表示对方关闭)。

但你的代码里,n == 0 时只是打印了语句,并没有 break 跳出循环

这会导致服务端陷入死循环 ,疯狂调用 read(),每次都返回 0,导致 CPU 占用飙到 100%。

建议修改

cpp

if (n == -1) {

cerr << "read() failed" << endl;

break; // 出错退出

} else if (n == 0) {

cout << "Client closed connection" << endl;

break; // 对方断开,退出循环

} else {

// ... 正常处理数据

}

2. 客户端发送速度过快(刷屏)

客户端在 while(1) 中没有任何延时,会疯狂地 往内核缓冲区塞数据。如果服务端处理稍慢,客户端的 write() 最终会被阻塞。虽然不会报错,但控制台会疯狂刷屏,不方便观察。

建议修改 :在客户端的循环末尾加个短暂延时,比如 sleep(1)usleep(500000),让观察更清晰。

3. 客户端没有检查 read() 的返回值

如果服务端崩溃或主动关闭,客户端的 read() 会返回 0-1,但你的代码没有判断,会继续打印空数据或执行错误逻辑。


✅ 修正后的服务端代码

cpp 复制代码
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
using namespace std;

int main() {
    // 1. 创建 TCP 套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        cerr << "socket() failed" << endl;
        return -1;
    }

    // 2. 设置端口复用
    int opt = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
        cerr << "setsockopt() failed" << endl;
        close(fd);
        return -1;
    }

    // 3. 绑定地址和端口
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(8888);
    if (bind(fd, (sockaddr *)&addr, sizeof(addr)) == -1) {
        cerr << "bind() failed (port 8888 may be in use)" << endl;
        close(fd);
        return -1;
    }

    // 4. 开始监听
    if (listen(fd, 5) == -1) {
        cerr << "listen() failed" << endl;
        close(fd);
        return -1;
    }
    cout << "Server listening on port 8888..." << endl;

    // 5. 接受客户端连接
    sockaddr_in client_addr{};
    socklen_t len = sizeof(client_addr);
    // sleep(50); // 调试用
    int cfd = accept(fd, (sockaddr *)&client_addr, &len);
    if (cfd == -1) {
        cerr << "accept() failed" << endl;
        close(fd);
        return -1;
    }

    // 6. 输出客户端信息
    char ip_str[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &client_addr.sin_addr, ip_str, sizeof(ip_str));
    cout << "Client connected from " << ip_str << ":"
         << ntohs(client_addr.sin_port) << endl;

    // 7. 循环通信
    while (1) {
        char buf[64] = {};
        ssize_t n = read(cfd, buf, sizeof(buf) - 1);

        if (n == -1) {
            cerr << "read() failed" << endl;
            break; // 读取出错,退出循环
        } else if (n == 0) {
            cout << "Client closed connection" << endl;
            break; // 客户端断开,退出循环
        } else {
            buf[n] = '\0';
            cout << "Received: " << buf << endl;

            // 发送回复
            const char *msg = "Hello from server";
            ssize_t sent = write(cfd, msg, strlen(msg));
            if (sent == -1) {
                cerr << "write() failed" << endl;
                break; // 写入失败,退出循环
            }
        }
    }

    // 8. 关闭连接
    close(cfd);
    close(fd);
    return 0;
}

✅ 修正后的客户端代码

cpp 复制代码
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
using namespace std;

int main() {
    // 创建 TCP 套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        cerr << "socket() failed" << endl;
        return -1;
    }

    // 设置服务器地址
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(8888);

    // 连接到服务器
    if (connect(fd, (sockaddr *)&addr, sizeof(addr)) == -1) {
        cerr << "connect() failed" << endl;
        close(fd);
        return -1;
    }
    cout << "Connected to server" << endl;

    // 循环通信
    int count = 0; // 添加计数器,避免无限发送(可选)
    while (1) {
        // 发送消息
        const char *msg = "Hello from client";
        ssize_t sent = write(fd, msg, strlen(msg));
        if (sent == -1) {
            cerr << "write() failed" << endl;
            break;
        }
        cout << "Sent: " << msg << endl;

        // 读取回复
        char buf[64] = {};
        ssize_t n = read(fd, buf, sizeof(buf) - 1);
        if (n == -1) {
            cerr << "read() failed" << endl;
            break;
        } else if (n == 0) {
            cout << "Server closed connection" << endl;
            break;
        } else {
            buf[n] = '\0';
            cout << "Received: " << buf << endl;
        }

        // 适当延时,避免刷屏(单位:秒)
        sleep(1);
        // 可选:发送一定次数后退出(避免无限循环)
        // if (++count >= 10) break;
    }

    close(fd);
    return 0;
}

📝 修正问题清单

问题 原代码行为 修正内容 影响
服务端未处理客户端断开 read() 返回 0 时仅打印,不退出循环,导致死循环占满 CPU n == 0 时添加 break 避免空转,正常退出
服务端未处理读取错误 read() 返回 -1 时仅打印,继续循环 添加 break 退出 避免在错误状态下继续
服务端未处理写入错误 write() 失败时仅打印,继续循环 添加 break 退出 及时发现问题并退出
客户端未检查 connect() 返回值 直接连接,未判断失败 添加 if 判断并退出 避免后续操作无效
客户端未检查 write() 返回值 未判断发送是否成功 添加 if 判断并退出 检测网络异常
客户端未检查 read() 返回值 直接读取,未判断对方关闭或错误 添加 n == -1n == 0 处理 正确处理服务端断开
客户端发送过快 无延时,疯狂发送导致刷屏 在循环末尾添加 sleep(1) 控制节奏,便于观察
相关推荐
珠***格8 小时前
双碳目标下:四可装置如何助力光伏消纳与碳数据上报
网络·人工智能·分布式·安全·边缘计算
mounter6258 小时前
绕过主机:通过 Devmem TCP 运行 RDMA 应用
网络·网络协议·tcp/ip·rdma·devmem
yyy(十一月限定版)9 小时前
017【入门】最小栈
linux·运维·服务器
我星期八休息10 小时前
网络编程—网络层
开发语言·前端·网络·人工智能·智能路由器
科力锐品牌君10 小时前
行业龙头|科力锐全链路防勒索 + 多中心容灾方案,构筑河南翔宇医疗业务安全闭环!
网络·数据库·分布式·安全·数据安全·备份
2401_8949155310 小时前
GEO 源码部署如何实现精准地域分发?核心配置参数深度讲解
java·运维·服务器·后端·开源
2401_8685347811 小时前
C语言与C++在基础语法上的区别
网络·网络协议
黄俊懿12 小时前
【架构师从入门到进阶】第五章:DNS&CDN&网关优化思路——第三节:CDN扩展-多地址直连
网络·计算机网络·架构·系统架构·cdn·dns·架构设计
Dr.kangder12 小时前
嵌入式总线设备解析——TTE总线应用与实践
开发语言·网络·算法·嵌入式·多任务·同步机制
2401_8582861113 小时前
OS82.【Linux】设计线程池
java·linux·运维·服务器·开发语言·算法·线程池