关注

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 课后练习

  1. 编写程序:使用TCP协议实现服务器和客户端通信
  2. 编写程序:使用UDP协议实现服务器和客户端通信
  3. 编写程序:使用HTTP协议实现简单的Web服务器
  4. 编写程序:使用FTP协议实现文件传输
  5. 编写程序:使用SMTP协议实现邮件发送
  6. 编写程序:使用POP3协议实现邮件接收
  7. 编写程序:使用DNS协议实现域名解析
  8. 编写程序:使用ICMP协议实现Ping功能
  9. 编写程序:使用ARP协议实现地址解析
  10. 编写程序:使用SSL/TLS协议实现安全通信

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/COLLINSXU/article/details/157650941

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--