linux学习笔记(29)网络编程——服务器客户端 及多进程多线程服务器
服务器代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {int server_fd, client_fd;struct sockaddr_in address;int addrlen = sizeof(address);char buffer[BUFFER_SIZE] = {0};printf("=== TCP服务器启动 ===\n");1. 创建Socket文件描述符//AF_INET = IPv4, SOCK_STREAM = TCP, 0 = 默认协议server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd == -1) {perror("socket创建失败");exit(EXIT_FAILURE);}printf("1. Socket创建成功\n"); 2. 设置服务器地址address.sin_family = AF_INET; // IPv4address.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡address.sin_port = htons(PORT); // 端口号,htons转换字节序3. 绑定Socket到地址if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {perror("绑定失败");close(server_fd);exit(EXIT_FAILURE);}printf("2. 绑定端口 %d 成功\n", PORT);4. 开始监听连接5 等待连接队列的最大长度if (listen(server_fd, 5) < 0) {perror("监听失败");close(server_fd);exit(EXIT_FAILURE);}printf("3. 开始监听,等待客户端连接...\n");5. 接受客户端连接没人连接则阻塞client_fd = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);if (client_fd < 0) {perror("接受连接失败");close(server_fd);exit(EXIT_FAILURE);}printf("4. 客户端连接成功!\n");6. 接收客户端数据 会阻塞int bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1);if (bytes_read > 0) {buffer[bytes_read] = '\0'; // 添加字符串结束符printf("5. 收到客户端消息: %s\n", buffer);}7. 发送回复给客户端char* response = "你好客户端!我是服务器!";write(client_fd, response, strlen(response));printf("6. 已发送回复给客户端\n");8. 关闭连接close(client_fd);close(server_fd);printf("7. 连接关闭,服务器退出\n");return 0;
}
端口号
0~1024知名端口
1025~4096 保留端口
一般用大于4096的
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_IP "127.0.0.1" // 本地回环地址
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {int sockfd;struct sockaddr_in serv_addr;char buffer[BUFFER_SIZE] = {0};printf("=== TCP客户端启动 ===\n");// 1. 创建Socketsockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1) {perror("socket创建失败");exit(EXIT_FAILURE);}printf("1. Socket创建成功\n");// 2. 设置服务器地址serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);// 将IP地址字符串转换为网络字节序if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {perror("IP地址转换失败");close(sockfd);exit(EXIT_FAILURE);}printf("2. 服务器地址设置: %s:%d\n", SERVER_IP, PORT);// 3. 连接服务器if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {perror("连接服务器失败");close(sockfd);exit(EXIT_FAILURE);}printf("3. 连接服务器成功!\n");// 4. 发送数据到服务器char* message = "你好服务器!我是客户端!";write(sockfd, message, strlen(message));printf("4. 已发送消息: %s\n", message);// 5. 接收服务器回复int bytes_read = read(sockfd, buffer, BUFFER_SIZE - 1);if (bytes_read > 0) {buffer[bytes_read] = '\0';printf("5. 收到服务器回复: %s\n", buffer);}// 6. 关闭连接close(sockfd);printf("6. 连接关闭,客户端退出\n");return 0;
}
多进程并发
多进程并发让服务器可以同时服务多个客户端。
多进程服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/wait.h>
int main() {printf("=== 多进程服务器启动 ===\n");// 1. 创建"总机电话"int server_fd = socket(AF_INET, SOCK_STREAM, 0);// 2. 设置电话号码struct sockaddr_in address;address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(8080);// 3. 绑定电话号码bind(server_fd, (struct sockaddr*)&address, sizeof(address));// 4. 开始等待来电listen(server_fd, 5);printf("服务器准备就绪,等待客户连接...\n");while (1) { // 无限循环,持续服务printf("\n=== 等待新客户 ===\n");// 5. 接听电话(接待顾客)int client_fd = accept(server_fd, NULL, NULL);printf("有新客户连接!\n");// 6. 神奇的一步:创建子进程(分配服务员)pid_t pid = fork();if (pid == 0) {// 子进程区域(服务员的工作)printf("子进程 %d: 开始服务客户\n", getpid());// 关闭不需要的服务器socket(子进程不需要接听新电话)close(server_fd);// 与客户对话char buffer[1024] = {0};read(client_fd, buffer, sizeof(buffer));printf("子进程 %d: 收到客户消息: %s\n", getpid(), buffer);// 回复客户char reply[100];snprintf(reply, sizeof(reply), "你好!我是服务员%d", getpid());write(client_fd, reply, strlen(reply));printf("子进程 %d: 服务完成,退出\n", getpid());close(client_fd);exit(0); // 子进程完成任务后退出}else {// 父进程区域(接待员的工作)printf("父进程: 已分配子进程 %d 服务客户\n", pid);close(client_fd); // 父进程不需要与客户直接通话}}close(server_fd);return 0;
}
=== 多进程服务器启动 ===
服务器准备就绪,等待客户连接...=== 等待新客户 ===
有新客户连接!
父进程: 已分配子进程 1234 服务客户
子进程 1234: 开始服务客户
子进程 1234: 收到客户消息: 你好服务器!我是客户5678
子进程 1234: 服务完成,退出=== 等待新客户 ===
有新客户连接!
父进程: 已分配子进程 1235 服务客户
子进程 1235: 开始服务客户
子进程 1235: 收到客户消息: 你好服务器!我是客户5679
子进程 1235: 服务完成,退出
测试客户端(可以开多个终端测试)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main() {printf("=== 客户端启动 ===\n");// 1. 创建电话int sockfd = socket(AF_INET, SOCK_STREAM, 0);// 2. 拨打服务器电话struct sockaddr_in serv_addr;serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(8080);inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));printf("连接到服务器成功!\n");// 3. 发送消息char message[100];snprintf(message, sizeof(message), "你好服务器!我是客户%d", getpid());write(sockfd, message, strlen(message));printf("发送: %s\n", message);// 4. 接收回复char buffer[1024] = {0};read(sockfd, buffer, sizeof(buffer));printf("收到回复: %s\n", buffer);// 5. 保持连接一段时间,方便观察printf("等待5秒后退出...\n");sleep(5);close(sockfd);printf("客户端退出\n");return 0;
}
=== 客户端启动 ===
连接到服务器成功!
发送: 你好服务器!我是客户5678
收到回复: 你好!我是服务员1234
等待5秒后退出...
客户端退出
多线程并发
简单的多线程并发服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
// 线程参数结构
typedef struct {int client_fd;
} thread_arg_t;
// 线程处理函数
void* handle_client(void* arg) {thread_arg_t* targ = (thread_arg_t*)arg;int client_fd = targ->client_fd;// 处理客户端请求char buffer[100];read(client_fd, buffer, sizeof(buffer));printf("线程%lu: 收到: %s\n", pthread_self(), buffer);// 回复客户端char reply[100];snprintf(reply, sizeof(reply), "Hello from thread %lu", pthread_self());write(client_fd, reply, strlen(reply));close(client_fd);printf("线程%lu: 服务完成\n", pthread_self());free(targ); // 释放线程参数内存return NULL;
}
int main() {printf("=== 多线程服务器 (端口: 8081) ===\n"); // 创建服务器socketint server_fd = socket(AF_INET, SOCK_STREAM, 0);// 设置地址struct sockaddr_in address;address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(8081);bind(server_fd, (struct sockaddr*)&address, sizeof(address));listen(server_fd, 5);printf("等待客户端连接...\n");while (1) {// 接受客户端连接int client_fd = accept(server_fd, NULL, NULL);printf("新客户端连接\n");// 创建线程参数thread_arg_t* targ = malloc(sizeof(thread_arg_t));targ->client_fd = client_fd;// 创建新线程处理客户端pthread_t thread_id;pthread_create(&thread_id, NULL, handle_client, targ);pthread_detach(thread_id); // 线程分离,自动回收资源}close(server_fd);return 0;
}
=== 多线程服务器 (端口: 8081) ===
等待客户端连接...
新客户端连接
线程140123456: 收到: Hello from client 5678
线程140123456: 服务完成
新客户端连接
线程140123789: 收到: Hello from client 5679
线程140123789: 服务完成
测试客户端(通用版)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main(int argc, char* argv[]) {if (argc != 3) {printf("用法: %s <IP> <端口>\n", argv[0]);printf("示例: %s 127.0.0.1 8080\n", argv[0]);return 1;}printf("=== 客户端启动 ===\n");// 创建socketint sockfd = socket(AF_INET, SOCK_STREAM, 0);// 设置服务器地址struct sockaddr_in serv_addr;serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(atoi(argv[2]));inet_pton(AF_INET, argv[1], &serv_addr.sin_addr);// 连接服务器connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));printf("连接到服务器 %s:%s 成功!\n", argv[1], argv[2]);// 发送消息char message[100];snprintf(message, sizeof(message), "Hello from client %d", getpid());write(sockfd, message, strlen(message));printf("发送: %s\n", message);// 接收回复char buffer[100] = {0};read(sockfd, buffer, sizeof(buffer));printf("收到: %s\n", buffer);// 保持连接一段时间便于观察sleep(2);close(sockfd);printf("客户端退出\n");return 0;
}
特性 | 多进程 | 多线程 |
创建方式 | fork() | pthread_create() |
资源开销 | 大(独立内存空间) | 小(共享内存空间) |
数据共享 | 困难(需要IPC) | 容易(共享全局变量) |
稳定性 | 高(进程崩溃不影响其他) | 低(线程崩溃影响整个进程) |
通信成本 | 高(进程间通信复杂) | 低(直接访问共享内存) |
创建速度 | 慢 | 快 |
- 用多进程当:需要高稳定性,任务相互独立
- 用多线程当:需要高性能,任务间需要频繁通信