源码学习:smallchat

源码:https://github.com/antirez/smallchat
可用于学习网络通信,源码很小。

考虑先基于第一版进行分析:
https://github.com/antirez/smallchat/blob/Episode-1/smallchat.c

先给出readme文件的翻译:
Smallchat
TLDR: 这只是一个我为几位朋友提供的编程示例。我上传了一个视频到我的YouTube频道,放大了代码,以查看从这个如此简单且故意破碎的示例中可以学到什么。将会有更多的视频和改进,详见本README文件末尾。

现在,让我来讲述完整的故事:

昨天我与我的几位朋友交谈,他们大多是前端开发者,对系统编程有些距离。我们在回忆IRC的旧时光。不可避免地,我说:编写一个非常简单的IRC服务器是每个人都应该做的经历(我向他们展示了我用TCL编写的实现,我很震惊我18年前就写了它:时间过得真快)。像那样的程序中有一些非常有趣的部分。一个进程执行多路复用,获取客户端状态,并在客户端有新数据时尝试快速访问这种状态,等等。

但后来讨论演变,我想,我来展示一个非常简单的C语言示例给你们看。你能写出最小的聊天服务器是什么样的?首先,要真正简化,我们不应该要求任何正式的客户端。即使不太完善,它应该能够与telnet或nc(netcat)一起使用。服务器的主要操作只是接收一些聊天行并将其发送给所有其他客户端,有时被称为扇出操作。但是,这将需要一个适当的readline()函数,然后进行缓冲等等。我们希望它更简单:让我们使用内核缓冲区来欺骗,假装我们每次都从客户端接收到一个完整形式的行(在实践中,这种假设通常成立,所以事情有点能用)。

好吧,通过这些技巧,我们可以实现一个聊天,甚至可以让用户在只有200行代码的情况下设置他们的昵称(当然要删除空格和注释)。由于我将这个小程序作为给我的朋友的示例,我决定也将其推送到Github上。

未来的工作
在接下来的几天里,我将继续修改这个程序以发展它。不同的演变步骤将根据我关于编写系统软件系列的YouTube剧集中的更改进行标记。这是我的计划(可能会改变,但大致上这是我想要涵盖的内容):

  1. 实现读写缓冲。
  2. 避免线性数组,使用字典数据结构来保存客户端状态。
  3. 编写一个适当的客户端:能够处理异步事件的行编辑。
  4. 实现频道。
  5. 从select(2)切换到更高级的API。
  6. 为聊天实现简单的对称加密。

不同的更改将由一个或多个YouTube视频进行介绍。完整的提交历史将保存在这个存储库中。

下面是第一版的源码,后面会与第二版源码进行对比说明:
smallchat.c – 读取客户端输入,发送给所有其他连接的客户端。
数据结构:最小化,且简单。实际核心就一个chatState对象Chat。

#define MAX_CLIENTS 1000 // This is actually the higher file descriptor. 客户端总数限制
#define SERVER_PORT 7711 //服务器端口
/* This structure represents a connected client. There is very little
 * info about it: the socket descriptor and the nick name, if set, otherwise
 * the first byte of the nickname is set to 0 if not set.
 * The client can set its nickname with /nick  command. */
struct client {
    int fd;     // Client socket. 客户端的套接字描述符,用于与客户端通信。
    char *nick; // Nickname of the client. 客户名,默认为0
};
/* This global structure encasulates the global state of the chat. 封装聊天程序的全局状态信息。*/
struct chatState {
    int serversock;     // Listening server socket. 服务器的监听套接字,用于接受客户端连接请求。
    int numclients;     // Number of connected clients right now.当前连接的客户端数量。
    int maxclient;      // The greatest 'clients' slot populated.最大的 'clients' 槽位被占用的索引。
    struct client *clients[MAX_CLIENTS]; // Clients are set in the corresponding slot of their socket descriptor.客户端数组,用于存储连接到服务器的客户端信息。每个客户端通过其套接字描述符在数组中找到对应的位置。
};
struct chatState *Chat; // Initialized at startup.指向 struct chatState 结构的指针,用于表示整个聊天程序的全局状态。在程序启动时会初始化该结构。

api:最基本的socket
我调整一下学习顺序,从低到高,按被调用顺序看

先看内存申请。其实只是为了在内存不足时,先输出错误信息,并正常退出返回1,避免崩溃

/* We also define an allocator that always crashes on out of memory: you
 * will discover that in most programs designed to run for a long time, that
 * are not libraries, trying to recover from out of memory is often futile
 * and at the same time makes the whole program terrible. */
void *chatMalloc(size_t size) {
    void *ptr = malloc(size);
    if (ptr == NULL) {
        perror("Out of memory");
        exit(1);
    }
    return ptr;
}
/* Also aborting realloc(). */
void *chatRealloc(void *ptr, size_t size) {
    ptr = realloc(ptr,size);
    if (ptr == NULL) {
        perror("Out of memory");
        exit(1);
    }
    return ptr;
}

初始化全局的唯一的数据结构对象,创建tcp服务器

/* Create a TCP socket lisetning to 'port' ready to accept connections. */
int createTCPServer(int port) {
    int s, yes = 1;
    struct sockaddr_in sa;
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1;
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); // Best effort.
    memset(&sa,0,sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(port);
    sa.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(s,(struct sockaddr*)&sa,sizeof(sa)) == -1 ||
        listen(s, 511) == -1)
    {
        close(s);
        return -1;
    }
    return s;
}
/* Allocate and init the global stuff. */
void initChat(void) {
    Chat = chatMalloc(sizeof(*Chat));
    memset(Chat,0,sizeof(*Chat));
    /* No clients at startup, of course. */
    Chat->maxclient = -1;
    Chat->numclients = 0;
    /* Create our listening socket, bound to the given port. This
     * is where our clients will connect. */
    Chat->serversock = createTCPServer(SERVER_PORT); //创建tcp服务器
    if (Chat->serversock == -1) {
        perror("Creating listening socket");
        exit(1);
    }
}

你可能感兴趣的:(code,c,c)