【Linux开发】 02 Linux TCP 网络编程——迭代、回声服务器

一、什么是迭代服务器?什么是回声服务器?

普通服务器TCP的缺点:启动一次服务程序,只能给一个客户端服务。
迭代服务器比较原始,它的原型可以描述成:

cpp 复制代码
while(1)
{
new_fd = 服务器accept客户端的连接(new_fd = accept(listenfd, XX, XX))
逻辑处理
在这个new_fd上给客户端发送消息
关闭new_fd
}

也就是说,这个程序是一个一个处理各个客户端发来的连接的,比如一个客户端发来一个连接,那么只要它还没有完成自己的任务,那么它就一直会占用服务器的进程直到处理完毕后服务器关闭掉这个socket。可以循环服务多个客户端。

回声服务器:收到什么数据,就原样返回什么数据,就像山谷里的回声一样。


二、整体代码结构

服务器端(echo_server.c)

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024          // 缓冲区大小
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;                      // 监听套接字和客户端套接字
    char message[BUF_SIZE];                        // 数据缓冲区
    int str_len, i;                                 // 读取的字节数,循环计数器

    struct sockaddr_in serv_adr;                   // 服务器地址结构
    struct sockaddr_in clnt_adr;                   // 客户端地址结构
    socklen_t clnt_adr_sz;                         // 客户端地址结构大小

    if (argc != 2) {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    // ========== 1. 创建套接字 ==========
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
        error_handling("socket() error");

    // ========== 2. 初始化服务器地址结构 ==========
    memset(&serv_adr, 0, sizeof(serv_adr));        // 清零
    serv_adr.sin_family = AF_INET;                 // IPv4
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); // 本机任意IP
    serv_adr.sin_port = htons(atoi(argv[1]));     // 命令行指定的端口

    // ========== 3. 绑定套接字到地址 ==========
    if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
        error_handling("bind() error");

    // ========== 4. 进入监听状态 ==========
    if (listen(serv_sock, 5) == -1)               // 最大等待队列长度为5
        error_handling("listen() error");

    clnt_adr_sz = sizeof(clnt_adr);

    // ========== 5. 迭代处理客户端 ==========
    for (i = 0; i < 5; i++)                        // 最多服务5个客户端
    {
        // 接受客户端连接(阻塞)
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
        if (clnt_sock == -1)
            error_handling("accept() error");
        else
            printf("Connected client %d \n", i + 1);

        // 循环接收客户端数据,直到对方关闭连接(read返回0)
        while ((str_len = read(clnt_sock, message, BUF_SIZE)) != 0)
        {
            // 将收到的数据原样写回(回声)
            write(clnt_sock, message, str_len);
        }

        // 关闭当前客户端连接
        close(clnt_sock);
    }

    // 所有客户端服务完毕,关闭监听套接字
    close(serv_sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

客户端(echo_client.c)

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    char message[BUF_SIZE];
    int str_len;
    struct sockaddr_in serv_adr;

    if (argc != 3) {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    // ========== 1. 创建套接字 ==========
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == -1)
        error_handling("socket() error");

    // ========== 2. 初始化服务器地址结构 ==========
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);  // 字符串IP → 整数
    serv_adr.sin_port = htons(atoi(argv[2]));

    // ========== 3. 连接服务器 ==========
    if (connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
        error_handling("connect() error!");
    else
        puts("Connected...........");

    // ========== 4. 与服务器交互 ==========
    while (1)
    {
        fputs("Input message(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);          // 从键盘读取一行

        // 如果输入 q 或 Q,退出循环
        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;

        // 发送消息给服务器
        write(sock, message, strlen(message));

        // 接收服务器返回的(回声)消息
        str_len = read(sock, message, BUF_SIZE - 1);
        if (str_len == -1)
            error_handling("read() error!");

        message[str_len] = 0;                     // 添加字符串结束符
        printf("Message from server: %s", message);
    }

    // 关闭连接
    close(sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

三、迭代服务器的工作流程

复制代码
1. socket()   → 创建监听套接字
2. bind()     → 绑定到本地地址和端口
3. listen()   → 进入监听状态
4. 循环(i = 0; i < 5; i++):
   a. accept()       → 接受一个客户端连接
   b. while (read() != 0):
        收到数据 → write() 原样返回
   c. close()        → 关闭与当前客户端的连接
5. close(监听套接字)

关键点

  • 每次 accept 后,服务器只服务一个客户端,直到该客户端关闭连接才继续接受下一个。
  • 通过 read 的返回值为 0 来判断客户端是否关闭(EOF)。
  • 这个示例最多处理 5 个客户端,之后自动退出。

四、编译与运行

1. 编译服务器和客户端

bash 复制代码
gcc echo_server.c -o server
gcc echo_client.c -o client

2. 运行服务器(指定端口,比如 9190)

bash 复制代码
./server 9190

服务器会等待客户端连接,输出:

复制代码
Connected client 1

(当第一个客户端连接后)

3. 运行客户端(指定服务器IP和端口)

bash 复制代码
./client 127.0.0.1 9190

客户端显示:

复制代码
Connected...........
Input message(Q to quit):

输入消息后,服务器会返回相同的消息。

4. 多客户端测试

  • 你可以依次启动多个客户端,服务器会按顺序处理它们(前一个断开后,下一个才能连接)。
  • 注意:服务器最多服务 5 个客户端,之后自动退出。

五、常见问题与解答

Q1:为什么服务器循环中 read 返回值可能为 0?

A:当客户端调用 close() 时,服务器端的 read 会返回 0,表示收到 EOF(文件结束)。此时我们退出内层循环,关闭当前客户端连接。

Q2:为什么 read 返回的字节数不一定等于我们发送的字节数?

A:TCP 是流式协议,不保留消息边界。一次 read 可能读到部分数据,也可能一次读到多个消息。但在本例中,我们每次发送一条完整消息,服务器也一次读完,所以没问题。在实际开发中,通常需要自己处理消息边界(例如规定消息长度或使用分隔符)。

Q3:如何让服务器一直运行,而不是只服务 5 个客户端?

A:将 for(i=0; i<5; i++) 改为 while(1) 即可无限循环。但要注意,这样服务器会一直运行,需要手动关闭(比如按 Ctrl+C)。

Q4:为什么 bind() 要用 INADDR_ANY

A:表示监听本机所有网络接口(包括 127.0.0.1、192.168.x.x 等)。如果只想监听某个特定 IP,可以用 inet_addr("192.168.1.100")

Q5:readwriterecv / send 有什么区别?

A:read / write 是 POSIX 标准函数,适用于任何文件描述符(包括 socket)。recv / send 是 socket 专用的,可以设置额外标志(如 MSG_DONTWAIT)。它们本质上是相似的。

相关推荐
luj_17684 小时前
残熵算法实时化三大瓶颈突破
c语言·开发语言·网络·经验分享·算法
吴懿不在4 小时前
某次热身赛re方向wp WORDS 51,500 READ 172 MIN base64,
网络
RisunJan7 小时前
Linux命令-rlogin(远程登录)
linux·运维
深圳恒讯7 小时前
菲律宾云服务器与传统VPS的架构差异
运维·服务器·架构
蓝天下的守望者7 小时前
svt_apb_if里的宏定义问题
运维·服务器·数据库
小林ixn7 小时前
从“玩具工具”到“跨语言利器”:MCP 协议实战解析
运维·服务器·网络
2603_954708319 小时前
全维度容错设计,打造微电网安全运行屏障
服务器·网络·数据库·人工智能·分布式·安全
其实防守也摸鱼9 小时前
蓝队相关知识学习(1)---常用堡垒机和服务器
服务器·功能测试·学习·网络安全·网络攻击模型·攻防对抗·蓝队
青岛前景互联信息技术有限公司10 小时前
以新标准为底座,前景互联打造高危场景智能接处警新体系
大数据·网络·人工智能
大飞记Python10 小时前
Linux命令速查手册(测试开发4年实战总结,附PDF)
linux·网络·pdf