5天挑战网络编程 -DAY1(linux版)
📅 Day 1:网络编程初探 —— 搭建你的第一个TCP连接
🎯 今日目标
- 理解网络分层模型(TCP/IP 四层模型)
- 掌握 TCP 协议的基本工作原理
- 理解字节序(大小端)及其转换函数
- 掌握 TCP 套接字(socket)编程的基本流程
- 实现一个简单的 TCP "回声+大写转换"服务器与客户端
🧠 第一部分:理论基础
📘 网络分层模型:TCP/IP 四层模型
层级 | 名称 | 功能描述 | 典型协议 |
---|---|---|---|
应用层 | Application | 提供网络服务,处理应用程序细节 | HTTP、FTP、DNS、SMTP |
传输层 | Transport | 提供端到端通信,负责数据传输的可靠性 | TCP、UDP |
网际层 | Internet | 处理IP地址、路由选择等网络逻辑 | IP、ICMP |
网络接口层 | Network Interface | 处理物理网络传输,如以太网帧 | 以太网、WIFI |
📌 补充:OSI 七层模型是理论模型,实际使用的是 TCP/IP 四层模型。
📘 TCP vs UDP 简要对比
特性 | TCP | UDP |
---|---|---|
连接性 | 面向连接(需要建立连接) | 无连接(直接发送数据) |
可靠性 | 可靠(确认、重传、流量控制) | 不可靠(尽力而为) |
速度 | 慢,开销大 | 快,开销小 |
数据边界 | 无(流式数据) | 有(数据报) |
应用场景 | Web、文件传输、邮件 | 视频、语音、游戏直播 |
📘 套接字(Socket)是什么?
在 Linux 中,“一切皆文件”,Socket 是一种特殊的文件描述符(fd),用于网络通信。通过套接字,应用程序可以像读写文件一样进行网络数据传输。
📘 字节序(Byte Order)
- 大端(Big Endian):高位字节存储在低地址位置
- 小端(Little Endian):低位字节存储在低地址位置
- 网络字节序:统一使用大端,确保跨平台数据一致性
🧠 第二部分:重点函数详解
TCP 套接字编程核心函数
函数 | 功能 | 参数详细说明 | 返回值 |
---|---|---|---|
int socket(int domain, int type, int protocol) | 创建套接字 | domain : 地址族(AF_INET);type : 套接字类型(SOCK_STREAM);protocol : 协议(0) | 成功返回套接字描述符,失败返回-1 |
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) | 绑定地址 | sockfd : 套接字描述符;addr : 地址结构体指针;addrlen : 地址结构体大小 | 成功返回0,失败返回-1 |
int listen(int sockfd, int backlog) | 监听连接 | sockfd : 监听套接字;backlog : 连接队列最大长度 | 成功返回0,失败返回-1 |
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) | 接受连接 | sockfd : 监听套接字;addr : 客户端地址结构体;addrlen : 地址结构体大小指针 | 成功返回新的连接套接字,失败返回-1 |
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) | 客户端连接服务器 | sockfd : 客户端套接字;addr : 服务器地址结构体;addrlen : 地址结构体大小 | 成功返回0,失败返回-1 |
ssize_t send(int sockfd, const void *buf, size_t len, int flags) | 发送数据 | sockfd : 连接套接字;buf : 发送缓冲区;len : 发送长度;flags : 发送标志 | 成功返回发送字节数,失败返回-1 |
ssize_t recv(int sockfd, void *buf, size_t len, int flags) | 接收数据 | sockfd : 连接套接字;buf : 接收缓冲区;len : 缓冲区大小;flags : 接收标志 | 成功返回接收字节数,连接关闭返回0,失败返回-1 |
int close(int fd) | 关闭套接字 | fd : 文件描述符(套接字) | 成功返回0,失败返回-1 |
字节序转换函数详解
函数名 | 功能 | 参数说明 | 返回值 |
---|---|---|---|
uint16_t htons(uint16_t hostshort) | 主机字节序转网络字节序(16位) | hostshort :主机字节序的短整型 | 网络字节序的短整型 |
uint32_t htonl(uint32_t hostlong) | 主机字节序转网络字节序(32位) | hostlong :主机字节序的长整型 | 网络字节序的长整型 |
uint16_t ntohs(uint16_t netshort) | 网络字节序转主机字节序(16位) | netshort :网络字节序的短整型 | 主机字节序的短整型 |
uint32_t ntohl(uint32_t netlong) | 网络字节序转主机字节序(32位) | netlong :网络字节序的长整型 | 主机字节序的长整型 |
🧱 TCP 服务器/客户端开发流程
🔧 TCP 服务器开发流程(框架)
1. 创建套接字 socket()
2. 绑定地址 bind()
3. 监听连接 listen()
4. 接受连接 accept()
5. 收发数据 send() / recv()
6. 关闭连接 close()
🔧 TCP 客户端开发流程(框架)
1. 创建套接字 socket()
2. 连接服务器 connect()
3. 收发数据 send() / recv()
4. 关闭连接 close()
💻 第三部分:动手实战 —— TCP 回声+大写转换服务器
🧩 功能说明
- 客户端输入字符串,发送给服务器
- 服务器将字符串转为大写,并返回给客户端
- 客户端打印收到的结果
📁 文件结构
tcp_server.c
tcp_client.c
🖥️ tcp_server.c(服务器端)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ctype.h>#define PORT 8888 // 服务器监听端口号
#define MAX_BUFFER 1024 // 缓冲区大小int main() {int server_fd, client_fd; // 服务器和客户端套接字描述符struct sockaddr_in server_addr, client_addr; // 服务器和客户端地址结构体socklen_t client_len = sizeof(client_addr); // 客户端地址结构体大小char buffer[MAX_BUFFER]; // 数据缓冲区// 1. 创建套接字// 函数原型:int socket(int domain, int type, int protocol);// 参数说明:// domain: 地址族,AF_INET表示IPv4// type: 套接字类型,SOCK_STREAM表示TCP流式套接字// protocol: 协议,0表示使用默认协议// 返回值:成功返回套接字描述符,失败返回-1server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd == -1) {perror("socket failed"); // 打印错误信息exit(EXIT_FAILURE); // 退出程序}// 2. 设置服务器地址结构memset(&server_addr, 0, sizeof(server_addr)); // 清零地址结构体server_addr.sin_family = AF_INET; // 设置地址族为IPv4server_addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有本地IP地址server_addr.sin_port = htons(PORT); // 设置端口号(网络字节序)// 3. 绑定地址// 函数原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);// 参数说明:// sockfd: 套接字描述符// addr: 指向地址结构体的指针// addrlen: 地址结构体的大小// 返回值:成功返回0,失败返回-1if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("bind failed");close(server_fd); // 关闭套接字exit(EXIT_FAILURE);}// 4. 监听连接// 函数原型:int listen(int sockfd, int backlog);// 参数说明:// sockfd: 监听套接字描述符// backlog: 等待连接队列的最大长度// 返回值:成功返回0,失败返回-1if (listen(server_fd, 5) == -1) {perror("listen failed");close(server_fd);exit(EXIT_FAILURE);}printf("服务器启动,监听端口 %d...\n", PORT);// 5. 接受客户端连接while (1) {// 函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);// 参数说明:// sockfd: 监听套接字// addr: 用于存储客户端地址信息的结构体// addrlen: 客户端地址结构体大小的指针// 返回值:成功返回新的连接套接字,失败返回-1client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);if (client_fd == -1) {perror("accept failed");continue; // 继续等待下一个连接}printf("客户端连接成功: %s:%d\n", inet_ntoa(client_addr.sin_addr), // 将网络地址转换为点分十进制字符串ntohs(client_addr.sin_port)); // 将网络字节序端口号转换为主机字节序// 6. 收发数据while (1) {// 函数原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags);// 参数说明:// sockfd: 连接套接字// buf: 接收数据的缓冲区// len: 缓冲区大小// flags: 接收标志,通常为0// 返回值:成功返回接收到的字节数,连接关闭返回0,出错返回-1int n = recv(client_fd, buffer, MAX_BUFFER, 0);if (n <= 0) break; // 客户端断开或出错buffer[n] = '\0'; // 添加字符串结束符printf("收到数据: %s", buffer);// 转为大写for (int i = 0; i < n; i++) {buffer[i] = toupper(buffer[i]); // 将字符转换为大写}// 函数原型:ssize_t send(int sockfd, const void *buf, size_t len, int flags);// 参数说明:// sockfd: 连接套接字// buf: 发送数据的缓冲区// len: 发送数据的长度// flags: 发送标志,通常为0// 返回值:成功返回发送的字节数,失败返回-1send(client_fd, buffer, n, 0); // 回传给客户端}close(client_fd); // 关闭客户端连接printf("客户端断开连接\n");}close(server_fd); // 关闭服务器监听套接字return 0;
}
🖥️ tcp_client.c(客户端)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>#define SERVER_IP "127.0.0.1" // 服务器IP地址(本地回环地址)
#define PORT 8888 // 服务器端口号
#define MAX_BUFFER 1024 // 缓冲区大小int main() {int client_fd; // 客户端套接字描述符struct sockaddr_in server_addr; // 服务器地址结构体char buffer[MAX_BUFFER]; // 数据缓冲区// 1. 创建套接字// 函数原型:int socket(int domain, int type, int protocol);// 参数说明同服务器端client_fd = socket(AF_INET, SOCK_STREAM, 0);if (client_fd == -1) {perror("socket failed");exit(EXIT_FAILURE);}// 2. 设置服务器地址memset(&server_addr, 0, sizeof(server_addr)); // 清零地址结构体server_addr.sin_family = AF_INET; // IPv4地址族server_addr.sin_port = htons(PORT); // 端口号(网络字节序)server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); // 服务器IP地址// 3. 连接服务器// 函数原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);// 参数说明:// sockfd: 客户端套接字// addr: 服务器地址结构体// addrlen: 地址结构体大小// 返回值:成功返回0,失败返回-1if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("connect failed");close(client_fd);exit(EXIT_FAILURE);}printf("连接服务器成功!输入字符串,服务器将返回大写版本。\n");// 4. 收发数据while (1) {printf("请输入: ");fgets(buffer, MAX_BUFFER, stdin); // 从标准输入读取一行数据if (strncmp(buffer, "exit", 4) == 0) break; // 输入exit退出// 发送数据到服务器send(client_fd, buffer, strlen(buffer), 0);// 接收服务器返回的数据int n = recv(client_fd, buffer, MAX_BUFFER, 0);if (n <= 0) break; // 服务器关闭或出错buffer[n] = '\0'; // 添加字符串结束符printf("服务器返回: %s", buffer);}close(client_fd); // 关闭客户端套接字return 0;
}
🛠️ 第四部分:编译与运行
编译命令详解
# 编译服务器程序
gcc tcp_server.c -o server# 编译客户端程序
gcc tcp_client.c -o client
命令解释:
gcc
: GNU C编译器tcp_server.c
: 源代码文件名-o server
: 指定输出可执行文件名为servertcp_client.c
: 客户端源代码文件名-o client
: 指定输出可执行文件名为client
运行方式
# 终端1:启动服务器
./server# 终端2:启动客户端
./client
🧪 第五部分:测试与验证
示例交互
客户端输入:
hello world
服务器输出:
客户端连接成功: 127.0.0.1:54321
收到数据: hello world
客户端输出:
服务器返回: HELLO WORLD
📌 总结
✅ 今天你学会了:
- TCP/IP 四层网络模型的基本概念
- TCP 协议的特点和应用场景
- 字节序的概念及转换函数的使用
- TCP 服务器与客户端开发的完整流程
- 套接字编程中各个关键函数的详细用法
- 编写并运行了一个 TCP 回声 + 大写转换服务器
🎯 明天我们将学习 UDP 协议,体验无连接的轻快通信方式!
📌 Tips:
- 多动手敲代码,不要只看
- 多用
man
命令查看函数文档(如man socket
、man bind
等) - 服务器运行后,不要关闭终端,保持运行状态
- 理解每个函数的参数含义,这是网络编程的基础
👉 点个赞和关注,更多知识不迷路!!明天见!Day 2 更精彩:UDP 无连接通信实战!