文章目录

  • 1 概述
    • 1.1聊天室设计内容
  • 2 系统设计
    • 2.1系统功能设计
      • 2.1.1用户管理
      • 2.1.2聊天室管理
      • 2.1.3聊天管理
      • 2.1.4系统管理
    • 2.2系统数据结构设计
    • 2.3系统主要函数设计
      • 2.3.1客户端
      • 2.3.2服务器
  • 3 系统实现
    • 3.1 开发环境配置
    • 3.2 功能模块的程序流程图及运行界面
      • 3.2.1功能模块流程图
      • 3.2.2运行界面
    • 3.3 关键代码分析说明
  • 4 程序调试分析
  • 参考文献
  • 附:源代码

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. 系统管理

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_inchar buf[BUFFSIZE], temp[BUFFSIZE];// 初始化服务端地址结构bzero(&serv_addr, sizeof(struct sockaddr_in)); // bzero 清零serv_addr.sin_family = AF_INET;                // sin_family   AF_INETserv_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 8888int sockfd;
void snd();
void get_help();
  • client.c
#include "client.h"int main()
{pthread_t thread;             // pthread_t 线程,gcc编译时需加上-lpthreadstruct sockaddr_in serv_addr; // struct sockaddr_inchar buf[BUFFSIZE], temp[BUFFSIZE];// 初始化服务端地址结构bzero(&serv_addr, sizeof(struct sockaddr_in)); // bzero 清零serv_addr.sin_family = AF_INET;                // sin_family   AF_INETserv_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");/* === 从此处开始 程序分做两个线程 === */// 创建发送消息的线程,调用发送消息的函数sndpthread_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) // 注意此处的\nexit(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 256int 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);
}

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

  1. NIO网络编程实战之简单多人聊天室

    NIO网络编程实战 利用NIO编程知识,实现多人聊天室. 1. NIO编程实现步骤 第一步:创建Selector 第二步:创建ServerSocketChannel,并绑定监听端口 第三步:将Chan ...

  2. 网络编程-基于MFC的仿QQ聊天室-2020

    基于MFC的仿QQ聊天室(2020) 有幸学习过网络编程的一些知识,出于对编程的热爱,把曾经的一次简单实践编程作业进行了自定义的完成. 编程所需: 编程工具为VS 2010,需要掌握MFC的基本操作以 ...

  3. JAVA网络编程NIO实现简易多人聊天室

    BIO模型 BIO即blocking IO,顾名思义是一种阻塞模型.当没有客户端连接时,服务端会一直阻塞,当有客户端新建连接时,服务端会新开一个线程去响应(不用多线程的话服务端同一时刻最多只能接收一个 ...

  4. 【Socket网络编程进阶与实战】-----简易聊天室案例

    前言 本篇博客实现:简易聊天室 聊天室案例: 聊天室数据传输设计 必要条件:客户端.服务器 必要约束:数据传输协议 原理: 服务器监听消息来源,客户端链接服务器并发送消息到服务器.

  5. Linux网络编程:用C语言实现的聊天程序(同步通信)

    通过TCP协议,用C语言实现的同步聊天程序,注释写的比较详细,个人觉得对字符串处理比较充分,能够正常编译运行,拿出来和大家分享一下! 1.客户端源代码: [cpp] view plaincopypri ...

  6. java 编程原理_Java网络编程 -- 网络编程基础原理

    Hello,今天记录下 Java网络编程 --> 网络编程基础原理. 一起学习,一起进步.继续沉淀,慢慢强大.希望这文章对您有帮助.若有写的不好的地方,欢迎评论给建议哈! 初写博客不久,我是杨展 ...

  7. python网络编程基础百度云_PYTHON网络编程基础 PDF 下载

    相关截图: 资料简介: <Python网络编程基础>全面介绍了使用Python语言进行网络编程的基础知识,主要内容包括网络基础知识.高级网络操作.Web Services.解析HTML和X ...

  8. 计算机网络(二) | 网络编程基础、Socket套接字、UDP和TCP套接字编程

    目录 一.网络编程基础 1.1 为什么需要网络编程 1.2 什么是网络编程 1.3 网络编程中的基本概念 二.Socket套接字 2.1 概念 2.2 分类 2.3 Java数据报套接字通信模型 2. ...

  9. python网络编程基础(线程与进程、并行与并发、同步与异步、阻塞与非阻塞、CPU密集型与IO密集型)...

    python网络编程基础(线程与进程.并行与并发.同步与异步.阻塞与非阻塞.CPU密集型与IO密集型) 目录 线程与进程并行与并发同步与异步阻塞与非阻塞CPU密集型与IO密集型 线程与进程 进程 前言 ...

最新文章

  1. [JQuery] jQuery选择器ID、CLASS、标签获取对象值、属性、设置css样式
  2. 深度 | 用代码构建机器心智,我们离这个目标还有多远?
  3. 开源Delphi:AutoCHM:CHM生成和还原Html工具
  4. 【转载】基于Linux命令行KVM虚拟机的安装配置与基本使用
  5. Some exceptional case in WebUI Component Repository Information System Design
  6. 【自适应(盲)均衡1】LMMSE、Godard、CMA常模、Sato等算法在信道均衡中的应用理论与MATLAB仿真
  7. TortoiseGit 更新远程仓库最新代码到本地仓库_入门试炼_05
  8. javascipt很有用的代码,实现全选与反选,还可以与struts2或sevelet交互使用
  9. 初创公司 经营_LibreCorps指导人道主义初创公司如何运行开源方式
  10. python百度知道_用Python写的一个【百度知道】自动点赞
  11. 各种机器学习和深度学习的中文微博情感分析
  12. jmter测试jmeter参数化(必须掌握)
  13. 输入输出知识点和问题超全总结(持续更新中)
  14. 数商云SCM供应链协同管理系统解决方案
  15. python中mid_Python生成音乐 之 mido库读取midi文件
  16. Carbon —— 代码分享利器
  17. 服务器软硬件安装和配置,Windows Server 2016-系统安装软硬件要求
  18. 喝酸奶竟然能预防霉菌性阴道炎
  19. 《中国图书馆图书分类法》(第五版)详表(中图分类号查询表)
  20. Java泛型方法的定义

热门文章

  1. Rhythmbox乱码的解决的方法
  2. 变频电源参数正确设定的重要性
  3. 像素鸟游戏开发(二)
  4. Robustness(健壮性)和 Correctness(正确性)
  5. 电脑网络连接正常,无法连接浏览器,无法上网
  6. 【AI名利场·公司】刚刚,猎豹的AI音箱开卖并夺销量第一,或许是他们机器人梦想渐进的一小步...
  7. 元学习之《Matching Networks for One Shot Learning》代码解读
  8. Android APP通过浏览器下载最新版本
  9. 该如何简单绘制CAD立方体呢?
  10. 《花开半夏》--4 我等你(2)