Uchicago的chirc项目的随笔

chirc

以下RFC对IRC协议的描述

  • [RFC2810] Internet Relay Chat:Architecture。 本文档描述了 IRC 的整体架构。

  • [RFC2811] Internet Relay Chat: Channel Management。本文档描述了如何在 IRC 中管理频道(channels)。

  • [RFC2812] Internet Relay Chat: Client Protocol。本文档描述了 IRC 客户端和服务器之间使用的协议(有时称为“客户端 - 服务器”协议)。

    1. ,每个命令对应的Command responsesRFC2812第5章 https://www.rfc-editor.org/rfc/rfc2812?utm_source=chatgpt.com#section-5.1
    2. 第三章为每个命令的detail https://www.rfc-editor.org/rfc/rfc2812?utm_source=chatgpt.com#section-3
  • [RFC2813] Internet Relay Chat: Server Protocol。本文档描述了同一网络中 IRC 服务器之间使用的“服务器 - 服务器”协议。

软件要求

alt text

项目的构建和执行

  1. Once you have done this, simply run make inside the build directory to build chirc:
cd build/
make
  1. 只允许修改src/文件夹下的内容和CMakeLists.txt
  2. If you add .c files to the src/ directory, you must also add that file to the list of files specified in the add_executable command in the CMakeLists.txt file.
  3. 增加第三方库:If you want to use third-party libraries, add them inside the lib/ directory (do not add them in the src/ directory!). If the third-party library has header files you need to #include in your code, do not copy the header files into the src/ directory. Instead, add the library’s directory to the list of directories in the include_directories command in the CMakeLists.txt file. This way, you will be able to #include header files in those directories.
  4. 如果你想让 make 输出更详细的信息(包括它在构建过程中实际执行的具体命令),只需要这样运行 make:
make VERBOSE=1
  1. 运行
./chirc -o foobar

Message部分

每条 IRC 消息最多由三部分组成:前缀(可选)、命令 和 命令参数(最多 15 个)
在 IRC 协议中,一条消息的结构大致是:

[:prefix](optional) command [params] [:trailing]
前缀      命令     参数      

前缀、命令和所有参数之间都使用一个 ASCII 空格字符(0x20)进行分隔。

消息类型:

登录到IRC服务器

当 IRC 客户端连接到 IRC 服务器时,必须首先注册其连接
alt text

所有可能的回复代码都在 RFC2812 第5节中定义。

用户间的消息传递

聊天中继,以及相关的格式

加入,聊天,以及离开频道

连接到 IRC 服务器的用户可以使用 JOIN 消息加入现有频道。消息本身的格式非常简单(唯一的参数是用户想要加入的频道的名称)
中文任务书中提到的,频道"运营商"和"操作员"其实就是频道的管理者,具有特权模式
一旦用户加入了频道,向频道发送消息与向个人用户发送消息基本相同

使用项目文件的注意事项

  1. 要修改代码,只需将文件添加到 src /目录。请注意,如果添加其他.c 文件,则需要修改Makefile 文件,以便把它们包含在构建中
  2. chirc 服务器使用简单日志记录函数 chilog()将消息打印到标准输出,该函数在 src /
    log.h 中声明。如果需要将消息打印到标准输出,则必须使用 chilog()函数。 这是一个简单的函数,它需与 printf 相同的参数,以及指定日志记录级别的附加参数

测试和调试

1. 分别自动测试(用于评分)和手动测试(用于debug)

2.

alt text

3. pytest具有调试功能

3.1 罗列category
make categories-assignment-N

alt text

3.2运行某一类测试
pytest ../tests/ --chirc-category PRIVMSG_NOTICE
3.3 运行某一个测试
pytest ../tests/ -k test_connect_both_messages_at_once

可以找到的测试名

===================================== FAILURES =====================================
______________ TestBasicConnection.test_connect_both_messages_at_once ______________
  1. 可以通过手动连接服务器的方式进行更交互式的调试。
    只需运行服务器,比如:
make && ./chirc -o foobar -p 7776

然后使用 telnet 连接:

telnet localhost 7776

这样你就能直接和 IRC 协议交互。
例如,注册一个用户,你需要在 telnet 里输入:

NICK user1

按回车(发送 \r\n)后,接着输入:

USER user1 * * :User One

再按回车。如果你的服务器实现正确,telnet 会显示服务器对这两个命令的欢迎回复。
登录成功后,你可以手动测试其他 IRC 命令。

C语言语法知识部分

🔚 总结:
struct a {}:定义了一个具名结构体类型,使用时要加 struct。
typedef struct {} a;:定义了一个匿名结构体,但起了个类型别名 a。推荐使用 typedef struct a { ... } a;,既支持递归定义又使用方便。
typedef 是为了让你可以用 chirc_connection_t 这个简洁的名字来代替 struct chirc_connection,提高代码可读性和一致性。

  1. 函数注释的一些规范
