跳到主要内容C语言网络编程:TCP/IP协议栈、套接字与通信实战 | 极客日志C
C语言网络编程:TCP/IP协议栈、套接字与通信实战
C语言网络编程涉及TCP/IP协议栈理解、套接字API使用及服务器客户端通信实现。内容涵盖TCP与UDP协议特性对比,Socket创建、绑定、监听、连接等核心步骤,并提供TCP/UDP及HTTP服务器的完整C代码示例。通过select模型演示多客户端并发处理,解析网络编程中的常见错误与配置要点,帮助开发者掌握底层网络交互机制。
心动瞬间24 浏览 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协议栈的避坑指南:避免协议栈配置错误、避免数据传输错误、避免连接失败
2.2 TCP/IP协议栈的结构
应用层:提供用户服务,如HTTP、FTP、SMTP等
传输层:提供端到端的通信,如TCP、UDP
网络层:提供路由和转发功能,如IP、ICMP
数据链路层:提供物理连接和帧传输,如以太网、WiFi
2.3 TCP/IP协议栈的常用协议
TCP协议:
- 可靠的、面向连接的传输协议
- 重传机制、滑动窗口、拥塞控制
- 适合要求高可靠性的应用,如HTTP、FTP
UDP协议:
- 不可靠的、无连接的传输协议
- 不需要建立连接,传输速度快
- 适合要求高传输速度的应用,如视频会议、游戏
三、模块2:套接字编程基础
3.1 学习目标
- 理解套接字的本质:网络通信的端点,用于实现服务器和客户端之间的通信
- 掌握套接字编程的基本步骤:创建套接字、绑定地址、监听连接、接受连接、发送和接收数据、关闭套接字
- 掌握套接字编程的避坑指南:避免地址绑定错误、避免连接失败、避免数据传输错误
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);
四、模块3:服务器/客户端通信开发
4.1 学习目标
- 理解服务器/客户端通信的本质:服务器监听连接,客户端发起连接,实现数据传输
- 掌握服务器/客户端通信的开发方法:使用TCP或UDP协议
- 掌握服务器/客户端通信的避坑指南:避免并发连接处理错误、避免数据传输错误、避免连接超时
4.2 服务器/客户端通信的开发方法
#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;
}
#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);
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;
}
#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;
}
#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协议实现网络应用
5.2 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!";
read(new_socket, buffer, BUFFER_SIZE);
printf("收到HTTP请求:\n%s\n", buffer);
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 {
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协议实现安全通信
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online