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 == -1 和 n == 0 处理 正确处理服务端断开
客户端发送过快 无延时,疯狂发送导致刷屏 在循环末尾添加 sleep(1) 控制节奏,便于观察
相关推荐
Escalating_xu15 小时前
【C 语言】数据在内存中的存储:补码、大小端、整型陷阱与 IEEE 754 全解析
java·c语言·网络
爱跳舞的烤冷面16 小时前
Linux篇——网络编程
网络
Knight_AL17 小时前
基于 Netty 实现的 WebSocket 服务端
网络·websocket·网络协议
Android系统攻城狮17 小时前
Linux Gstreamer深度解析之gst_audio_sink_get_type调用流程与实战(六十五)
linux·运维·服务器·音视频·gstreamer音视频·音视频进阶·gstreamer音视频进阶
wuyk55518 小时前
《WiFi 嵌入式物联网开发全套实战》| 第 31 章 休眠唤醒 WiFi 断连、时间同步、网络恢复机制
网络·物联网·php
沫璃染墨19 小时前
从零入门计算机网络系列(一):计算机网络初识——从网络发展史到TCP/IP协议》
linux·网络·网络协议·tcp/ip·计算机网络
zyseo819 小时前
谷歌SEO 站内搜索优化实战:把站内搜索词变成关键词金矿
java·服务器·数据库
王大傻092819 小时前
堆叠注入(Stacked Queries Injection)详解:原理、利用与防御
服务器·网络·数据库·web安全·网络安全
艾芯微科技19 小时前
钰泰 ETA5055V330DD1E|SOT-223 3.3V LDO,低压差、大电流线性稳压选型分享
网络·单片机·嵌入式硬件·集成测试·51单片机
bigcarp19 小时前
安卓模拟器MUMU+tcpdump抓包Socket协议流量
服务器·网络·tcpdump