辽阳市网站建设_网站建设公司_定制开发_seo优化
2025/12/26 6:08:15 网站建设 项目流程

Linux Socket 编程代码整理

1. 双向聊天程序 (双线程版)

客户端

#include <netinet/in.h> #include <netinet/ip.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> // 简化类型定义,方便使用 typedef struct sockaddr*(SA); // 线程1:专门接收服务器消息 void* th1(void* arg) { int conn = *(int*)arg; // 获取连接套接字 while (1) { char buf[512] = {0}; // 接收服务器发来的消息 int ret = recv(conn, buf, sizeof(buf), 0); // 如果接收失败或收到退出命令 if (ret <= 0 || 0 == strcmp(buf, "#quit\n")) { exit(0); // 退出整个程序 } printf("from ser:%s", buf); // 显示服务器消息 fflush(stdout); // 立即刷新输出缓冲区 } return NULL; } // 线程2:专门发送消息给服务器 void* th2(void* arg) { int conn = *(int*)arg; while (1) { char buf[512] = {0}; printf("to ser:"); // 提示用户输入 fgets(buf, sizeof(buf), stdin); // 从键盘获取输入 send(conn, buf, strlen(buf), 0); // 发送给服务器 if (0 == strcmp(buf, "#quit\n")) // 如果是退出命令 { exit(0); } } return NULL; } int main(int argc, char** argv) { // 1. 创建TCP套接字(相当于买手机) int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == sockfd) { perror("socket"); // 打印错误信息 return 1; } // 2. 设置服务器地址(知道要打给谁) struct sockaddr_in ser; bzero(&ser, sizeof(ser)); // 清空结构体 ser.sin_family = AF_INET; // IPv4地址族 ser.sin_port = htons(50000); // 端口50000,htons转换为网络字节序 ser.sin_addr.s_addr = INADDR_ANY; // 任意地址(客户端通常用INADDR_ANY) // 3. 连接服务器(打电话) int ret = connect(sockfd, (SA)&ser, sizeof(ser)); if (-1 == ret) { perror("connect"); return 1; } // 4. 创建两个线程(一个耳朵听,一个嘴巴说) pthread_t tid1, tid2; pthread_create(&tid1, NULL, th1, &sockfd); pthread_create(&tid2, NULL, th2, &sockfd); // 5. 等待线程结束 pthread_join(tid1, NULL); pthread_join(tid2, NULL); // 6. 关闭连接(挂电话) close(sockfd); return 0; }
// 功能:创建两个线程,一个负责接收服务器消息,一个负责发送消息 // 特点:使用 pthread 实现双工通信 // 退出条件:输入 "#quit" 或连接断开

服务器端

#include <netinet/in.h> #include <netinet/ip.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> typedef struct sockaddr*(SA); // 线程1:接收客户端消息 void* th1(void* arg) { int conn = *(int*)arg; while (1) { char buf[512] = {0}; int ret = recv(conn, buf, sizeof(buf), 0); if (ret <= 0 || 0 == strcmp(buf, "#quit\n")) { exit(0); } printf("from cli:%s", buf); fflush(stdout); } return NULL; } // 线程2:发送消息给客户端 void* th2(void* arg) { int conn = *(int*)arg; while (1) { char buf[512] = {0}; printf("to cli:"); fgets(buf, sizeof(buf), stdin); send(conn, buf, strlen(buf), 0); if (0 == strcmp(buf, "#quit\n")) { exit(0); } } return NULL; } int main(int argc, char** argv) { // 1. 创建监听套接字(相当于开一家店) int listfd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == listfd) { perror("socket"); return 1; } // 2. 绑定地址(给店铺挂牌子) struct sockaddr_in ser, cli; bzero(&ser, sizeof(ser)); bzero(&cli, sizeof(cli)); ser.sin_family = AF_INET; ser.sin_port = htons(50000); // 店铺在50000号 ser.sin_addr.s_addr = INADDR_ANY; // 接受任何地址来的客户 int ret = bind(listfd, (SA)&ser, sizeof(ser)); if (-1 == ret) { perror("bind"); return 1; } // 3. 开始监听(打开店门营业) listen(listfd, 3); // 3表示最多3个客户可以排队 socklen_t len = sizeof(cli); // 4. 接受客户连接(接待客户) int conn = accept(listfd, (SA)&cli, &len); if (-1 == conn) { perror("accept"); return 1; } // 5. 创建线程与客户聊天 pthread_t tid1, tid2; pthread_create(&tid1, NULL, th1, &conn); pthread_create(&tid2, NULL, th2, &conn); pthread_join(tid1, NULL); pthread_join(tid2, NULL); // 6. 关闭连接 close(listfd); close(conn); return 0; }
// 功能:与客户端对称的双线程通信 // 特点:监听端口 50000,接受单个客户端连接 // 注意:exit(0) 会终止整个进程

