网络编程基础,纯C语言实现聊天室(附源代码)——从铁矿到钢铁的打造

1 概述

1.1聊天室设计内容

设计一个聊天系统,实现一下内容:

  1. 用户管理
    用户注册、登录、修改密码

  2. 聊天室管理
    用户登录、创建聊天室、设置聊天室密码、用户可以加入聊天室、退出聊天室

  3. 聊天管理
    在同一聊天室里,用户所发送的消息每位在线用户都可以收到,也可以单独给某位在线用户发消息;给所有在线用户群发消息

  4. 系统管理
    显示所有在线用户;显示所有聊天室;可以查询聊天室在线用户信息;提供命令帮助,让用户了解命令的格式

    例如:

    send user1 message1  /*表示给用户user1发送消息message1等*/
    

2 系统设计

2.1系统功能设计

​ 网络聊天室系统分为服务器和客户端,客户端主要进行命令的发送,以及接收从服务器发送过来的信息。服务器主要进行指令的解析,然后根据解析出来的指令执行相关的函数,将执行的结果返回给客户端。

2.1.1用户管理

​ 用户管理要完成的用户的注册、登录和密码修改。在服务器程序未启动时,已注册的用户信息存储在文件中。服务器程序启动之后把用户的用户名和密码加载到结构体数组中,当用户登录时,依次遍历数组,将数组中的用户名和用户输入的用户名进行比较,若用户名相同,继续比较密码,若密码相同,则登录成功,否则登录失败。若在数组中没找到用户输入的用户名,则告知用户该账号不存在。

​ 用户注册时,引导用户输入用户名和密码,将用户输入的用户名和密码存入结构体数组中,若成功执行,则告知用户注册成功。

​ 当用户想要修改密码时,需要输入更改密码命令和你想要更改的密码,然后从在结构体数组中查询该用户,找到之后将其旧密码修改为新密码,并提示用户密码修改成功。

​ 当服务器程序退出运行时,将数组中的用户名和密码已“w+”的模式(即先清除文件里的内容,再写入新的内容)写入文件,这样就可以保证每次新注册的用户的信息都存入文件中。用一句话来总结用户管理就是,当服务器程序未运行时,用户信息存在文件中;服务器程序运行时,将用户信息加载到内存;当服务器程序关闭时,再将用户信息从内存写入到文件中。

2.1.2聊天室管理

​ 聊天室存于结构体数组中。用户登录之后便可以创建聊天室,创建聊天室时,用户需要输入聊天室的名字和密码,然后依次遍历聊天室结构体数组,当找到一个空闲的聊天室时,便将用户输入的名字和密码赋给该聊天室,并且还要将创建者加入到该聊天室的成员中。

​ 用户加入聊天室时,需要输入聊天室的名字和聊天室的密码。程序先判断用户是否已经加入了聊天室,如果用户已经加入了聊天室,则提醒用户已经加入了聊天室,不能再加入另外一个聊天室。如果用户还未加入聊天室,则遍历整个聊天室结构体数组,将用户输入的聊天室名和数组中的聊天室名进行比较,若相同继续比较密码,若密码不行同,则告知用户密码不正确,若密码相同,则把用户加入到当前聊天室的成员中,告知用户加入聊天室成功。

​ 当用户退出聊天室时,遍历整个聊天室结构体数组,找到用户加入的聊天室,然后将用户从该聊天室中删除,告知用户成功退出聊天室。

2.1.3聊天管理

​ 用户发送消息的情况有三种,分别为1.发送给聊天室;3.群发信息,所有人都能收到;2.发送给私人(私聊)。用户发送信息都用到了“send”命令,可以根据命令后的选项确定用户要发送的是哪种类型的信息。

  1. 聊天室消息
    发送聊天室消息“send”后面跟“-chatroom”选项。当用户发送聊天室消息时,先查询用户在哪个聊天室中,若没找到,则告知用户还未加入聊天室。若找到用户所在的聊天室,则用一个循环向聊天室中的所有成员发送消息。
  2. 群发消息
    群发消息后跟“send”后面跟“-all”选型。当用户发送群发消息时,只需要用一个循环,向所有在线的用户消息即可。
  3. 私聊
    发送私聊信息,“send”后面跟你要发送的用户的用户名。当用户发送私聊信息时,先查询被发送用户是否存在或在线,若被发送用户不存在或不在线,则提醒用户发送失败。若被发送用户存在且在线,则将信息发送过去,告知用户发送成功。

2.1.4系统管理

​ 显示在线用户信息、显示所有聊天室信息、显示所有聊天室在线用户信息,都用到了“ls”命令,根据命令后的选型确定用户要显示的是什么。

  1. 显示在线用户信息
    显示在线用户信息在“ls”后面跟“-users”选项。显示在线用户信息只需要直接遍历在线用户结构体数组,把查询到的用户返回给客户端显示出来即可。
  2. 显示所有聊天室信息
    显示所有聊天室信息在“ls”后面跟“-chatrooms”选项。显示所有聊天室信息只需要遍历聊天室结构体数组,找出可用的聊天室,并把查询结果返回给客户端。
  3. 显示所有聊天室在线用户信息
    显示所有聊天室在线用户信息在“ls”后面跟“-inrmusr”选项。显示所有聊天室在线用户信息,需要先从聊天室结构体数组中找到要查询的聊天室,然后遍历该聊天室中的用户,将用户信息返回给客户端显示。

​ 帮助信息分为两部分,一部分为用户开始运行客户端程序时提供的帮助信息,有注册、登录、显示帮助信息、退出四条帮助信息;另一部分帮助信息需要输入“help”命令获取,它包含了系统需要用到的所有命令的使用帮助信息。“help”命令获取帮助信息不需要从服务器获取,直接在客户端调用函数来显示。

2.2系统数据结构设计

  1. struct user结构体

    struct user
    {
        char username[20];  //用户名
        char password[20];  //用户密码
    };
    

    该结构体用来存储从文件中加载到内存中的所有用户信息,无论用户是否在线,都把用户存在该结构体中。该结构体包含用户的用户名和密码。

  2. struct user_socket结构体

    struct user_socket
    {
        char username[20];
        int socketfd;
        int status; //标识是否在线 0:在线 -1:下线
    };
    

    该结构体用来存储在线用户,“username”是用户名,“socketfd”是服务器与客户端建立连接时,服务器存储套接字文件描述符数组的下标,“status”标识用户是否在线,初始时为-1。

  3. struct chatroom结构体

    struct chatroom
    {
        char name[20];
        char passwd[20];
        int user[10]; //聊天室成员
        int status;   //标识是否正在使用 0:使用中 -1:销毁
    };
    

    该结构体用来存储聊天室,“name”字符数组为聊天室名,“passwd”为聊天室密码,“user”数组存放加入聊天室的成员,存入的是内容与结构体struct user_socket中“socketfd”相同,“status”标识该聊天室是否在使用中,初始时为-1。

  4. int connfd[]套接字描述符数组

    该数组为一个整型数组,里面存的是客户端和服务器建立连接时的套接字,要访问某一个套接字直接使用数组下标访问。初始时为-1。

