# 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/ ,如需转载,请注明出处,否则将追究法律责任。