《密码系统设计》第十一周预习

20231313 张景云《密码系统设计》第十一周预习


AI对内容的总结Headfirst C

一、核心概念

  1. 网络通信基础:网络程序由服务器和客户端组成,通过“协议”(结构化对话规则)实现数据交互,如自定义的IKKP协议、通用的HTTP协议。
  2. 套接字(Socket):网络通信的核心数据流,双向支持输入输出,需通过专用函数(send/recv)操作,区别于文件、标准I/O数据流。
  3. 端口与IP:端口(如30000、80)用于区分同一主机上的不同服务,IP地址(如127.0.0.1)定位网络中的主机,域名(如www.oreilly.com)可通过DNS解析为IP。

二、服务器开发核心流程

1. BLAB四部曲

  • 绑定(Bind):将套接字与指定端口关联,需定义sockaddr_in结构指定协议族、端口和IP(INADDR_ANY表示监听所有本地IP)。
  • 监听(Listen):设置客户端连接等待队列长度(如10),超出队列的连接会被拒绝。
  • 接受(Accept):阻塞等待客户端连接,连接成功后返回新的通信套接字(与监听套接字分离)。
  • 开始(Begin):通过新套接字与客户端通信,通信结束后关闭套接字。

2. 关键技术

  • 错误处理:必须检查socket、bind、listen、accept等系统调用的返回值,避免端口占用等问题。
  • 端口复用:通过setsockopt设置SO_REUSEADDR选项,解决服务器重启时端口占用延时问题。
  • 多客户端支持:使用fork()创建子进程处理单个客户端连接,父进程继续监听新连接,实现并发服务。
  • 数据读写:用send()发送数据,recv()读取数据;recv()可能需多次调用才能获取完整数据,可封装为read_in()函数简化操作。

三、客户端开发核心流程

  1. 创建套接字:与服务器套接字创建方式一致(socket(PF_INET, SOCK_STREAM, 0))。
  2. 连接远程服务
    • 直接使用IP地址:通过sockaddr_in结构指定目标IP和端口,调用connect()连接。
    • 使用域名:通过getaddrinfo()解析域名获取IP,自动适配多个IP地址,连接后需用freeaddrinfo()释放资源。
  3. 数据交互:连接成功后,通过send()发送请求(如HTTP的GET命令),recv()接收服务器响应。

四、核心函数与工具

1. 关键函数

函数用途 核心函数 功能说明
套接字操作 socket()、bind()、listen()、accept() 创建、绑定、监听、接受连接
数据传输 send()、recv() 发送数据、读取数据
地址解析 getaddrinfo()、freeaddrinfo() 域名解析为IP、释放解析资源
辅助功能 setsockopt()、inet_addr()、htons() 设置套接字选项、IP格式转换、端口字节序转换

2. 测试工具

  • telnet:快速测试服务器连接与通信,如telnet 127.0.0.1 30000
  • gcc编译:编译服务器/客户端代码,如gcc ikkp_server.c -o ikkp_server

五、实战案例

  1. 忠告服务器:随机向客户端发送忠告,核心演示BLAB流程与基础数据发送。
  2. IKKP笑话服务器:实现“敲门笑话”结构化对话,支持错误校验(如客户端未按规则回复时关闭连接),并通过fork()支持多客户端并发。
  3. HTTP网页客户端:解析域名、连接维基百科服务器,发送GET请求并接收网页内容,演示客户端完整工作流。

六、注意事项

  1. 网络错误频发,所有系统调用必须添加错误处理逻辑。
  2. 1024以下端口通常为系统知名服务占用,普通用户需使用1024以上端口。
  3. 子进程处理完客户端后需关闭通信套接字并退出,避免资源泄漏。
  4. 客户端发送HTTP请求需遵循协议格式:包含GET命令、Host字段和空行。

对 AI 总结的反思


1. 字节序处理的重要性

// 网络字节序转换函数族
htons() // host to network short (16位)
htonl() // host to network long (32位)  
ntohs() // network to host short
ntohl() // network to host long

这是网络编程中极易出错但至关重要的一点,不同架构的CPU字节序不同,必须统一为网络字节序。

2. recv()函数的复杂性

您提到recv()可能需要多次调用,这里需要强调几个关键点:

// recv()返回值含义:
// >0: 实际接收的字节数
// =0: 连接已关闭
// -1: 发生错误

而且recv()是阻塞调用,在没有数据时会一直等待。

3. fork()并发模型的局限性

虽然fork()简单易用,但在实际生产环境中存在:

  • 资源消耗大:每个连接一个进程
  • 进程间通信复杂
  • 扩展性有限

可以补充说明还有select/poll/epoll等I/O多路复用技术。

4. 协议设计的细节

在IKKP协议示例中,有几个重要细节:

// 必须使用\r\n而不是\n
"Knock! Knock!\r\n"
// 字符串比较要忽略大小写
strncasecmp("Who's there?", buf, 12)

5. 信号处理的重要性

// 处理Ctrl+C优雅关闭
signal(SIGINT, handle_shutdown);

这在服务器程序中是必要的,避免强制关闭导致端口占用。

建议的速查表补充内容

如果您制作速查表,建议包含:

错误处理模式

if (system_call() == -1) {
    perror("description");
    exit(1);
}

常用端口号

  • 80: HTTP
  • 443: HTTPS
  • 22: SSH
  • 30000: 示例端口

调试技巧

  • 使用netstat -tulpn查看端口占用
  • 使用strace跟踪系统调用