2.3系统主要函数设计

2.3.1客户端

  1. int main()

    客户端主程序中主要完成设置服务器地址,创建客户端流式套接字,打印登录前的帮助信息,之后程序分为两个线程,一个线程去执行客户端的发送函数,另一个线程继续在主函数中完成接收消息的任务。

  2. void snd()

    该函数由一个线程执行,作用是接收客户端的输入,若是接受到的输入是“help”或“quit”,则直接调用get_hep()函数或是退出客户端程序;若是其他命令,则直接发给服务器处理。

2.3.2服务器

  1. int main()

    服务器主程序主要完成服务器地址的设置,创建服务器流式套接字,将地址结构和套接字绑定,设置侦听端口。然后创建一个线程,该线程接受“quit”命令,用于退出服务器程序。最后接收客户端连接,针对每一个套接字建立一个线程,对当前套接字的消息进行处理。

  2. void init()

    完成相应结构体数组和变量的初始化,将用户从文件中读入内存。结构体数组主要由三个,分别为:

    struct user users[MAXMEM];               //记录所有用户
    struct user_socket online_users[MAXMEM]; //记录在线用户
    struct chatroom chatrooms[MAXROOM];      //记录聊天室
    

    初始时要把online_users的status、chatroomd的status以及chatroom里的user数组全部初始化为-1。

  3. void save_users()

    将内存中的的所有用户信息从新写入到文件中。

  4. void quit()

    该函数由一个线程执行,当服务器输入“quit”命令时,执行该函数,主要调用save_users()函数,将所有用户写入文件,关闭套接字。

  5. void rcv_snd(int n)

    在客户端与服务器建立连接后,针对每一个套接字建立一个线程来执行该函数,该函数完成服务器的接收和发送功能。

    参数含义
    n建立服务器与客户端时存入connfd[]数组的套接字的对应下标

    例如:建立的套接字为connfd[i] = accept(listenfd, (struct sockaddr *)&cli_addr, &len);则传入的参数为rcv_snd(i);

    该函数主要分为两个循环。第一个循环在用户登录前执行,主要完成用户的登录或者注册,当用户登录成功后,跳出该循环去执行第二个循环。第二个循环接收从客户端发来的命令,并将命令解析,根据解析后的命令决定执行的函数,最后将执行的结果写回给客户端。

  6. int user_login(int n)

    该函数在客户端输入“login”命令后执行,完成用户的登录。

    参数含义
    n建立服务器与客户端时存入connfd[]数组的套接字的对应下标

    返回值为0或-1,登录成功返回0,失败返回-1。

    函数执行时,提示用户输入用户名和密码,然后在users[]数组中查询该用户的用户名,若未找到,则告知“Account does not exist.”,返回-1;若找到用户名,则比较密码是否一致,若密码不一致,则告知“Wrong password.”返回-1;若密码一致,登录成功,告知用户“Login successfully.”,返回0。

  7. void register_user(int n)

    该函数在客户端输入“register”命令后执行,完成用户注册。

    参数含义
    n建立服务器与客户端时存入connfd[]数组的套接字的对应下标

    函数执行时,提示用户输入要注册的用户名和密码,然后验证要注册的用户名是否存在,若用户名已存在,则告知用户“The username already exists.”,函数结束执行;若用户名不存在,则将用户名和密码存入users[]数组中,同时告知用户“Account created successfully.”。

  8. void change_passwd(int sfd, char *passwd)

    该函数在用户输入命令“chgpsw xxx”后执行,完成用户密码的修改。“xxx”想要修改为的密码,作为参数传入该函数。

    参数含义
    sfd建立服务器与客户端时存入connfd[]数组的套接字的对应下标
    passwd想要修改为的密码
  9. void send_private_msg(char *username, char *data, int sfd)

    该函数在用户输入命令“send username xxx”后执行,完成聊天系统的私聊功能。“username”为信息接收人的用户名,“xxx”为发送消息的内容。

    参数含义
    username信息接收人的用户名
    data发送消息的内容
    sfd建立服务器与客户端时存入connfd[]数组的套接字的对应下标

    函数执行时遍历在线用户数组,查询被发送用户的用户名,若没找到用户,告知客户端“User is not online or user does not exist.”;若找到用户,则把消息发给该用户,同时将“Sent successfully”返回给客户端。

  10. void send_all_msg(char *msg, int sfd)

    该函数在用户输入命令“send -all xxx”后执行,完成用户信息的群发功能,“xxx”为发送的消息的内容。

    参数含义
    msg发送的消息的内容
    sfd建立服务器与客户端时存入connfd[]数组的套接字的对应下标
  11. void send_chatroom_msg(char *msg, int sfd)

    该函数在用户输入命令“send -chatroom xxx”后执行,完成用户消息在聊天室内的发送,“xxx”为发送的消息的内容。

    参数含义
    msg发送的消息的内容
    sfd建立服务器与客户端时存入connfd[]数组的套接字的对应下标
  12. void get_online_users(int sfd)

    该函数在用户输入命令“ls -users”后执行,完成查询所有在线用户的功能。

    参数含义
    sfd建立服务器与客户端时存入connfd[]数组的套接字的对应下标
  13. void get_online_chatrooms(int sfd)

    该函数在用户输入命令“ls -chatrooms”后执行,完成查询所有已创建房间的功能。

    参数含义
    sfd建立服务器与客户端时存入connfd[]数组的套接字的对应下标
  14. void get_inroom_users(int sfd)

    该函数在用户输入命令“ls -inrmusr”后执行,完成查询用户所加入聊天室的所有成员信息。

    参数含义
    sfd建立服务器与客户端时存入connfd[]数组的套接字的对应下标
  15. void create_chatroom(char *name, char *passwd, int sfd)

    该函数在用户输入命令“create chatroom passwd”后执行,完成创建一个叫做“chatroom”密码为“passwd”的聊天室。

    参数含义
    name聊天室的名字
    passwd聊天室的密码
    sfd建立服务器与客户端时存入connfd[]数组的套接字的对应下标
  16. void join_chatroom(char *name, char *passwd, int sfd)

    该函数在用户输入命令“join chatroom passwd”后执行,完成加入一个叫做“chatroom”加入密码为“passwd”的聊天室。

    参数含义
    name聊天室的名字
    passwd聊天室的加入密码
    sfd建立服务器与客户端时存入connfd[]数组的套接字的对应下标
  17. void exit_chatroom(int sfd)

    该函数在用户输入命令“exit”后执行,完成退出聊天室的功能。

    参数含义
    sfd建立服务器与客户端时存入connfd[]数组的套接字的对应下标
  18. void quit_client(int n)

    该函数在用户输入命令“quit”后执行,表示用户要退出客户端程序,服务器要关闭与该用户的套接字,同时修改该用户的状态为下线,退出与该用户连接时创建的线程。

    参数含义
    sfd建立服务器与客户端时存入connfd[]数组的套接字的对应下标
  19. void invalid_command(int sfd)

    该函数在客户端输入无效命令时执行,向客户端返回“Invalid command.”的提示信息。

    参数含义
    sfd建立服务器与客户端时存入connfd[]数组的套接字的对应下标

