ITPub博客

首页 > 应用开发 > IT综合 > TCP Socket编程实践:自定义协议实现网络计算器

TCP Socket编程实践:自定义协议实现网络计算器

投稿 IT综合 作者:bhluo 时间:2025-12-25 11:51:41 0 删除 编辑

# TCP Socket编程实践:自定义协议实现网络计算器


基于TCP的Socket编程是网络通信的基石,通过自定义应用层协议,我们可以构建复杂的分布式应用。本文将通过实现一个网络计算器,深入探讨TCP Socket编程的核心技术。


## TCP Socket编程基础框架


TCP Socket编程遵循客户端-服务器模型,核心流程如下:


```c

// 服务器端基础框架

// 1. 创建socket -> socket()

// 2. 绑定地址 -> bind()

// 3. 监听连接 -> listen()

// 4. 接受连接 -> accept()

// 5. 读写数据 -> read()/write()

// 6. 关闭连接 -> close()


// 客户端基础框架

// 1. 创建socket -> socket()

// 2. 连接服务器 -> connect()

// 3. 读写数据 -> read()/write()

// 4. 关闭连接 -> close()

```


## 自定义计算器协议设计


良好的协议设计是网络应用成功的关键。我们的计算器协议采用二进制格式,确保高效传输:


```c

#pragma once

#include <stdint.h>


// 操作码定义

typedef enum {

    OP_ADD = 1,      // 加法

    OP_SUB = 2,      // 减法

    OP_MUL = 3,      // 乘法

    OP_DIV = 4,      // 除法

    OP_MOD = 5       // 取模

} OperationCode;


// 请求协议头(固定12字节)

typedef struct {

    uint32_t magic;          // 魔数:0xCACACACA

    uint16_t version;        // 协议版本:1

    uint16_t op_code;        // 操作码

    uint32_t seq_num;        // 序列号

} RequestHeader;


// 请求体(固定8字节)

typedef struct {

    int32_t operand1;        // 操作数1

    int32_t operand2;        // 操作数2

} RequestBody;


// 响应协议头(固定12字节)

typedef struct {

    uint32_t magic;          // 魔数:0xCAFECAFE

    uint16_t version;        // 协议版本:1

    uint16_t status;         // 状态码

    uint32_t seq_num;        // 序列号(回显)

} ResponseHeader;


// 响应体(固定8字节)

typedef struct {

    int64_t result;          // 计算结果

    int32_t remainder;       // 余数(仅除法需要)

} ResponseBody;


// 完整请求包

typedef struct {

    RequestHeader header;

    RequestBody body;

} CalculatorRequest;


// 完整响应包

typedef struct {

    ResponseHeader header;

    ResponseBody body;

} CalculatorResponse;


// 状态码定义

#define STATUS_SUCCESS 0

#define STATUS_INVALID_REQUEST 1

#define STATUS_DIVIDE_BY_ZERO 2

#define STATUS_OVERFLOW 3

#define STATUS_SERVER_ERROR 4

```


## 服务器端完整实现


以下是计算器服务器的详细实现:


```c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/socket.h>

#include <pthread.h>

#include <errno.h>

#include "calculator_protocol.h"


#define PORT 8888

#define BACKLOG 10

#define BUFFER_SIZE sizeof(CalculatorRequest)


// 线程参数结构

typedef struct {

    int client_fd;

    struct sockaddr_in client_addr;

} ClientInfo;


// 验证请求合法性

int validate_request(const CalculatorRequest *req) {

    if (req->header.magic != htonl(0xCACACACA)) {

        fprintf(stderr, "无效的魔数\n");

        return 0;

    }

    if (req->header.version != htons(1)) {

        fprintf(stderr, "不支持的协议版本\n");

        return 0;

    }

    if (req->header.op_code < OP_ADD || req->header.op_code > OP_MOD) {

        fprintf(stderr, "无效的操作码\n");

        return 0;

    }

    return 1;

}


// 处理计算请求

int process_calculation(const CalculatorRequest *req, CalculatorResponse *resp) {

    int32_t a = ntohl(req->body.operand1);

    int32_t b = ntohl(req->body.operand2);

    int64_t result = 0;

    int32_t remainder = 0;

    uint16_t status = STATUS_SUCCESS;

    

    // 初始化响应头

    resp->header.magic = htonl(0xCAFECAFE);

    resp->header.version = htons(1);

    resp->header.seq_num = req->header.seq_num; // 保持序列号一致

    

    switch (ntohs(req->header.op_code)) {

        case OP_ADD:

            result = (int64_t)a + b;

            break;

        case OP_SUB:

            result = (int64_t)a - b;

            break;

        case OP_MUL:

            result = (int64_t)a * b;

            break;

        case OP_DIV:

            if (b == 0) {

                status = STATUS_DIVIDE_BY_ZERO;

            } else {

                result = a / b;

                remainder = a % b;

            }

            break;

        case OP_MOD:

            if (b == 0) {

                status = STATUS_DIVIDE_BY_ZERO;

            } else {

                result = a % b;

            }

            break;

        default:

            status = STATUS_INVALID_REQUEST;

            break;

    }

    

    // 检查溢出(简化检查)

    if (result > INT32_MAX || result < INT32_MIN) {

        status = STATUS_OVERFLOW;

    }

    

    resp->header.status = htons(status);

    resp->body.result = htonll(result);

    resp->body.remainder = htonl(remainder);

    

    return (status == STATUS_SUCCESS);

}


// 处理客户端连接的线程函数

void *handle_client(void *arg) {

    ClientInfo *info = (ClientInfo *)arg;

    char buffer[BUFFER_SIZE];

    CalculatorRequest req;

    CalculatorResponse resp;

    

    printf("客户端连接: %s:%d\n", 

           inet_ntoa(info->client_addr.sin_addr),

           ntohs(info->client_addr.sin_port));

    

    while (1) {

        // 接收请求

        ssize_t bytes_received = recv(info->client_fd, buffer, BUFFER_SIZE, 0);

        

        if (bytes_received <= 0) {

            if (bytes_received == 0) {

                printf("客户端断开连接\n");

            } else {

                perror("接收数据失败");

            }

            break;

        }

        

        // 检查数据长度

        if (bytes_received != sizeof(CalculatorRequest)) {

            fprintf(stderr, "数据长度错误: 期望%ld字节, 收到%ld字节\n",

                   sizeof(CalculatorRequest), bytes_received);

            continue;

        }

        

        // 解析请求

        memcpy(&req, buffer, sizeof(CalculatorRequest));

        

        // 验证请求

        if (!validate_request(&req)) {

            resp.header.magic = htonl(0xCAFECAFE);

            resp.header.version = htons(1);

            resp.header.status = htons(STATUS_INVALID_REQUEST);

            resp.header.seq_num = req.header.seq_num;

            send(info->client_fd, &resp, sizeof(ResponseHeader), 0);

            continue;

        }

        

        // 处理计算

        if (process_calculation(&req, &resp)) {

            printf("计算请求: %d %c %d = %lld (序列号: %u)\n",

                   ntohl(req.body.operand1),

                   "+-*/%"[ntohs(req.header.op_code)-1],

                   ntohl(req.body.operand2),

                   ntohll(resp.body.result),

                   ntohl(req.header.seq_num));

        }

        

        // 发送响应

        if (send(info->client_fd, &resp, sizeof(CalculatorResponse), 0) < 0) {

<"dyd.xingjungy.cn"><"dybzq.xingjungy.cn"><"migu.xingjungy.cn">

            perror("发送响应失败");

            break;

        }

    }

    

    close(info->client_fd);

    free(info);

    return NULL;

}


// 初始化服务器

int init_server(int port) {

    int server_fd;

    struct sockaddr_in server_addr;

    

    // 创建socket

    server_fd = socket(AF_INET, SOCK_STREAM, 0);

    if (server_fd < 0) {

        perror("创建socket失败");

        return -1;

    }

    

    // 设置socket选项

    int opt = 1;

    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {

        perror("设置socket选项失败");

        close(server_fd);

        return -1;

    }

    

    // 绑定地址

    memset(&server_addr, 0, sizeof(server_addr));

    server_addr.sin_family = AF_INET;

    server_addr.sin_addr.s_addr = INADDR_ANY;

    server_addr.sin_port = htons(port);

    

    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {

        perror("绑定地址失败");

        close(server_fd);

        return -1;

    }

    

    // 监听连接

    if (listen(server_fd, BACKLOG) < 0) {

        perror("监听失败");

        close(server_fd);

        return -1;

    }

    

    printf("计算器服务器启动,监听端口: %d\n", port);

    return server_fd;

}


int main() {

    int server_fd = init_server(PORT);

    if (server_fd < 0) {

        return EXIT_FAILURE;

    }

    

    while (1) {

        ClientInfo *client_info = malloc(sizeof(ClientInfo));

        socklen_t addr_len = sizeof(client_info->client_addr);

        

        // 接受连接

        client_info->client_fd = accept(server_fd,

            (struct sockaddr *)&client_info->client_addr,

            &addr_len);

        

        if (client_info->client_fd < 0) {

            perror("接受连接失败");

            free(client_info);

            continue;

        }

        

        // 创建线程处理客户端

        pthread_t thread_id;

        if (pthread_create(&thread_id, NULL, handle_client, client_info) != 0) {

            perror("创建线程失败");

            close(client_info->client_fd);

            free(client_info);

            continue;

        }

        

        // 分离线程,使其自动回收资源

        pthread_detach(thread_id);

    }

    

    close(server_fd);

    return EXIT_SUCCESS;

}

```