mermaid 代码与截图

  root(C语言网络编程)
    核心概念
      网络通信基础
        服务器-客户端模型
        协议:结构化对话规则
        IKKP/HTTP协议示例
      套接字
        双向数据流
        send/recv专用函数
        区别于文件I/O
      端口与IP
        端口区分服务
        IP地址定位主机
        DNS域名解析
    服务器开发
      BLAB四部曲
        Bind绑定端口
          sockaddr_in结构
          INADDR_ANY
        Listen监听队列
          队列长度设置
          连接排队机制
        Accept接受连接
          阻塞等待
          返回新套接字
        Begin开始通信
          数据传输
          关闭套接字
      关键技术
        错误处理
          系统调用返回值检查
          perror错误报告
        端口复用
          SO_REUSEADDR选项
          setsockopt设置
        多客户端并发
          fork创建子进程
          父子进程套接字管理
        数据读写
          send发送数据
          recv读取数据
          read_in封装函数
    客户端开发
      创建套接字
        socket函数
        PF_INET参数
      连接远程服务
        IP直连方式
          sockaddr_in结构
          connect连接
        域名解析方式
          getaddrinfo解析
          freeaddrinfo释放
      数据交互
        send发送请求
        recv接收响应
        HTTP协议格式
    核心函数
      套接字操作
        socket创建
        bind绑定
        listen监听
        accept接受
      数据传输
        send发送
        recv接收
      地址解析
        getaddrinfo解析
        freeaddrinfo释放
      辅助功能
        setsockopt选项设置
        inet_addrIP转换
        htons字节序转换
    实战案例
      忠告服务器
        随机发送忠告
        BLAB流程演示
      IKKP笑话服务器
        结构化对话
        错误校验
        fork并发
      HTTP网页客户端
        域名解析
        GET请求
        网页下载
    重要补充
      字节序处理
        htons端口转换
        htonlIP转换
        ntohs反向转换
      recv复杂性
        返回值含义
          0:连接关闭
          -1:错误
          >0:字节数
        阻塞调用特性
      fork局限性
        资源消耗大
        进程间通信复杂
        扩展性有限
      协议细节
        \\r\\n换行符
        strncasecmp比较
      信号处理
        SIGINT处理
       优雅关闭服务器
    工具与调试
      测试工具
        telnet连接测试
        gcc编译代码
      调试技巧
        netstat查看端口
        strace跟踪调用
      常用端口
        80:HTTP
        443:HTTPS
        22:SSH
        30000:示例

deepseek_mermaid_20251113_8a79a7

基于AI的学习

image


学习实践过程遇到的问题与解决方式(AI 驱动)


问题一:端口绑定失败 - "Address already in use"

问题描述

在开发 knock-knock 服务器时,经常遇到服务器重启后无法立即重新绑定端口的问题:

> ./server
Waiting for connection
^C
> ./server
Can't bind the port: Address already in use

问题分析

通过查询文档和向AI助手询问,了解到这是TCP协议的TIME_WAIT状态导致的:

  • 当服务器主动关闭连接时,端口会进入TIME_WAIT状态
  • 该状态默认持续60秒,期间端口不能被重用
  • 这是TCP协议确保数据包在网络中完全消失的机制

解决方案

使用 setsockopt() 设置 SO_REUSEADDR 选项:

// 在bind()调用前添加
int reuse = 1;
if (setsockopt(listener_d, SOL_SOCKET, SO_REUSEADDR, 
               (char*)&reuse, sizeof(int)) == -1) {
    error("无法设置套接字的\"重新使用端口\"选项");
}

// 然后再调用bind()
if (bind(listener_d, (struct sockaddr*)&name, sizeof(name)) == -1) {
    error("无法绑定端口");
}

验证结果

设置后服务器可以立即重启:

> ./server
Waiting for connection
^C
> ./server  # 立即重启成功
Waiting for connection

问题二:数据接收不完整 - recv()分批到达

问题描述

在实现HTTP客户端时,发现recv()不能一次性接收完整响应:

// 问题代码 - 期望一次recv()获取完整网页
char buffer[4096];
int bytes = recv(sockfd, buffer, sizeof(buffer), 0);
printf("Received: %s\n", buffer);  // 输出不完整!

问题分析

通过AI助手解释和查阅Linux手册,认识到:

  • recv()是面向流的,不保证返回所有请求的数据
  • 网络数据可能被分割成多个TCP包到达
  • 需要循环调用recv()直到获取完整数据

解决方案

实现read_in()函数处理分批到达的数据:

int read_in(int socket, char* buf, int len) {
    char* s = buf;
    int slen = len;
    int c = recv(socket, s, slen, 0);
    
    // 循环读取直到换行符或错误
    while ((c > 0) && (s[c-1] != '\n')) {
        s += c;
        slen -= c;
        c = recv(socket, s, slen, 0);
    }
    
    if (c < 0) return c;           // 错误
    else if (c == 0) buf[0] = '\0'; // 连接关闭
    else s[c-1] = '\0';            // 用\0替换\n
    
    return len - slen;
}

使用效果

char response[8192];
int total_bytes = read_in(sockfd, response, sizeof(response));
printf("完整响应(%d字节): %s\n", total_bytes, response);

参考资料

AI工具

  • 豆包
  • Deepseek

图书

  • 《Headfirst C》
posted @ 2025-11-13 18:52  Raymongillichmks  阅读(9)  评论(0)    收藏  举报