3 系统实现

3.1 开发环境配置

系统环境:Ubuntu 18.04

gcc版本:7.5.0

编辑器:visual studio code version 1.45

visual studio code配置:

  • launch.json

    {
        "version": "0.2.0",
        "configurations": [
            {
                "name": "gcc - 生成和调试活动文件",
                "type": "cppdbg",
                "request": "launch",
                "program": "${fileDirname}/${fileBasenameNoExtension}",
                "args": [],
                "stopAtEntry": false,
                "cwd": "${workspaceFolder}",
                "environment": [],
                "externalConsole": false,
                "MIMode": "gdb",
                "preLaunchTask": "build",
                "setupCommands": [
                    {
                        "description": "为 gdb 启用整齐打印",
                        "text": "-enable-pretty-printing",
                        "ignoreFailures": true
                    }
                ],
                "preLaunchTask": "C/C++: gcc build active file",
                "miDebuggerPath": "/usr/bin/gdb"
            }
        ]
    }
    
  • tasks.json

    {
        "version": "2.0.0",
        "tasks": [
            {
                "label": "build",
                "type": "shell",
                "command": "gcc",
                "args": [
                    "${file}",
                    "-o",
                    "${fileBasenameNoExtension}",
                    "-lpthread"
                ]
            }
        ]
    }
    

    因为pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a,所以编译的时候需要加上-lpthread。

3.2 功能模块的程序流程图及运行界面

3.2.1功能模块流程图

  1. 用户管理
    在这里插入图片描述

  2. 聊天室管理
    在这里插入图片描述

  3. 聊天管理
    在这里插入图片描述

  4. 系统管理
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9lQJnAGX-1594543504282)(系统管理.png)]

3.2.2运行界面

  • 登录界面
    在这里插入图片描述

  • 登录成功
    在这里插入图片描述

  • 登录失败
    在这里插入图片描述

  • 查看帮助信息
    在这里插入图片描述

  • 查看所有在线用户
    在这里插入图片描述

  • 发送群聊消息
    在这里插入图片描述

  • 发送私聊信息
    在这里插入图片描述
    strong向trump发送“我爱中国!”私聊信息,trump成功收到私聊信息。

  • 私聊信息发送失败
    在这里插入图片描述
    strong向不在线的用户elito发送消息。

  • 创建聊天室
    在这里插入图片描述
    strong创建聊天室test,trump创建聊天室America。

  • 查看所有在使用中聊天室
    在这里插入图片描述

  • 加入聊天室失败
    在这里插入图片描述
    pony加入聊天室test时聊天室密码输入错误。

  • 成功加入聊天室
    在这里插入图片描述
    pony成功加入聊天室test。

  • 查看聊天室成员
    在这里插入图片描述
    pony成功加入了strong创建的聊天室test。

  • 发送聊天室消息
    在这里插入图片描述
    strong发送了一条群聊信息,群里只有strong和pony,所以只有pony和strong接收到了群聊信息。

  • 退出聊天室
    在这里插入图片描述
    pony退出聊天室test。

  • 聊天室外不能查看聊天室成员
    在这里插入图片描述
    pony退出了聊天室,所以不能查看聊天室成员信息。

  • 退出聊天室后不能再接收到聊天室消息
    在这里插入图片描述
    pony退出了聊天室test,所以不能收到strong在聊天室test发出的聊天室消息。

  • 不能同时加入两个聊天室
    在这里插入图片描述
    trump已经加入了聊天室America,所以不能再加入聊天室test。