## 客户端实现


客户端需要处理用户输入和网络通信:


```c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/socket.h>

#include "calculator_protocol.h"


#define SERVER_IP "127.0.0.1"

#define SERVER_PORT 8888


// 连接服务器

int connect_to_server(const char *ip, int port) {

    int sock_fd;

    struct sockaddr_in server_addr;

    

    sock_fd = socket(AF_INET, SOCK_STREAM, 0);

    if (sock_fd < 0) {

        perror("创建socket失败");

        return -1;

    }

    

    memset(&server_addr, 0, sizeof(server_addr));

    server_addr.sin_family = AF_INET;

    server_addr.sin_port = htons(port);

    

    if (inet_pton(AF_INET, ip, &server_addr.sin_addr) <= 0) {

        perror("无效的IP地址");

        close(sock_fd);

        return -1;

    }

    

    if (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {

        perror("连接服务器失败");

        close(sock_fd);

        return -1;

    }

    

    printf("已连接到服务器 %s:%d\n", ip, port);

    return sock_fd;

}


// 解析操作符

uint16_t parse_operator(char op) {

    switch (op) {

        case '+': return OP_ADD;

        case '-': return OP_SUB;

        case '*': return OP_MUL;

        case '/': return OP_DIV;

        case '%': return OP_MOD;

        default: return 0;

    }

}


// 打印状态信息

void print_status(uint16_t status) {

<"iqiy.xingjungy.cn"><"3z.xingjungy.cn"><"7a.xingjungy.cn">

    switch (status) {

        case STATUS_SUCCESS:

            printf("计算成功\n");

            break;

        case STATUS_INVALID_REQUEST:

            printf("无效的请求\n");

            break;

        case STATUS_DIVIDE_BY_ZERO:

            printf("错误:除数为零\n");

            break;

        case STATUS_OVERFLOW:

            printf("错误:计算结果溢出\n");

            break;

        case STATUS_SERVER_ERROR:

            printf("服务器内部错误\n");

            break;

        default:

            printf("未知错误\n");

            break;

    }

}


int main() {

    int sock_fd = connect_to_server(SERVER_IP, SERVER_PORT);

    if (sock_fd < 0) {

        return EXIT_FAILURE;

    }

    

    CalculatorRequest req;

    CalculatorResponse resp;

    static uint32_t seq_num = 1;

    char op;

    int a, b;

    

    printf("网络计算器客户端\n");

    printf("输入格式: 数字 运算符 数字 (例如: 10 + 20)\n");

    printf("可用运算符: + - * / %%\n");

    printf("输入 'quit' 退出\n\n");

    

    while (1) {

        printf("> ");

        

        char input[100];

        if (!fgets(input, sizeof(input), stdin)) {

            break;

        }

        

        // 移除换行符

        input[strcspn(input, "\n")] = 0;

        

        if (strcmp(input, "quit") == 0) {

            printf("退出程序\n");

            break;

        }

        

        // 解析输入

        if (sscanf(input, "%d %c %d", &a, &op, &b) != 3) {

            printf("输入格式错误\n");

            continue;

        }

        

        uint16_t op_code = parse_operator(op);

        if (op_code == 0) {

            printf("不支持的运算符: %c\n", op);

            continue;

        }

        

        // 构建请求

        req.header.magic = htonl(0xCACACACA);

        req.header.version = htons(1);

        req.header.op_code = htons(op_code);

        req.header.seq_num = htonl(seq_num++);

        req.body.operand1 = htonl(a);

        req.body.operand2 = htonl(b);

        

        // 发送请求

        if (send(sock_fd, &req, sizeof(req), 0) < 0) {

            perror("发送请求失败");

            break;

        }

        

        // 接收响应

        ssize_t bytes_received = recv(sock_fd, &resp, sizeof(resp), 0);

        if (bytes_received <= 0) {

            if (bytes_received == 0) {

                printf("服务器断开连接\n");

            } else {

                perror("接收响应失败");

            }

            break;

        }

        

        if (bytes_received != sizeof(resp)) {

            printf("响应长度错误\n");

            continue;

        }

        

        // 验证响应

        if (resp.header.magic != htonl(0xCAFECAFE)) {

            printf("无效的响应\n");

            continue;

        }

        

        // 处理响应

        uint16_t status = ntohs(resp.header.status);

        if (status == STATUS_SUCCESS) {

            int64_t result = ntohll(resp.body.result);

            if (op_code == OP_DIV) {

                int32_t remainder = ntohl(resp.body.remainder);

                printf("结果: %lld 余数: %d\n", result, remainder);

            } else {

                printf("结果: %lld\n", result);

            }

        } else {

            print_status(status);

        }

    }

    

    close(sock_fd);

    return EXIT_SUCCESS;

}

```


