C语言网络编程:TCP/IP协议栈、套接字、服务器/客户端通信深度解析
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 套接字编程的基本步骤
创建套接字:
intsocket(int domain,int type,int protocol);绑定地址:
intbind(int sockfd,conststructsockaddr*addr,socklen_t addrlen);监听连接:
intlisten(int sockfd,int backlog);接受连接:
intaccept(int sockfd,structsockaddr*addr,socklen_t*addrlen);发送和接收数据:
ssize_tsend(int sockfd,constvoid*buf,size_t len,int flags);ssize_trecv(int sockfd,void*buf,size_t len,int flags);关闭套接字:
intclose(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>#definePORT8080#defineBUFFER_SIZE1024intmain(){int server_fd, new_socket;structsockaddr_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,(structsockaddr*)&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,(structsockaddr*)&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);return0;}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>#definePORT8080#defineBUFFER_SIZE1024intmain(){int sock =0;structsockaddr_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,(structsockaddr*)&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);return0;}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>#definePORT8080#defineBUFFER_SIZE1024intmain(){int sockfd;structsockaddr_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,(structsockaddr*)&servaddr,sizeof(servaddr))<0){perror("bind() error");exit(EXIT_FAILURE);} len =sizeof(cliaddr);// 接收数据recvfrom(sockfd, buffer, BUFFER_SIZE,0,(structsockaddr*)&cliaddr,&len);printf("收到客户端消息:%s\n", buffer);// 发送数据sendto(sockfd, hello,strlen(hello),0,(structsockaddr*)&cliaddr, len);printf("向客户端发送消息:%s\n", hello);// 关闭套接字close(sockfd);return0;}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>#definePORT8080#defineBUFFER_SIZE1024intmain(){int sockfd;structsockaddr_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,(structsockaddr*)&servaddr, len);printf("向服务器发送消息:%s\n", hello);// 接收数据recvfrom(sockfd, buffer, BUFFER_SIZE,0,(structsockaddr*)&servaddr,&len);printf("收到服务器消息:%s\n", buffer);// 关闭套接字close(sockfd);return0;}五、模块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>#definePORT8080#defineBUFFER_SIZE4096#defineMAX_CLIENTS10voidhandle_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);}intmain(){int server_fd, new_socket;structsockaddr_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,(structsockaddr*)&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,(structsockaddr*)&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,(structsockaddr*)&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;}}}}return0;}六、本章总结与课后练习
6.1 总结
✅ TCP/IP协议栈:用于网络通信的协议集合,分为应用层、传输层、网络层、数据链路层
✅ 套接字编程:网络通信的端点,用于实现服务器和客户端之间的通信
✅ 服务器/客户端通信:服务器监听连接,客户端发起连接,实现数据传输
✅ 实战案例分析:HTTP服务器开发,使用套接字编程和HTTP协议实现网络应用
6.2 课后练习
- 编写程序:使用TCP协议实现服务器和客户端通信
- 编写程序:使用UDP协议实现服务器和客户端通信
- 编写程序:使用HTTP协议实现简单的Web服务器
- 编写程序:使用FTP协议实现文件传输
- 编写程序:使用SMTP协议实现邮件发送
- 编写程序:使用POP3协议实现邮件接收
- 编写程序:使用DNS协议实现域名解析
- 编写程序:使用ICMP协议实现Ping功能
- 编写程序:使用ARP协议实现地址解析
- 编写程序:使用SSL/TLS协议实现安全通信