3.3 关键代码分析说明

  1. 以为聊天室的客户端和服务器连接是不能断开的,除非有一方程序停止运行,所以客户端和服务器之间的连接采用TCP协议。

        /*服务器*/
        struct sockaddr_in serv_addr, cli_addr;
        int i;
        time_t timenow;
        pthread_t thread;
        char buff[BUFFSIZE];
        printf("Running...\nEnter command \"quit\" to exit server.\n\n");
        bzero(&serv_addr, sizeof(struct sockaddr_in));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(PORT);
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
        listenfd = socket(AF_INET, SOCK_STREAM, 0); // 建立流式套接字
        if (listenfd < 0)
        {
            perror("fail to socket");
            exit(-1);
        }
    
        if (bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
        {
            perror("fail to bind");
            exit(-2);
        }
    
        listen(listenfd, MAXMEM);
    
    
    	/*客户端*/
    	struct sockaddr_in serv_addr; // struct sockaddr_in
        char buf[BUFFSIZE], temp[BUFFSIZE];
    
        // 初始化服务端地址结构
        bzero(&serv_addr, sizeof(struct sockaddr_in)); // bzero 清零
        serv_addr.sin_family = AF_INET;                // sin_family   AF_INET
        serv_addr.sin_port = htons(PORT);              // sin_port     htons(PORT)
        inet_pton(HOST_IP, &serv_addr.sin_addr);       // inet_pton
        // 创建客户端套接字
        sockfd = socket(AF_INET, SOCK_STREAM, 0); // socket 创建流式套接字
        if (sockfd < 0)
        {
            perror("fail to socket");
            exit(-1);
        }
        // 与服务器建立连接
        printf("connecting... \n");
        if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) 
        {
            perror("fail to connect");
            exit(-2);
        }
    
    
  2. 服务器对待每一个来自客户端的连接的处理方式是:服务器先从connfd[]中找到一个空位,然后再从侦听队列中选取一个加入到该空位,创建于客户端的连接。每一个连接创建一个单独的线程去处理。

    	while (1)
        {
            int len;
            for (i = 0; i < MAXMEM; i++)
            {
                if (connfd[i] == -1)
                    break;
            }
            // accept 从listen接受的连接队列中取得一个连接
            connfd[i] = accept(listenfd, (struct sockaddr *)&cli_addr, &len);
            if (connfd[i] < 0)
            {
                perror("fail to accept.");
            }
            // 针对当前套接字创建一个线程,对当前套接字的消息进行处理
            pthread_create(malloc(sizeof(pthread_t)), NULL, (void *)(&rcv_snd), (void *)i);
        }
    
  3. 服务器中最重要的函数是rcv_snd(),它接收来自客户端的命令,并将命令解析,根据不同的命令执行不同的功能,最后将结果返回给客户端。

    该函数有两个循环,第一个循环是在用户登录前执行,用于引导用户登录和注册,一旦当用户登录成功,遍退出该循环,接着执行第二个循环。

    第二个循环主要完成用户登录后,对用户发来的命令进行解析,解析命令用到了函数ssanf(),它能根据设置的格式将一个字符串中空格隔开的字符串分离出来,具体用法为:

    sscanf(temp, "%s %s %[^\n]", command, arg1, arg2);
    

    temp位为用户发来的命令,它是一整个字符串;command、arg1、arg2都是字符串,用来接收从temp分离出来的字符串。

    例如:当temp=“send pony have a nice day!”,解析后command=“send”,arg1=“pony”,arg2=“have a nice day!”;当temp=“ls -users"时,解析后command=“ls”,arg1=”-users",arg2=""。

    然后再根据解析出的命令执行不同的功能。

    void rcv_snd(int n)
    {
        ssize_t len;
        int i;
        char mytime[32], buf[BUFFSIZE];
        char temp[BUFFSIZE];
        char command[20], arg1[20], arg2[BUFFSIZE];
        time_t timenow;
    
        while (1)
        {
            len = read(connfd[n], buf, BUFFSIZE);
            if (len > 0)
            {
                buf[len - 1] = '\0'; // 去除换行符
    
                if (strcmp(buf, "login") == 0)
                {
                    //登录成功时退出该循环
                    if (user_login(n) == 0)
                    {
                        break;
                    }
                }
                else if (strcmp(buf, "register") == 0)
                {
                    register_user(n);
                }
                else if (strcmp(buf, "quit") == 0)
                {
                    quit_client(n);
                }
            }
        }
    
        while (1)
        {
            if ((len = read(connfd[n], temp, BUFFSIZE)) > 0)
            {
                temp[len - 1] = '\0';
                sscanf(temp, "%s %s %[^\n]", command, arg1, arg2); //解析命令
    
                /*根据解析出的命令执行不同的函数*/
                if (strcmp(command, "send") == 0 && strcmp(arg1, "-all") == 0)
                {
                    send_all_msg(arg2, n);
                }
                else if (strcmp(command, "send") == 0 && strcmp(arg1,"-chatroom")==0)
                {
                    send_chatroom_msg(arg2, n);
                }
                else if (strcmp(command, "send") == 0)
                {
                    send_private_msg(arg1, arg2, n);
                }
                else if (strcmp(command, "quit") == 0)
                {
                    quit_client(n);
                }
                else if (strcmp(command, "chgpsw") == 0)
                {
                    change_passwd(n, arg1);
                }
                else if (strcmp(command, "create") == 0)
                {
                    create_chatroom(arg1, arg2, n);
                }
                else if (strcmp(command, "join") == 0)
                {
                    join_chatroom(arg1, arg2, n);
                }
                else if (strcmp(command, "ls") == 0 && strcmp(arg1, "-chatrooms")==0)
                {
                    get_online_chatrooms(n);
                }
                else if (strcmp(command, "ls") == 0 && strcmp(arg1, "-users") == 0)
                {
                    get_online_users(n);
                }
                else if (strcmp(command, "ls") == 0 && strcmp(arg1, "-inrmusr") == 0)
                {
                    get_inroom_users(n);
                }
                else if (strcmp(command, "exit") == 0)
                {
                    exit_chatroom(n);
                }
                else
                {
                    invalid_command(n);
                }
            }
        }
    }
    
  4. 在服务器退出服务,执行quit()函数时,一定要把内存总的用户信息以“w+”模式从新写入文件,这样才能保障数据的一致性。

    /*将用户保存到文件*/
    void save_users()
    {
        int i;
        char buf[20];
        FILE *fp = NULL;
        fp = fopen("users.txt", "w+");
        for (i = 0; i < user_count; i++)
        {
            strcpy(buf, users[i].username);
            strcat(buf, "\n");
            fprintf(fp, buf);
            strcpy(buf, users[i].password);
            strcat(buf, "\n");
            fprintf(fp, buf);
        }
        fclose(fp);
    }
    

4 程序调试分析

  1. 从文件读出数据时数据缺失,于用户输入的注册时输入的用户名和密码不一致。

    一开始设计时存入文件的数据没有转行,每条数据用一个特殊字符结尾,然后读的时候读到特殊符号就停止,将该条数据读出,再读下一条数据,直到文件结尾。然而这样在读出数据时很容易出错,并且代码繁琐。

    解决方法:一条数据占一行,写入文件时加入’\n’,读出数据时用fscanf(),忽略结尾的’\n’,这样就能得到正确的数据,并且代码简化了不少。

    //写入数据
    strcpy(buf, users[i].username);
    strcat(buf, "\n"); //加上换行符
    fprintf(fp, buf);
    strcpy(buf, users[i].password);
    strcat(buf, "\n");
    fprintf(fp, buf);
    
    //读出数据
    while (fscanf(fp, "%s", buf) != EOF)
    {
        strcpy(users[user_count].username, buf);
        fscanf(fp, "%s", buf); //忽略结尾的'\n'
        strcpy(users[user_count].password, buf);
        user_count++;
    }
    
  2. 当一个用户登录系统,然后下线,调用查看在线用户信息时,仍然能看到该用户的信息。

    问题来源:结构体struct user_socket没有标识用户是否在线的标志,若一个用户退出聊天系统,只要还存在于struct user_socket数组中,就可以认为他还是在线的。

    解决方法:在结构体struct user_socket添加一个状态status,标识用户是否在线。

    struct user_socket
    {
        char username[20];
        int socketfd;
        int status; //标识是否在线 0:在线 -1:下线
    };
    
  3. 在用户加入聊天室时,同时比较聊天室名、聊天室密码和用户输入的聊天室名、聊天室密码,但是这种比较方法在某些情况下会造成bug,破坏系统运行。

    解决方法:加入聊天室时正确的比较逻辑是先比较聊天室名是否一样,若聊天室名一样再进一步比较聊天室密码。



菜鸡之作,避免不了纰漏和疏忽,各位大佬若有任何问题,欢迎在评论区讨论