/*! \brief Process (handle) a message received by the server
 *
 * \param ctx Server context
 * \param conn Connection the message arrived through
 * \param msg The message to process
 * \return If the message was handled correctly, returns 0 (CHIRC_OK).
 *         In some commands, the expected outcome of the command is
 *         for the connection to be closed (e.g., the QUIT command)
 *         In those cases, chirc_handle will return -42 (CHIRC_HANDLER_DISCONNECT).
 *         If the handling of the message fails,  a non-zero value
 *         (other than -42) will be returned.
 */
int chirc_handle(chirc_ctx_t *ctx, chirc_connection_t *conn, chirc_message_t *msg);
  1. sds 是 Simple Dynamic Strings 的缩写,是 Redis 项目中使用的一种 C 字符串封装,旨在解决标准 C 字符串(char)存在的一些问题,例如:长度获取效率低(strlen 是 O(n))、不安全(容易缓冲区溢出)、无法记录字符串容量、字符串拼接效率低。sds 是一个结构体,而不是简单的 char,它通常定义如下(简化):
struct sdshdr {
    int len;       // 当前字符串长度
    int free;      // 分配的空间中剩余多少
    char buf[];    // 实际字符串数据(以 '\0' 结尾)
};

sds 类型本质是 char*,但指向的是 sdshdr.buf。通过这个封装,sds 可以:O(1) 得到长度(直接访问 len 字段,而不是调用 strlen)、安全拼接(自动扩展空间)、支持二进制数据(不强制 '\0' 作为结束符,更方便内存管理)

  1. alt text

  2. 在 C99 中,#include <stdbool.h> 后的 bool 是 _Bool 的别名,占用 1 字节,其合法值是 0(false)或 1(true),但它本质上是一个整数类型,可以被赋任何非 0 值,只是非 0 值在逻辑判断中会当作 true。所以,bool b = 128; 是合法的,虽然它语义上不正确。

  3. 段错误
    “段错误”(Segmentation Fault,简称 Segfault)是 C/C++ 程序中最常见的运行时错误之一,表示程序访问了不允许访问的内存区域。常见于指针未初始化,为垃圾指针

  • 为什么访问空指针会报段错误?空指针 (NULL) 是一个特殊的地址,表示“没有指向任何有效内存”。当你写 msg->params[0] 是 NULL 时,msg->params[0][0] 实际是去访问“空地址上的数据”,操作系统为了保护内存安全,会拦截这种非法访问,直接抛出“段错误(Segmentation Fault)”,这样程序就会崩溃,防止对非法内存的写入或读取。
  1. C语言中哈希表的使用,示例:
chirc_channeluser_t *cu, *tmp;
HASH_ITER(hh_from_channel, channel->users, cu, tmp)
{
    construct_transmit_msg("JOIN",ctx,cu->user->conn,params,"",prefix);   
}
  1. C语言的条件表达式是按从左到右顺序执行的

项目过程笔记

  1. 任务1,2,3没有网络模式
  2. chirc_ctx_t这个结构体比较重要,关系服务器的上下文管理
  3. 任务1输出调试:
NICK nick42  
USER user42 * * :Some user
  1. 在 RFC 1459 中确实提到了 IRC 协议的消息格式要求,具体如下:在 2.3 Messages 部分提到:
    IRC messages are always lines of characters terminated with a CR-LF (Carriage Return - Line Feed) pair, and these messages shall not exceed 512 characters in length, counting all characters including the trailing CR-LF.

  2. chirc_message_add_parameter
    这个函数用于向消息中添加额外的参数(从第二个参数以后)。

  3. 对于字符串的操作,可以对其所在的空间,进行不同长度的切割操作

  4. 共享变量标记。

    chirc_ctx_t ctx;

读写锁是一种线程同步机制,它允许:多个线程同时读取共享资源(读-读不冲突)。但在写操作时必须独占(读-写、写-写互斥)。相比互斥锁(pthread_mutex_t),读写锁可以提升读密集型程序的并发性。

  1. C语言中要先检查一个指针是否为空,才能进一步访问,不然就会出现段错误

  2. 你这个问题的本质是 “重复定义变量”而不是共享变量”,违反了 C 的链接规则

  3. 头文件中的声明与定义

  • 区分好定义和声明
    案例:
    在头文件(reply.h)里:
char *reply_code_msg[512];  // ❌ 这不是声明,是定义!

reply.c 里:

reply_code_msg[431] = "No nickname given";  // ✔ 这是初始化

但是在 其他 .c 文件中 #include "reply.h" 的时候,因为 reply.h 里是一个定义,会导致每个 .c 文件都生成了一个 新的独立的 reply_code_msg[512] 数组

所以你在别处使用 reply_code_msg[431]不是访问的你初始化的那个,而是访问了一个“没初始化的别的数组”,结果就是值为 NULL。

🧠 小结:为什么你原来那样不行?

  • 你把定义写进了头文件:头文件被多个 .c 引用时,造成多个定义,链接器冲突或运行时行为异常
  • 所以你初始化的 reply_code_msg[431] = ... 根本不作用于你调用的那个数组
  • 解决方法就是:在头文件中用 extern 声明,在 .c 文件中定义
  1. 区别好不同连接的状态
posted @ 2025-07-14 16:16  www-light  阅读(6)  评论(0)    收藏  举报