2. 文件传输程序

客户端

#include <fcntl.h> #include <netinet/in.h> #include <netinet/ip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> typedef struct sockaddr *(SA); int main(int argc, char **argv) { // 1. 连接服务器(过程同上) int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in ser; bzero(&ser, sizeof(ser)); ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = INADDR_ANY; connect(sockfd, (SA)&ser, sizeof(ser)); // 2. 打开要发送的文件 int fd = open("/home/linux/1.png", O_RDONLY); if (-1 == fd) { perror("open"); return 1; } // 3. 循环读取文件并发送 while (1) { char buf[4096] = {0}; int rd_ret = read(fd, buf, sizeof(buf)); // 从文件读取 if (rd_ret <= 0) // 读到文件末尾 { break; } send(sockfd, buf, rd_ret, 0); // 发送读取的内容 // 等待服务器确认 bzero(buf, sizeof(buf)); recv(sockfd, buf, sizeof(buf), 0); // 接收"go on" } // 4. 关闭连接 close(sockfd); close(fd); return 0; }
// 功能:发送文件 /home/linux/1.png 到服务器 // 流程:读取文件 -> 发送 -> 等待服务器确认 -> 继续发送 // 特点:使用简单的流式传输协议

服务器端

#include <fcntl.h> #include <netinet/in.h> #include <netinet/ip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> typedef struct sockaddr*(SA); int main(int argc, char** argv) { // 1. 创建服务器(过程同聊天版) int listfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in ser, cli; bzero(&ser, sizeof(ser)); bzero(&cli, sizeof(cli)); ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = INADDR_ANY; bind(listfd, (SA)&ser, sizeof(ser)); listen(listfd, 3); socklen_t len = sizeof(cli); int conn = accept(listfd, (SA)&cli, &len); // 2. 创建文件准备写入 int fd = open("2.png", O_WRONLY | O_CREAT | O_TRUNC, 0666); // O_WRONLY: 只写模式 // O_CREAT: 如果文件不存在就创建 // O_TRUNC: 如果文件存在就清空 // 0666: 文件权限(rw-rw-rw-) // 3. 循环接收数据并写入文件 while (1) { char buf[4096] = {0}; int ret = recv(conn, buf, sizeof(buf), 0); if (ret <= 0) // 客户端断开或出错 { break; } write(fd, buf, ret); // 写入文件 // 发送确认消息给客户端 bzero(buf, sizeof(buf)); strcpy(buf, "go on"); send(conn, buf, strlen(buf), 0); } close(listfd); close(conn); close(fd); return 0; }
// 功能:接收客户端发来的文件,保存为 2.png // 特点:每次接收数据后发送 "go on" 确认 // 文件模式:0666 (rw-rw-rw-)

3. 字典查询服务

客户端