参考文献

[1] 宋敬彬 《Linux网络编程》,清华大学出版社,2014

[2] 菜鸟教程 ,C语言教程 https://www.runoob.com/cprogramming/c-tutorial.html

附:源代码

  • client.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

#define BUFFSIZE 128
#define HOST_IP "192.168.159.3"
#define PORT 8888

int sockfd;
void snd();
void get_help();
  • client.c
#include "client.h"

int main()
{
    pthread_t thread;             // pthread_t 线程,gcc编译时需加上-lpthread
    struct sockaddr_in serv_addr; // struct sockaddr_in
    char buf[BUFFSIZE], temp[BUFFSIZE];

    // 初始化服务端地址结构
    bzero(&serv_addr, sizeof(struct sockaddr_in)); // bzero 清零
    serv_addr.sin_family = AF_INET;                // sin_family   AF_INET
    serv_addr.sin_port = htons(PORT);              // sin_port     htons(PORT)
    inet_pton(HOST_IP, &serv_addr.sin_addr);       // inet_pton
    // 创建客户端套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0); // socket 创建套接字
    if (sockfd < 0)
    {
        perror("fail to socket");
        exit(-1);
    }
    // 与服务器建立连接
    printf("connecting... \n");
    if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
        perror("fail to connect");
        exit(-2);
    }

    printf("Enter \"login\" to login\n");
    printf("Enter \"register\" to create an account\n");
    printf("Enter \"quit\" to quit\n");
    printf("Enter \"help\" to get more help\n\n");

    /* === 从此处开始 程序分做两个线程 === */
    // 创建发送消息的线程,调用发送消息的函数snd
    pthread_create(&thread, NULL, (void *)(&snd), NULL); // pthread_create
    // 接收消息的线程
    while (1)
    {
        int len;
        if ((len = read(sockfd, buf, BUFFSIZE)) > 0) // read 读取通信套接字
        {
            write(1, buf, len); //1:标准输出
            printf("\n");
        }
    }
    return 0;
}

/*发送消息的函数*/
void snd()
{
    char buf[BUFFSIZE];
    while (1)
    {
        fgets(buf, BUFFSIZE, stdin);
        if (strcmp(buf, "help\n") == 0)
        {
            get_help();
            continue;
        }
        if (strcmp(buf, "\n") != 0)
            write(sockfd, buf, strlen(buf));
        if (strcmp(buf, "quit\n") == 0) // 注意此处的\n
            exit(0);
    }
}

/*获取帮助信息*/
void get_help()
{
    printf("Commands introduction:\n");
    printf("\t'ls -users':\t\tShow all online users\n");
    printf("\t'ls -chatrooms':\tShow all chat rooms\n");
    printf("\t'ls -inrmusr'\t\tShow all online users in chat room you joined\n");
    printf("\t'send username msg':\tSend a message to the user named 'username' msg:the content of the message\n");
    printf("\t'join chatroom passwd':\tJoin in a chat room named 'chatroom' with password 'passwd'\n");
    printf("\t'create chatrname passwd':\tCreate a chat room named 'chatrname' with password 'passwd'\n");
    printf("\t'chgpsw passwd':\t\tChange your password to 'passwd'\n");
    printf("\t'send -chatroom msg':\tSend a message to the chat room\n");
    printf("\t'exit':\t\t\tExit the chat room you joined\n");
    printf("\t'send -all msg':\tSend a message to all online users\n");
    printf("\t'login':\t\tLogin chat system\n");
    printf("\t'register':\t\tCreate an account\n");
    printf("\t'quit':\t\t\tExit chat system\n");
    printf("\t'help':\t\t\tGet more help information\n\n");
}
  • server.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <time.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/*存储用户*/
struct user
{
    char username[20];
    char password[20];
};

/*存储用户及其用户套接字文件描述符*/
struct user_socket
{
    char username[20];
    int socketfd;
    int status; //标识是否在线 0:在线 -1:下线
};

/*存储聊天室*/
struct chatroom
{
    char name[20];
    char passwd[20];
    int user[10]; //加入聊天室的人数
    int status;   //标识是否还存在 0:存在 -1:销毁
};

#define PORT 8888
#define MAXMEM 20
#define MAXROOM 5
#define BUFFSIZE 256

int user_count;     //记录总的用户数
int chatroom_count; //记录聊天室个数
int listenfd, connfd[MAXMEM];
struct user users[MAXMEM];               //记录所有用户
struct user_socket online_users[MAXMEM]; //记录在线用户
struct chatroom chatrooms[MAXROOM];      //记录聊天室

void init();
void quit();
void save_users();
void register_user(int n);
void rcv_snd(int p);
void quit_client(int n);
int user_login(int n);
void get_help();
void send_private_msg(char *username, char *data, int sfd);
void send_all_msg(char *msg, int sfd);
void get_online_users(int sfd);
void send_chatroom_msg(char *msg, int sfd);
void create_chatroom(char *name, char *passwd, int sfd);
void join_chatroom(char *name, char *passwd, int sfd);
void get_online_chatrooms(int sfd);
void change_passwd(int sfd, char *passwd);
void get_inroom_users(int sfd);
void exit_chatroom(int sfd);
void invalid_command(int sfd);
  • server.c
#include "server.h"

int main()
{
    init();

    struct sockaddr_in serv_addr, cli_addr;
    int i;
    time_t timenow;
    pthread_t thread;
    char buff[BUFFSIZE];

    printf("Running...\nEnter command \"quit\" to exit server.\n\n");

    bzero(&serv_addr, sizeof(struct sockaddr_in));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0)
    {
        perror("fail to socket");
        exit(-1);
    }

    if (bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
        perror("fail to bind");
        exit(-2);
    }

    listen(listenfd, MAXMEM);

    // 创建一个线程,对服务器程序进行管理,调用quit函数
    pthread_create(&thread, NULL, (void *)(quit), NULL);

    // 将套接字描述符数组初始化为-1,表示空闲
    for (i = 0; i < MAXMEM; i++)
        connfd[i] = -1;

    while (1)
    {
        int len;
        for (i = 0; i < MAXMEM; i++)
        {
            if (connfd[i] == -1)
                break;
        }
        // accept 从listen接受的连接队列中取得一个连接
        connfd[i] = accept(listenfd, (struct sockaddr *)&cli_addr, &len);
        if (connfd[i] < 0)
        {
            perror("fail to accept.");
        }
        timenow = time(NULL);
        printf("%.24s\n\tconnect from: %s, port %d\n",
               ctime(&timenow), inet_ntop(AF_INET, &(cli_addr.sin_addr), buff, BUFFSIZE),
               ntohs(cli_addr.sin_port));

        // 针对当前套接字创建一个线程,对当前套接字的消息进行处理
        pthread_create(malloc(sizeof(pthread_t)), NULL, (void *)(&rcv_snd), (void *)i);
    }
    return 0;
}

