C语言网络编程:TCP/IP协议栈、套接字、服务器/客户端通信深度解析

一、前言:为什么网络编程是C语言开发的重要技能?
学习目标
- 理解网络编程的本质:编写程序实现不同设备之间的网络通信
- 明确网络编程的重要性:支撑互联网、物联网、云计算等应用的基础
- 掌握本章学习重点:TCP/IP协议栈、套接字、服务器/客户端通信的开发方法、避坑指南、实战案例分析
- 学会使用C语言开发网络应用,实现数据传输和网络交互
重点提示
💡 网络编程是C语言开发的重要技能!互联网和物联网的普及,使得网络编程成为程序员的必备技能,C语言的高性能和可移植性使其在网络编程中具有重要地位。
二、模块1:TCP/IP协议栈基础
2.1 学习目标
- 理解TCP/IP协议栈的本质:用于网络通信的协议集合,分为应用层、传输层、网络层、数据链路层
- 掌握TCP/IP协议栈的结构:各层协议的功能和交互
- 掌握TCP/IP协议栈的常用协议:TCP、UDP、IP、HTTP、FTP等
- 掌握TCP/IP协议栈的避坑指南:避免协议栈配置错误、避免数据传输错误、避免连接失败
- 避开TCP/IP协议栈使用的3大常见坑
2.2 TCP/IP协议栈的结构
应用层:提供用户服务,如HTTP、FTP、SMTP等
传输层:提供端到端的通信,如TCP、UDP
网络层:提供路由和转发功能,如IP、ICMP
数据链路层:提供物理连接和帧传输,如以太网、WiFi
2.3 TCP/IP协议栈的常用协议
TCP协议:
- 可靠的、面向连接的传输协议
- 重传机制、滑动窗口、拥塞控制
- 适合要求高可靠性的应用,如HTTP、FTP
UDP协议:
- 不可靠的、无连接的传输协议
- 不需要建立连接,传输速度快
- 适合要求高传输速度的应用,如视频会议、游戏
三、模块2:套接字编程基础
3.1 学习目标
- 理解套接字的本质:网络通信的端点,用于实现服务器和客户端之间的通信
- 掌握套接字编程的基本步骤:创建套接字、绑定地址、监听连接、接受连接、发送和接收数据、关闭套接字
- 掌握套接字编程的避坑指南:避免地址绑定错误、避免连接失败、避免数据传输错误
- 避开套接字编程使用的3大常见坑
3.2 套接字编程的基本步骤
创建套接字:
int socket(int domain, int type, int protocol);
绑定地址:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
监听连接:
int listen(int sockfd, int backlog);
接受连接:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
发送和接收数据:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
关闭套接字:
int close(int sockfd);
四、模块3:服务器/客户端通信开发
4.1 学习目标
- 理解服务器/客户端通信的本质:服务器监听连接,客户端发起连接,实现数据传输
- 掌握服务器/客户端通信的开发方法:使用TCP或UDP协议
- 掌握服务器/客户端通信的避坑指南:避免并发连接处理错误、避免数据传输错误、避免连接超时
- 避开服务器/客户端通信使用的3大常见坑
4.2 服务器/客户端通信的开发方法
TCP服务器:
#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, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
char *hello = "Hello from server";
// 创建套接字文件描述符
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket() error");
exit(EXIT_FAILURE);
}
// 设置套接字选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt() error");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定套接字到指定地址和端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind() error");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("listen() error");
exit(EXIT_FAILURE);
}
// 接受连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {
perror("accept() error");
exit(EXIT_FAILURE);
}
// 接收数据
read(new_socket, buffer, BUFFER_SIZE);
printf("收到客户端消息:%s\n", buffer);
// 发送数据
send(new_socket, hello, strlen(hello), 0);
printf("向客户端发送消息:%s\n", hello);
// 关闭套接字
close(new_socket);
close(server_fd);
return 0;
}
TCP客户端:
#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 sock = 0;
struct sockaddr_in serv_addr;
char buffer[BUFFER_SIZE] = {0};
char *hello = "Hello from client";
// 创建套接字文件描述符
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket() error");
exit(EXIT_FAILURE);
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// 将IPv4和IPv6地址从文本转换为二进制形式
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
perror("无效的地址/地址不可支持");
exit(EXIT_FAILURE);
}
// 连接服务器
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("连接失败");
exit(EXIT_FAILURE);
}
// 发送数据
send(sock, hello, strlen(hello), 0);
printf("向服务器发送消息:%s\n", hello);
// 接收数据
read(sock, buffer, BUFFER_SIZE);
printf("收到服务器消息:%s\n", buffer);
// 关闭套接字
close(sock);
return 0;
}
UDP服务器:
#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 sockfd;
struct sockaddr_in servaddr, cliaddr;
int opt = 1;
int len;
char buffer[BUFFER_SIZE] = {0};
char *hello = "Hello from UDP server";
// 创建套接字文件描述符
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket() error");
exit(EXIT_FAILURE);
}
// 设置套接字选项
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt() error");
exit(EXIT_FAILURE);
}
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
// 绑定套接字到指定地址和端口
if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind() error");
exit(EXIT_FAILURE);
}
len = sizeof(cliaddr);
// 接收数据
recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&cliaddr, &len);
printf("收到客户端消息:%s\n", buffer);
// 发送数据
sendto(sockfd, hello, strlen(hello), 0, (struct sockaddr *)&cliaddr, len);
printf("向客户端发送消息:%s\n", hello);
// 关闭套接字
close(sockfd);
return 0;
}
UDP客户端:
#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 sockfd;
struct sockaddr_in servaddr;
int len;
char buffer[BUFFER_SIZE] = {0};
char *hello = "Hello from UDP client";
// 创建套接字文件描述符
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket() error");
exit(EXIT_FAILURE);
}
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
len = sizeof(servaddr);
// 发送数据
sendto(sockfd, hello, strlen(hello), 0, (struct sockaddr *)&servaddr, len);
printf("向服务器发送消息:%s\n", hello);
// 接收数据
recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&servaddr, &len);
printf("收到服务器消息:%s\n", buffer);
// 关闭套接字
close(sockfd);
return 0;
}
五、模块4:实战案例分析——HTTP服务器开发
5.1 学习目标
- 掌握HTTP服务器开发:使用C语言实现一个简单的HTTP服务器,处理HTTP请求和响应
- 学会使用套接字编程和HTTP协议实现网络应用
- 避开实战案例使用的3大常见坑
5.2 HTTP服务器开发
代码示例5:HTTP服务器
#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 4096
#define MAX_CLIENTS 10
void handle_client(int new_socket) {
char buffer[BUFFER_SIZE] = {0};
char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 12\r\n\r\nHello, World!";
// 接收HTTP请求
read(new_socket, buffer, BUFFER_SIZE);
printf("收到HTTP请求:\n%s\n", buffer);
// 发送HTTP响应
send(new_socket, response, strlen(response), 0);
printf("向客户端发送响应:\n%s\n", response);
// 关闭套接字
close(new_socket);
}
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
int clients[MAX_CLIENTS];
int max_clients = MAX_CLIENTS;
int i, j, valread, sd;
int activity, max_sd;
fd_set readfds;
char buffer[BUFFER_SIZE] = {0};
// 初始化客户端数组
for (i = 0; i < max_clients; i++) {
clients[i] = 0;
}
// 创建套接字文件描述符
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket() error");
exit(EXIT_FAILURE);
}
// 设置套接字选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt() error");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定套接字到指定地址和端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind() error");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("listen() error");
exit(EXIT_FAILURE);
}
printf("HTTP服务器启动成功,监听端口:%d\n", PORT);
while (1) {
// 清空文件描述符集合
FD_ZERO(&readfds);
// 添加服务器套接字到集合
FD_SET(server_fd, &readfds);
max_sd = server_fd;
// 添加客户端套接字到集合
for (i = 0; i < max_clients; i++) {
sd = clients[i];
if (sd > 0) {
FD_SET(sd, &readfds);
}
if (sd > max_sd) {
max_sd = sd;
}
}
// 等待活动
activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);
if (activity < 0) {
perror("select() error");
}
// 如果服务器套接字有活动,说明有新连接
if (FD_ISSET(server_fd, &readfds)) {
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {
perror("accept() error");
exit(EXIT_FAILURE);
}
// 打印客户端信息
printf("新连接:套接字文件描述符=%d,IP=%s,端口=%d\n",
new_socket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));
// 将新套接字文件描述符添加到数组
for (i = 0; i < max_clients; i++) {
if (clients[i] == 0) {
clients[i] = new_socket;
break;
}
}
if (i == max_clients) {
printf("客户端数量已达上限!\n");
}
}
// 处理客户端活动
for (i = 0; i < max_clients; i++) {
sd = clients[i];
if (FD_ISSET(sd, &readfds)) {
valread = read(sd, buffer, BUFFER_SIZE);
if (valread == 0) {
// 客户端关闭连接
getpeername(sd, (struct sockaddr *)&address, (socklen_t *)&addrlen);
printf("客户端断开连接:IP=%s,端口=%d\n",
inet_ntoa(address.sin_addr), ntohs(address.sin_port));
close(sd);
clients[i] = 0;
} else {
// 处理HTTP请求
buffer[valread] = '\0';
handle_client(sd);
// 关闭套接字
close(sd);
clients[i] = 0;
}
}
}
}
return 0;
}
六、本章总结与课后练习
6.1 总结
✅ TCP/IP协议栈:用于网络通信的协议集合,分为应用层、传输层、网络层、数据链路层
✅ 套接字编程:网络通信的端点,用于实现服务器和客户端之间的通信
✅ 服务器/客户端通信:服务器监听连接,客户端发起连接,实现数据传输
✅ 实战案例分析:HTTP服务器开发,使用套接字编程和HTTP协议实现网络应用
6.2 课后练习
- 编写程序:使用TCP协议实现服务器和客户端通信
- 编写程序:使用UDP协议实现服务器和客户端通信
- 编写程序:使用HTTP协议实现简单的Web服务器
- 编写程序:使用FTP协议实现文件传输
- 编写程序:使用SMTP协议实现邮件发送
- 编写程序:使用POP3协议实现邮件接收
- 编写程序:使用DNS协议实现域名解析
- 编写程序:使用ICMP协议实现Ping功能
- 编写程序:使用ARP协议实现地址解析
- 编写程序:使用SSL/TLS协议实现安全通信
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/COLLINSXU/article/details/157650941