#include <netinet/in.h> #include <netinet/ip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> typedef struct sockaddr *(SA); int main(int argc, char **argv) { // 1. 连接服务器 int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in ser; bzero(&ser, sizeof(ser)); ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = INADDR_ANY; connect(sockfd, (SA)&ser, sizeof(ser)); // 2. 循环查询单词 while (1) { char buf[512] = {0}; char mean[512] = {0}; // 存储查询结果 printf("input want_word:"); fgets(buf, sizeof(buf), stdin); // 获取用户输入 // 去掉换行符 buf[strlen(buf) - 1] = '\0'; // 发送单词给服务器 send(sockfd, buf, strlen(buf), 0); if (0 == strcmp(buf, "#quit")) // 退出命令 { break; } // 接收查询结果 int rd_ret = recv(sockfd, mean, sizeof(mean), 0); if (rd_ret <= 0) { break; } printf("%s:%s\n", buf, mean); // 显示结果 } close(sockfd); return 0; }
// 功能:向服务器查询单词释义 // 输入处理:fgets 获取输入,去掉换行符 // 退出:输入 "#quit"

服务器端

#include <netinet/in.h> #include <netinet/ip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> typedef struct sockaddr *(SA); // 查询单词函数 int find_word(char *want_word, char *want_mean) { // 1. 打开字典文件 FILE *fp = fopen("/home/linux/dict.txt", "r"); if (NULL == fp) { perror("fopen"); return 0; } // 2. 逐行读取文件 while (1) { char *word = NULL; char *mean = NULL; char linebuf[1024] = {0}; if (NULL == fgets(linebuf, sizeof(linebuf), fp)) { break; // 读到文件末尾 } // 3. 分割单词和释义 // 假设文件格式:"hello 你好\r" word = strtok(linebuf, " "); // 按空格分割 mean = strtok(NULL, "\r"); // 按回车分割 // 4. 比较单词 if (0 == strcmp(word, want_word)) { strcpy(want_mean, mean); // 复制释义 fclose(fp); return 1; // 找到 } } fclose(fp); return 0; // 没找到 } int main(int argc, char **argv) { // 1. 创建服务器(过程同上) int listfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in ser, cli; bzero(&ser, sizeof(ser)); bzero(&cli, sizeof(cli)); ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = INADDR_ANY; bind(listfd, (SA)&ser, sizeof(ser)); listen(listfd, 3); socklen_t len = sizeof(cli); int conn = accept(listfd, (SA)&cli, &len); // 2. 处理客户端查询请求 while (1) { char buf[512] = {0}; char mean[512] = {0}; // 接收客户端发来的单词 int ret = recv(conn, buf, sizeof(buf), 0); if (ret <= 0) // 客户端断开 { break; } if (0 == strcmp(buf, "#quit")) // 退出命令 { break; } // 查询单词 bzero(mean, sizeof(mean)); int find_ret = find_word(buf, mean); if (0 == find_ret) // 没找到 { strcpy(mean, "没那词"); } // 发送查询结果 send(conn, mean, strlen(mean), 0); } close(listfd); close(conn); return 0; }
// 功能:查询字典文件 /home/linux/dict.txt // 查找逻辑:按行解析 "单词 释义" 格式 // 找不到返回:"没那词" // 注意:strtok 使用可能导致问题

4. 简单 TCP 测试程序

客户端

#include <netinet/in.h> #include <netinet/ip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> typedef struct sockaddr *(SA); int main(int argc, char **argv) { // 1. 连接服务器(过程同上) int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in ser; bzero(&ser, sizeof(ser)); ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = INADDR_ANY; connect(sockfd, (SA)&ser, sizeof(ser)); // 2. 发送100次测试消息 int i = 100; while (i--) { char buf[512] = "this is tcp test"; send(sockfd, buf, strlen(buf), 0); // 发送 // 接收服务器回复 bzero(buf, sizeof(buf)); recv(sockfd, buf, sizeof(buf), 0); printf("ser:%s\n", buf); sleep(1); // 等待1秒 } close(sockfd); return 0; }
// 功能:发送 100 次 "this is tcp test" // 特点:每次发送后等待 1 秒,接收服务器回复