/*服务器接收和发送函数*/
void rcv_snd(int n)
{
    ssize_t len;
    int i;
    char mytime[32], buf[BUFFSIZE];
    char temp[BUFFSIZE];
    char command[20], arg1[20], arg2[BUFFSIZE];
    time_t timenow;

    while (1)
    {
        len = read(connfd[n], buf, BUFFSIZE);
        if (len > 0)
        {
            buf[len - 1] = '\0'; // 去除换行符

            if (strcmp(buf, "login") == 0)
            {
                //登录成功时退出该循环
                if (user_login(n) == 0)
                {
                    break;
                }
            }
            else if (strcmp(buf, "register") == 0)
            {
                register_user(n);
            }
            else if (strcmp(buf, "quit") == 0)
            {
                quit_client(n);
            }
        }
    }

    while (1)
    {
        if ((len = read(connfd[n], temp, BUFFSIZE)) > 0)
        {
            temp[len - 1] = '\0';
            sscanf(temp, "%s %s %[^\n]", command, arg1, arg2); //解析命令

            /*根据解析出的命令执行不同的函数*/
            if (strcmp(command, "send") == 0 && strcmp(arg1, "-all") == 0)
            {
                send_all_msg(arg2, n);
            }
            else if (strcmp(command, "send") == 0 && strcmp(arg1, "-chatroom") == 0)
            {
                send_chatroom_msg(arg2, n);
            }
            else if (strcmp(command, "send") == 0)
            {
                send_private_msg(arg1, arg2, n);
            }
            else if (strcmp(command, "quit") == 0)
            {
                quit_client(n);
            }
            else if (strcmp(command, "chgpsw") == 0)
            {
                change_passwd(n, arg1);
            }
            else if (strcmp(command, "create") == 0)
            {
                create_chatroom(arg1, arg2, n);
            }
            else if (strcmp(command, "join") == 0)
            {
                join_chatroom(arg1, arg2, n);
            }
            else if (strcmp(command, "ls") == 0 && strcmp(arg1, "-chatrooms") == 0)
            {
                get_online_chatrooms(n);
            }
            else if (strcmp(command, "ls") == 0 && strcmp(arg1, "-users") == 0)
            {
                get_online_users(n);
            }
            else if (strcmp(command, "ls") == 0 && strcmp(arg1, "-inrmusr") == 0)
            {
                get_inroom_users(n);
            }
            else if (strcmp(command, "exit") == 0)
            {
                exit_chatroom(n);
            }
            else
            {
                invalid_command(n);
            }
        }
    }
}

/*初始化*/
void init()
{
    int i, j;
    user_count = 0;
    chatroom_count = 0;
    for (i = 0; i < MAXMEM; i++)
    {
        online_users[i].status = -1;
    }
    for (i = 0; i < MAXROOM; i++)
    {
        chatrooms[i].status = -1;
        for (j = 0; j < 10; j++)
        {
            chatrooms[i].user[j] = -1;
        }
    }
    char buf[20];
    FILE *fp = NULL;
    fp = fopen("users.txt", "r");
    //从文件中读取用户
    while (fscanf(fp, "%s", buf) != EOF)
    {
        strcpy(users[user_count].username, buf);
        fscanf(fp, "%s", buf);
        strcpy(users[user_count].password, buf);
        user_count++;
    }
    fclose(fp);
}

/*将用户保存到文件*/
void save_users()
{
    int i;
    char buf[20];
    FILE *fp = NULL;
    fp = fopen("users.txt", "w+");
    for (i = 0; i < user_count; i++)
    {
        strcpy(buf, users[i].username);
        strcat(buf, "\n");
        fprintf(fp, buf);
        strcpy(buf, users[i].password);
        strcat(buf, "\n");
        fprintf(fp, buf);
    }
    fclose(fp);
}

/*服务器处理用户退出*/
void quit_client(int n)
{
    int ret, i;
    close(connfd[n]);
    connfd[n] = -1;
    for (i = 0; i < MAXMEM; i++)
    {
        if (n == online_users[i].socketfd)
        {
            online_users[i].status = -1;
        }
    }
    pthread_exit(&ret);
}

/*用户登录*/
int user_login(int n)
{
    int len, i, j;
    char buf[BUFFSIZE], username[20], password[20];
    sprintf(buf, "your username: ");
    write(connfd[n], buf, strlen(buf) + 1);
    len = read(connfd[n], username, 20);
    if (len > 0)
    {
        username[len - 1] = '\0'; // 去除换行符
    }
    sprintf(buf, "your password: ");
    write(connfd[n], buf, strlen(buf) + 1);
    len = read(connfd[n], password, 20);
    if (len > 0)
    {
        password[len - 1] = '\0'; // 去除换行符
    }

    for (i = 0; i < MAXMEM; i++)
    {
        if (strcmp(username, users[i].username) == 0)
        {
            if (strcmp(password, users[i].password) == 0)
            {
                sprintf(buf, "Login successfully.\n\n");
                write(connfd[n], buf, strlen(buf + 1));
                for (j = 0; j < MAXMEM; j++)
                {
                    if (online_users[j].status == -1)
                        break;
                }
                strcpy(online_users[j].username, username);
                online_users[j].socketfd = n;
                online_users[j].status = 0;
                return 0;
            }
            else
            {
                sprintf(buf, "Wrong password.\n\n");
                write(connfd[n], buf, strlen(buf + 1));
                return -1;
            }
        }
    }
    sprintf(buf, "Account does not exist.\n\n");
    write(connfd[n], buf, strlen(buf + 1));
    return -1;
}

/*用户注册*/
void register_user(int n)
{
    int len, i;
    char buf[BUFFSIZE], username[20], password[20];
    sprintf(buf, "your username: ");
    write(connfd[n], buf, strlen(buf) + 1);
    len = read(connfd[n], username, 20);
    if (len > 0)
    {
        username[len - 1] = '\0'; // 去除换行符
    }
    sprintf(buf, "your password: ");
    write(connfd[n], buf, strlen(buf) + 1);
    len = read(connfd[n], password, 20);
    if (len > 0)
    {
        password[len - 1] = '\0'; // 去除换行符
    }
    for (i = 0; i < MAXMEM; i++)
    {
        if (strcmp(users[i].username, username) == 0)
        {
            strcpy(buf, "The username already exists.\n\n");
            write(connfd[n], buf, strlen(buf) + 1);
            return;
        }
    }
    strcpy(users[user_count].username, username);
    strcpy(users[user_count].password, password);
    user_count++;
    sprintf(buf, "Account created successfully.\n\n");
    write(connfd[n], buf, strlen(buf) + 1);
}

