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 客户端和服务器之间使用的协议(有时称为“客户端 - 服务器”协议)。
- ,每个命令对应的Command responsesRFC2812第5章 https://www.rfc-editor.org/rfc/rfc2812?utm_source=chatgpt.com#section-5.1
- 第三章为每个命令的detail https://www.rfc-editor.org/rfc/rfc2812?utm_source=chatgpt.com#section-3
-
[RFC2813] Internet Relay Chat: Server Protocol。本文档描述了同一网络中 IRC 服务器之间使用的“服务器 - 服务器”协议。
软件要求
项目的构建和执行
- Once you have done this, simply run make inside the build directory to build chirc:
cd build/
make
- 只允许修改src/文件夹下的内容和
CMakeLists.txt
- 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.
- 增加第三方库: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.
- 如果你想让 make 输出更详细的信息(包括它在构建过程中实际执行的具体命令),只需要这样运行 make:
make VERBOSE=1
- 运行
./chirc -o foobar
Message部分
每条 IRC 消息最多由三部分组成:前缀(可选)、命令 和 命令参数(最多 15 个)。
在 IRC 协议中,一条消息的结构大致是:
[:prefix](optional) command [params] [:trailing]
前缀 命令 参数
前缀、命令和所有参数之间都使用一个 ASCII 空格字符(0x20)进行分隔。
消息类型:
登录到IRC服务器
当 IRC 客户端连接到 IRC 服务器时,必须首先注册其连接
所有可能的回复代码都在 RFC2812 第5节中定义。
用户间的消息传递
聊天中继,以及相关的格式
加入,聊天,以及离开频道
连接到 IRC 服务器的用户可以使用 JOIN 消息加入现有频道。消息本身的格式非常简单(唯一的参数是用户想要加入的频道的名称)
中文任务书中提到的,频道"运营商"和"操作员"其实就是频道的管理者,具有特权模式
一旦用户加入了频道,向频道发送消息与向个人用户发送消息基本相同
使用项目文件的注意事项
- 要修改代码,只需将文件添加到 src /目录。请注意,如果添加其他.c 文件,则需要修改Makefile 文件,以便把它们包含在构建中
- chirc 服务器使用简单日志记录函数 chilog()将消息打印到标准输出,该函数在 src /
log.h 中声明。如果需要将消息打印到标准输出,则必须使用 chilog()函数。 这是一个简单的函数,它需与 printf 相同的参数,以及指定日志记录级别的附加参数
测试和调试
1. 分别自动测试(用于评分)和手动测试(用于debug)
2.
3. pytest具有调试功能
3.1 罗列category
make categories-assignment-N
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 ______________
- 可以通过手动连接服务器的方式进行更交互式的调试。
只需运行服务器,比如:
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,提高代码可读性和一致性。
- 函数注释的一些规范
/*! \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);
- 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' 作为结束符,更方便内存管理)
-
-
在 C99 中,#include <stdbool.h> 后的 bool 是 _Bool 的别名,占用 1 字节,其合法值是 0(false)或 1(true),但它本质上是一个整数类型,可以被赋任何非 0 值,只是非 0 值在逻辑判断中会当作 true。所以,
bool b = 128
; 是合法的,虽然它语义上不正确。 -
段错误
“段错误”(Segmentation Fault,简称 Segfault)是 C/C++ 程序中最常见的运行时错误之一,表示程序访问了不允许访问的内存区域。常见于指针未初始化,为垃圾指针
- 为什么访问空指针会报段错误?空指针 (NULL) 是一个特殊的地址,表示“没有指向任何有效内存”。当你写 msg->params[0] 是 NULL 时,msg->params[0][0] 实际是去访问“空地址上的数据”,操作系统为了保护内存安全,会拦截这种非法访问,直接抛出“段错误(Segmentation Fault)”,这样程序就会崩溃,防止对非法内存的写入或读取。
- 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);
}
- C语言的条件表达式是按从左到右顺序执行的
项目过程笔记
- 任务1,2,3没有网络模式
chirc_ctx_t
这个结构体比较重要,关系服务器的上下文管理- 任务1输出调试:
NICK nick42
USER user42 * * :Some user
-
在 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. -
chirc_message_add_parameter
这个函数用于向消息中添加额外的参数(从第二个参数以后)。 -
对于字符串的操作,可以对其所在的空间,进行不同长度的切割操作
-
共享变量标记。
chirc_ctx_t ctx;
读写锁是一种线程同步机制,它允许:多个线程同时读取共享资源(读-读不冲突)。但在写操作时必须独占(读-写、写-写互斥)。相比互斥锁(pthread_mutex_t),读写锁可以提升读密集型程序的并发性。
-
C语言中要先检查一个指针是否为空,才能进一步访问,不然就会出现段错误
-
你这个问题的本质是 “重复定义变量”而不是共享变量”,违反了 C 的链接规则。
-
头文件中的声明与定义
- 区分好定义和声明
案例:
在头文件(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
文件中定义
- 区别好不同连接的状态
Light 你和我—本文来自博客园,作者:www-light,转载请注明原文链接:https://www.cnblogs.com/wlighten/p/18984220