## 协议设计的进阶考虑


### 1. 字节序处理


网络字节序转换函数:

```c

// 64位整数字节序转换(需要自定义)

uint64_t htonll(uint64_t host) {

    if (htonl(1) == 1) {  // 大端系统

        return host;

    } else {  // 小端系统

        return ((uint64_t)htonl(host & 0xFFFFFFFF) << 32) | htonl(host >> 32);

    }

}


uint64_t ntohll(uint64_t net) {

    return htonll(net);

}

```


### 2. 超时与重试机制


```c

#include <sys/time.h>


// 设置socket超时

void set_socket_timeout(int sock_fd, int seconds) {

    struct timeval timeout;

    timeout.tv_sec = seconds;

    timeout.tv_usec = 0;

    

    setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));

    setsockopt(sock_fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));

}


// 带重试的发送

int send_with_retry(int sock_fd, const void *buf, size_t len, int max_retries) {

    int retries = 0;

    ssize_t sent = 0;

    

    while (sent < len && retries < max_retries) {

        ssize_t n = send(sock_fd, (char *)buf + sent, len - sent, 0);

        if (n < 0) {

            if (errno == EINTR || errno == EAGAIN) {

                retries++;

                usleep(100000); // 100ms延迟

                continue;

            }

            return -1;

        }

        sent += n;

        retries = 0; // 成功发送后重置重试计数

    }

    

    return (sent == len) ? 0 : -1;

}

```


### 3. 连接池管理


对于高性能服务器,连接池是必要的:


```c

typedef struct {

    int *connections;

    int size;

    int capacity;

    pthread_mutex_t lock;

} ConnectionPool;


ConnectionPool* create_pool(int capacity) {

    ConnectionPool *pool = malloc(sizeof(ConnectionPool));

    pool->connections = malloc(capacity * sizeof(int));

    pool->size = 0;

    pool->capacity = capacity;

    pthread_mutex_init(&pool->lock, NULL);

    return pool;

}


int get_connection(ConnectionPool *pool) {

    pthread_mutex_lock(&pool->lock);

    if (pool->size > 0) {

        int conn = pool->connections[--pool->size];

        pthread_mutex_unlock(&pool->lock);

        return conn;

    }

    pthread_mutex_unlock(&pool->lock);

    return -1; // 需要创建新连接

}

```


## 测试与验证


```bash

# 编译服务器

gcc -o calculator_server calculator_server.c -lpthread


# 编译客户端

gcc -o calculator_client calculator_client.c


# 启动服务器

./calculator_server


# 在另一个终端启动客户端

./calculator_client


# 测试命令

10 + 20

100 * 50

15 / 4

10 % 3

10 / 0  # 测试错误处理

```


## 性能优化建议


1. **使用epoll/kqueue**:对于高并发场景

2. **零拷贝技术**:使用sendfile、splice等系统调用

3. **协议压缩**:对于大数据传输

4. **连接复用**:使用HTTP/1.1的Keep-Alive机制

5. **内存池**:减少内存分配开销


## 总结


通过实现网络计算器这个具体案例,我们深入探讨了基于TCP的Socket编程和自定义协议设计。从基础Socket API的使用,到协议设计、字节序处理、错误处理,再到多线程服务端实现,这个项目涵盖了网络编程的核心要素。


良好的协议设计需要考虑扩展性、兼容性和错误处理。网络字节序、数据对齐、边界检查等问题在实际开发中经常遇到,需要特别关注。多线程模型虽然简单,但在高并发场景下可能需要考虑更高效的IO模型。


这个计算器示例虽然简单,但它展示了如何将现实需求转化为网络协议,并通过Socket编程实现。这种模式可以扩展到更复杂的应用,如即时通讯、文件传输、远程控制等。理解这些基础原理,是构建可靠网络应用的必备基础。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/19545/viewspace-3106233/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2015-11-01

  • 博文量
    895
  • 访问量
    933503