云计算百科
云计算领域专业知识百科平台

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 套接字编程的基本步骤

创建套接字:

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协议实现安全通信
  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » C语言网络编程:TCP/IP协议栈、套接字、服务器/客户端通信深度解析
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!