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) 控制节奏,便于观察
相关推荐
liulilittle7 小时前
论无知:分布式
服务器·网络·分布式·并发·通信·竞态
Web极客码7 小时前
MariaDB 将成为 cPanel 中的默认数据库
服务器·mariadb·cpanel
川石课堂软件测试7 小时前
安全测试|服务器安全加固方法
服务器·功能测试·测试工具·jmeter·mysql·web安全·单元测试
杨了个杨89827 小时前
docker的网络模式
网络·docker·容器
2401_858286117 小时前
OS79.【Linux】POSIX信号量
linux·运维·服务器
公众号:fuwuqiBMC7 小时前
(转自“服务器BMC”)服务器BMC功能——DDR故障预测
运维·服务器·人工智能
大熊背8 小时前
双目拼接中色差消除优化
网络
Sagittarius_A*8 小时前
Command Injection 命令注入漏洞:系统命令拼接缺陷与执行利用技术
网络·安全·web安全·dvwa·命令注入漏洞
CodingPioneer8 小时前
03零刻Pro-LS2K3000-Loongnix 20.7.rc1硬盘分区、格式化、挂载与卸载
linux·运维·服务器·硬盘挂载