服务器端

#include <netinet/in.h> #include <netinet/ip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <time.h> #include <unistd.h> typedef struct sockaddr*(SA); int main(int argc, char** argv) { // 1. 创建服务器(过程同上) int listfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in ser, cli; bzero(&ser, sizeof(ser)); bzero(&cli, sizeof(cli)); ser.sin_family = AF_INET; ser.sin_port = htons(50000); ser.sin_addr.s_addr = INADDR_ANY; bind(listfd, (SA)&ser, sizeof(ser)); listen(listfd, 3); socklen_t len = sizeof(cli); int conn = accept(listfd, (SA)&cli, &len); // 2. 处理客户端消息 while (1) { char buf[512] = {0}; int ret = recv(conn, buf, sizeof(buf), 0); if (ret <= 0) { break; } // 获取当前时间 time_t tm; time(&tm); // 获取系统时间 // 转换为本地时间结构 struct tm* info = localtime(&tm); sprintf(buf, "%s %d:%d:%d", buf, info->tm_hour, info->tm_min, info->tm_sec); // 发送添加时间戳后的消息 send(conn, buf, strlen(buf), 0); } close(listfd); close(conn); return 0; }
// 功能:接收消息并添加时间戳返回 // 时间格式:原消息 + 时:分:秒 // 注意:sprintf 用法有误,会覆盖原内容

二、关键函数总结

1.Socket API

socket() // 创建套接字 bind() // 绑定地址 listen() // 监听连接 accept() // 接受连接 connect() // 连接服务器 send()/recv() // TCP 发送/接收 close() // 关闭套接字

2.网络地址结构

struct sockaddr_in { sa_family_t sin_family; // AF_INET in_port_t sin_port; // 端口号 (需 htons) struct in_addr sin_addr; // IP地址 };

3.字节序问题

  • 不同计算机存储数据的方式不同(大端/小端)

  • 网络传输统一用大端字节序

  • htons():把"主机字节序"转为"网络字节序"

  • ntohs():把"网络字节序"转回"主机字节序"

//字节序转换

  • htons() // 主机到网络短整型

  • ntohs() // 网络到主机短整型

///地址设置

  • INADDR_ANY // 任意地址 (0.0.0.0)

  • bzero() // 清空结构体

三、编程注意事项

  1. 错误处理:所有 socket 调用都应检查返回值

  2. 资源释放:记得 close socket 和文件描述符

  3. 线程安全:多线程访问共享资源需要同步

  4. 缓冲区管理:注意 buf 的大小和清空

  5. 字符串处理:正确处理字符串结束符 '\0'

四、常见问题

  1. 地址复用:服务器重启可能遇到 "Address already in use"

  2. 阻塞调用:recv() 可能阻塞线程

  3. 粘包问题:TCP 是流协议,需要定义消息边界

  4. 退出机制:exit(0) 会终止整个进程

五、改进建议

  1. 使用 select/poll/epoll 处理多个客户端

  2. 添加更完善的错误处理

  3. 实现协议头来标识消息类型和长度

  4. 使用线程池管理连接

  5. 配置文件化参数(如端口号、文件路径)

5.常见问题解答

  • Q: 为什么客户端也用INADDR_ANY

  • A: 客户端不关心自己的IP,系统会自动选择

  • Q: 为什么需要bzero()

  • A: 清除内存中的垃圾数据,避免意外错误

  • Q: 为什么用strlen(buf)而不是sizeof(buf)

  • A:strlen计算实际字符串长度,sizeof计算数组总大小

六、实际应用场景

  • 聊天程序:微信、QQ

  • 文件传输:FTP、网盘

  • 字典查询:在线翻译、搜索引擎

  • 时间服务:网络校时、日志记录

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询