/*用户发送私聊信息*/
void send_private_msg(char *username, char *data, int sfd)
{
    int i, j;
    time_t now;
    char send_man[20];
    char buf[BUFFSIZE], nowtime[20], temp[30];
    now = time(NULL);
    time(&now);
    struct tm *tempTime = localtime(&now);
    strftime(nowtime, 20, "[%H:%M:%S]", tempTime);
    for (j = 0; j < MAXMEM; j++)
    {
        if (sfd == online_users[j].socketfd)
        {
            strcpy(send_man, online_users[j].username);
            break;
        }
    }
    for (i = 0; i < MAXMEM; i++)
    {
        if (strcmp(username, online_users[i].username) == 0)
        {
            strcpy(buf, nowtime);
            strcat(buf, "\t");
            strcat(buf, "from ");
            strcat(buf, send_man);
            strcat(buf, ":\n");
            strcat(buf, data);
            strcat(buf, "\n");
            write(connfd[online_users[i].socketfd], buf, strlen(buf) + 1);
            strcpy(temp, "Sent successfully.\n");
            write(connfd[sfd], temp, strlen(temp) + 1);
            return;
        }
    }
    strcpy(buf, "User is not online or user does not exist.\n");
    write(connfd[sfd], buf, strlen(buf) + 1);
    return;
}

/*用户群发信息给所有用户*/
void send_all_msg(char *msg, int sfd)
{
    int i;
    char buf[BUFFSIZE], nowtime[20], send_man[20], temp[30];
    time_t now;
    time(&now);
    struct tm *tempTime = localtime(&now);
    strftime(nowtime, 20, "[%H:%M:%S]", tempTime);
    for (i = 0; i < MAXMEM; i++)
    {
        if (sfd == online_users[i].socketfd)
        {
            strcpy(send_man, online_users[i].username);
            break;
        }
    }
    strcpy(buf, nowtime);
    strcat(buf, "\t");
    strcat(buf, "from ");
    strcat(buf, send_man);
    strcat(buf, "(goup-sent):\n");
    strcat(buf, msg);
    strcat(buf, "\n");
    for (i = 0; i < MAXMEM; i++)
    {
        if (connfd[i] != -1 && i != sfd)
        {
            write(connfd[i], buf, strlen(buf) + 1);
        }
    }
    strcpy(temp, "Sent successfully\n");
    write(connfd[sfd], temp, strlen(temp) + 1);
}

/*获取所有在线用户信息*/
void get_online_users(int sfd)
{
    int i;
    char buf[BUFFSIZE], nowtime[20];
    time_t now;
    time(&now);
    struct tm *tempTime = localtime(&now);
    strftime(nowtime, 20, "[%H:%M:%S]", tempTime);
    strcpy(buf, nowtime);
    strcat(buf, "\t");
    strcat(buf, "All online user(s):\n");
    for (i = 0; i < MAXMEM; i++)
    {
        if (online_users[i].status == 0)
        {
            strcat(buf, "\t");
            strcat(buf, online_users[i].username);
            strcat(buf, "\n");
        }
    }
    write(connfd[sfd], buf, strlen(buf) + 1);
}

/*向聊天室发送信息*/
void send_chatroom_msg(char *msg, int sfd)
{
    int i, j, k;
    int flag;
    flag = -1;
    char buf[BUFFSIZE], nowtime[20];
    time_t now;
    time(&now);
    struct tm *tempTime = localtime(&now);
    strftime(nowtime, 20, "[%H:%M:%S]", tempTime);
    for (i = 0; i < MAXROOM; i++)
    {
        if (chatrooms[i].status == 0)
        {
            for (j = 0; j < 10; j++)
            {
                if (chatrooms[i].user[j] == sfd)
                {
                    flag = 0;
                    break;
                }
            }
        }
        if (flag == 0)
        {
            break;
        }
    }
    if (flag == -1)
    {
        strcpy(buf, "You have not joined the chat room.\n");
        write(connfd[sfd], buf, strlen(buf) + 1);
    }
    else
    {

        for (k = 0; k < MAXMEM; k++)
        {
            if (online_users[k].status == 0 && online_users[k].socketfd == sfd)
                break;
        }
        strcpy(buf, nowtime);
        strcat(buf, "\tchatroom ");
        strcat(buf, chatrooms[i].name);
        strcat(buf, ":\nfrom ");
        strcat(buf, online_users[k].username);
        strcat(buf, ":\t");
        strcat(buf, msg);
        strcat(buf, "\n");
        for (k = 0; k < 10; k++)
        {
            if (chatrooms[i].user[k] != -1)
            {
                write(connfd[chatrooms[i].user[k]], buf, strlen(buf) + 1);
            }
        }
    }
}

/*创建聊天室*/
void create_chatroom(char *name, char *passwd, int sfd)
{
    int i, j;
    char buf[BUFFSIZE];
    for (i = 0; i < MAXROOM; i++)
    {
        if (chatrooms[i].status == -1)
            break;
    }
    strcpy(chatrooms[i].name, name);
    strcpy(chatrooms[i].passwd, passwd);
    chatrooms[i].status = 0;
    for (j = 0; j < 10; j++)
    {
        if (chatrooms[i].user[j] == -1)
            break;
    }
    chatrooms[i].user[j] = sfd;
    strcpy(buf, "Successfully created chat room.\n");
    write(connfd[sfd], buf, strlen(buf) + 1);
}

/*加入聊天室*/
void join_chatroom(char *name, char *passwd, int sfd)
{
    int i, j;
    int room, flag;
    char buf[BUFFSIZE];
    flag = -1;
    for (i = 0; i < MAXROOM; i++)
    {
        for (j = 0; j < 10; j++)
        {
            if (chatrooms[i].user[j] == sfd)
            {
                room = i;
                flag = 0;
            }
        }
    }
    if (flag == 0)
    {
        strcpy(buf, "You have joined the chat room ");
        strcat(buf, chatrooms[room].name);
        strcat(buf, ".\n");
        write(connfd[sfd], buf, strlen(buf) + 1);
    }
    else
    {
        for (i = 0; i < MAXROOM; i++)
        {
            if (chatrooms[i].status != -1)
            {
                if (strcmp(chatrooms[i].name, name) == 0)
                {
                    if (strcmp(chatrooms[i].passwd, passwd) == 0)
                    {
                        for (j = 0; j < 10; j++)
                        {
                            if (chatrooms[i].user[j] == -1)
                            {
                                break;
                            }
                        }
                        chatrooms[i].user[j] = sfd;
                        strcpy(buf, "Successfully joined the chat room.\n");
                        write(connfd[sfd], buf, strlen(buf) + 1);
                        return;
                    }
                    else
                    {
                        strcpy(buf, "Incorrect chat room password.\n");
                        write(connfd[sfd], buf, strlen(buf) + 1);
                        return;
                    }
                }
            }
        }
    }
}

/*获取所有已创建的聊天室的信息*/
void get_online_chatrooms(int sfd)
{
    int i;
    char buf[BUFFSIZE], nowtime[20];
    time_t now;
    time(&now);
    struct tm *tempTime = localtime(&now);
    strftime(nowtime, 20, "[%H:%M:%S]", tempTime);
    strcpy(buf, nowtime);
    strcat(buf, "\tAll online chat room(s):\n");
    for (i = 0; i < MAXROOM; i++)
    {
        if (chatrooms[i].status == 0)
        {
            strcat(buf, "\t");
            strcat(buf, chatrooms[i].name);
            strcat(buf, "\n");
        }
    }
    write(connfd[sfd], buf, strlen(buf) + 1);
}

/*修改密码*/
void change_passwd(int sfd, char *passwd)
{
    int i, j;
    char buf[BUFFSIZE], name[20];
    for (j = 0; j < MAXMEM; j++)
    {
        if (sfd == online_users[j].socketfd)
        {
            strcpy(name, online_users[j].username);
            break;
        }
    }
    for (i = 0; i < MAXMEM; i++)
    {
        if (strcmp(name, users[i].username) == 0)
        {
            strcpy(users[i].password, passwd);
            strcpy(buf, "Password has been updated.\n");
            write(connfd[sfd], buf, strlen(buf) + 1);
            break;
        }
    }
}

/*查询所有加入某聊天室的用户*/
void get_inroom_users(int sfd)
{
    int i, j;
    int room, flag; //room记录查询查询发起人所在的房间,flag标识用户是否加入房间
    flag = -1;
    char buf[BUFFSIZE], nowtime[20];
    time_t now;
    time(&now);
    struct tm *tempTime = localtime(&now);
    strftime(nowtime, 20, "[%H:%M:%S]", tempTime);
    for (i = 0; i < MAXROOM; i++)
    {
        for (j = 0; j < 10; j++)
        {
            if (chatrooms[i].user[j] == sfd)
            {
                room = i;
                flag = 0;
            }
        }
    }
    if (flag == -1)
    {
        strcpy(buf, "Sorry, you have not joined the chat room.\n");
        write(connfd[sfd], buf, strlen(buf) + 1);
    }
    else
    {
        strcpy(buf, nowtime);
        strcat(buf, "\tAll users in the ");
        strcat(buf, chatrooms[room].name);
        strcat(buf, ":\n");
        for (i = 0; i < 10; i++)
        {
            if (chatrooms[room].user[i] >= 0)
                for (j = 0; j < MAXMEM; j++)
                {
                    if (online_users[j].status != -1 && (chatrooms[room].user[i] == online_users[j].socketfd))
                    {
                        strcat(buf, "\t");
                        strcat(buf, online_users[j].username);
                        strcat(buf, "\n");
                    }
                }
        }
        write(connfd[sfd], buf, strlen(buf) + 1);
    }
}

/*退出聊天室*/
void exit_chatroom(int sfd)
{
    int i, j;
    int room, flag;
    flag = -1;
    char buf[BUFFSIZE];
    for (i = 0; i < MAXROOM; i++)
    {
        if (chatrooms[i].status == 0)
        {
            for (j = 0; j < 10; j++)
            {
                if (chatrooms[i].user[j] == sfd)
                {
                    chatrooms[i].user[j] = -1;
                    room = i;
                    flag = 0;
                    break;
                }
            }
        }
        if (flag == 0)
            break;
    }
    if (flag == -1)
    {
        strcpy(buf, "You have not joined the chat room.\n");
        write(connfd[sfd], buf, strlen(buf) + 1);
    }
    else
    {
        strcpy(buf, "Successfully quit chat room ");
        strcat(buf, chatrooms[room].name);
        strcat(buf, ".\n");
        write(connfd[sfd], buf, strlen(buf) + 1);
    }
}

/*服务器推出*/
void quit()
{
    int i;
    char msg[10];
    while (1)
    {
        scanf("%s", msg); // scanf 不同于fgets, 它不会读入最后输入的换行符
        if (strcmp(msg, "quit") == 0)
        {
            save_users();
            printf("Byebye... \n");
            close(listenfd);
            exit(0);
        }
    }
}

/*用户输入无效命令*/
void invalid_command(int sfd)
{
    char buf[BUFFSIZE];
    strcpy(buf, "Invalid command.\n");
    write(connfd[sfd], buf, strlen(buf) + 1);
}

{
strcat(buf, “\t”);
strcat(buf, online_users[j].username);
strcat(buf, “\n”);
}
}
}
write(connfd[sfd], buf, strlen(buf) + 1);
}
}

/退出聊天室/
void exit_chatroom(int sfd)
{
int i, j;
int room, flag;
flag = -1;
char buf[BUFFSIZE];
for (i = 0; i < MAXROOM; i++)
{
if (chatrooms[i].status == 0)
{
for (j = 0; j < 10; j++)
{
if (chatrooms[i].user[j] == sfd)
{
chatrooms[i].user[j] = -1;
room = i;
flag = 0;
break;
}
}
}
if (flag == 0)
break;
}
if (flag == -1)
{
strcpy(buf, “You have not joined the chat room.\n”);
write(connfd[sfd], buf, strlen(buf) + 1);
}
else
{
strcpy(buf, "Successfully quit chat room ");
strcat(buf, chatrooms[room].name);
strcat(buf, “.\n”);
write(connfd[sfd], buf, strlen(buf) + 1);
}
}

/服务器推出/
void quit()
{
int i;
char msg[10];
while (1)
{
scanf("%s", msg); // scanf 不同于fgets, 它不会读入最后输入的换行符
if (strcmp(msg, “quit”) == 0)
{
save_users();
printf(“Byebye… \n”);
close(listenfd);
exit(0);
}
}
}

/用户输入无效命令/
void invalid_command(int sfd)
{
char buf[BUFFSIZE];
strcpy(buf, “Invalid command.\n”);
write(connfd[sfd], buf, strlen(buf) + 1);
}






  • 80
    点赞
  • 330
    收藏
    觉得还不错? 一键收藏
  • 39
    评论
评论 39
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值