网络编程基础,纯C语言实现聊天室(附源代码)——从铁矿到钢铁的打造
文章目录
- 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聊天室设计内容
设计一个聊天系统,实现一下内容:
用户管理
用户注册、登录、修改密码聊天室管理
用户登录、创建聊天室、设置聊天室密码、用户可以加入聊天室、退出聊天室聊天管理
在同一聊天室里,用户所发送的消息每位在线用户都可以收到,也可以单独给某位在线用户发消息;给所有在线用户群发消息系统管理
显示所有在线用户;显示所有聊天室;可以查询聊天室在线用户信息;提供命令帮助,让用户了解命令的格式例如:
send user1 message1 /*表示给用户user1发送消息message1等*/
2 系统设计
2.1系统功能设计
网络聊天室系统分为服务器和客户端,客户端主要进行命令的发送,以及接收从服务器发送过来的信息。服务器主要进行指令的解析,然后根据解析出来的指令执行相关的函数,将执行的结果返回给客户端。
2.1.1用户管理
用户管理要完成的用户的注册、登录和密码修改。在服务器程序未启动时,已注册的用户信息存储在文件中。服务器程序启动之后把用户的用户名和密码加载到结构体数组中,当用户登录时,依次遍历数组,将数组中的用户名和用户输入的用户名进行比较,若用户名相同,继续比较密码,若密码相同,则登录成功,否则登录失败。若在数组中没找到用户输入的用户名,则告知用户该账号不存在。
用户注册时,引导用户输入用户名和密码,将用户输入的用户名和密码存入结构体数组中,若成功执行,则告知用户注册成功。
当用户想要修改密码时,需要输入更改密码命令和你想要更改的密码,然后从在结构体数组中查询该用户,找到之后将其旧密码修改为新密码,并提示用户密码修改成功。
当服务器程序退出运行时,将数组中的用户名和密码已“w+”的模式(即先清除文件里的内容,再写入新的内容)写入文件,这样就可以保证每次新注册的用户的信息都存入文件中。用一句话来总结用户管理就是,当服务器程序未运行时,用户信息存在文件中;服务器程序运行时,将用户信息加载到内存;当服务器程序关闭时,再将用户信息从内存写入到文件中。
2.1.2聊天室管理
聊天室存于结构体数组中。用户登录之后便可以创建聊天室,创建聊天室时,用户需要输入聊天室的名字和密码,然后依次遍历聊天室结构体数组,当找到一个空闲的聊天室时,便将用户输入的名字和密码赋给该聊天室,并且还要将创建者加入到该聊天室的成员中。
用户加入聊天室时,需要输入聊天室的名字和聊天室的密码。程序先判断用户是否已经加入了聊天室,如果用户已经加入了聊天室,则提醒用户已经加入了聊天室,不能再加入另外一个聊天室。如果用户还未加入聊天室,则遍历整个聊天室结构体数组,将用户输入的聊天室名和数组中的聊天室名进行比较,若相同继续比较密码,若密码不行同,则告知用户密码不正确,若密码相同,则把用户加入到当前聊天室的成员中,告知用户加入聊天室成功。
当用户退出聊天室时,遍历整个聊天室结构体数组,找到用户加入的聊天室,然后将用户从该聊天室中删除,告知用户成功退出聊天室。
2.1.3聊天管理
用户发送消息的情况有三种,分别为1.发送给聊天室;3.群发信息,所有人都能收到;2.发送给私人(私聊)。用户发送信息都用到了“send”命令,可以根据命令后的选项确定用户要发送的是哪种类型的信息。
- 聊天室消息
发送聊天室消息“send”后面跟“-chatroom”选项。当用户发送聊天室消息时,先查询用户在哪个聊天室中,若没找到,则告知用户还未加入聊天室。若找到用户所在的聊天室,则用一个循环向聊天室中的所有成员发送消息。 - 群发消息
群发消息后跟“send”后面跟“-all”选型。当用户发送群发消息时,只需要用一个循环,向所有在线的用户消息即可。 - 私聊
发送私聊信息,“send”后面跟你要发送的用户的用户名。当用户发送私聊信息时,先查询被发送用户是否存在或在线,若被发送用户不存在或不在线,则提醒用户发送失败。若被发送用户存在且在线,则将信息发送过去,告知用户发送成功。
2.1.4系统管理
显示在线用户信息、显示所有聊天室信息、显示所有聊天室在线用户信息,都用到了“ls”命令,根据命令后的选型确定用户要显示的是什么。
- 显示在线用户信息
显示在线用户信息在“ls”后面跟“-users”选项。显示在线用户信息只需要直接遍历在线用户结构体数组,把查询到的用户返回给客户端显示出来即可。 - 显示所有聊天室信息
显示所有聊天室信息在“ls”后面跟“-chatrooms”选项。显示所有聊天室信息只需要遍历聊天室结构体数组,找出可用的聊天室,并把查询结果返回给客户端。 - 显示所有聊天室在线用户信息
显示所有聊天室在线用户信息在“ls”后面跟“-inrmusr”选项。显示所有聊天室在线用户信息,需要先从聊天室结构体数组中找到要查询的聊天室,然后遍历该聊天室中的用户,将用户信息返回给客户端显示。
帮助信息分为两部分,一部分为用户开始运行客户端程序时提供的帮助信息,有注册、登录、显示帮助信息、退出四条帮助信息;另一部分帮助信息需要输入“help”命令获取,它包含了系统需要用到的所有命令的使用帮助信息。“help”命令获取帮助信息不需要从服务器获取,直接在客户端调用函数来显示。
2.2系统数据结构设计
struct user结构体
struct user {char username[20]; //用户名char password[20]; //用户密码 };
该结构体用来存储从文件中加载到内存中的所有用户信息,无论用户是否在线,都把用户存在该结构体中。该结构体包含用户的用户名和密码。
struct user_socket结构体
struct user_socket {char username[20];int socketfd;int status; //标识是否在线 0:在线 -1:下线 };
该结构体用来存储在线用户,“username”是用户名,“socketfd”是服务器与客户端建立连接时,服务器存储套接字文件描述符数组的下标,“status”标识用户是否在线,初始时为-1。
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。
int connfd[]套接字描述符数组
该数组为一个整型数组,里面存的是客户端和服务器建立连接时的套接字,要访问某一个套接字直接使用数组下标访问。初始时为-1。
2.3系统主要函数设计
2.3.1客户端
int main()
客户端主程序中主要完成设置服务器地址,创建客户端流式套接字,打印登录前的帮助信息,之后程序分为两个线程,一个线程去执行客户端的发送函数,另一个线程继续在主函数中完成接收消息的任务。
void snd()
该函数由一个线程执行,作用是接收客户端的输入,若是接受到的输入是“help”或“quit”,则直接调用get_hep()函数或是退出客户端程序;若是其他命令,则直接发给服务器处理。
2.3.2服务器
int main()
服务器主程序主要完成服务器地址的设置,创建服务器流式套接字,将地址结构和套接字绑定,设置侦听端口。然后创建一个线程,该线程接受“quit”命令,用于退出服务器程序。最后接收客户端连接,针对每一个套接字建立一个线程,对当前套接字的消息进行处理。
void init()
完成相应结构体数组和变量的初始化,将用户从文件中读入内存。结构体数组主要由三个,分别为:
struct user users[MAXMEM]; //记录所有用户 struct user_socket online_users[MAXMEM]; //记录在线用户 struct chatroom chatrooms[MAXROOM]; //记录聊天室
初始时要把online_users的status、chatroomd的status以及chatroom里的user数组全部初始化为-1。
void save_users()
将内存中的的所有用户信息从新写入到文件中。
void quit()
该函数由一个线程执行,当服务器输入“quit”命令时,执行该函数,主要调用save_users()函数,将所有用户写入文件,关闭套接字。
void rcv_snd(int n)
在客户端与服务器建立连接后,针对每一个套接字建立一个线程来执行该函数,该函数完成服务器的接收和发送功能。
参数 含义 n 建立服务器与客户端时存入connfd[]数组的套接字的对应下标 例如:建立的套接字为
connfd[i] = accept(listenfd, (struct sockaddr *)&cli_addr, &len);
则传入的参数为rcv_snd(i);
该函数主要分为两个循环。第一个循环在用户登录前执行,主要完成用户的登录或者注册,当用户登录成功后,跳出该循环去执行第二个循环。第二个循环接收从客户端发来的命令,并将命令解析,根据解析后的命令决定执行的函数,最后将执行的结果写回给客户端。
int user_login(int n)
该函数在客户端输入“login”命令后执行,完成用户的登录。
参数 含义 n 建立服务器与客户端时存入connfd[]数组的套接字的对应下标 返回值为0或-1,登录成功返回0,失败返回-1。
函数执行时,提示用户输入用户名和密码,然后在users[]数组中查询该用户的用户名,若未找到,则告知“Account does not exist.”,返回-1;若找到用户名,则比较密码是否一致,若密码不一致,则告知“Wrong password.”返回-1;若密码一致,登录成功,告知用户“Login successfully.”,返回0。
void register_user(int n)
该函数在客户端输入“register”命令后执行,完成用户注册。
参数 含义 n 建立服务器与客户端时存入connfd[]数组的套接字的对应下标 函数执行时,提示用户输入要注册的用户名和密码,然后验证要注册的用户名是否存在,若用户名已存在,则告知用户“The username already exists.”,函数结束执行;若用户名不存在,则将用户名和密码存入users[]数组中,同时告知用户“Account created successfully.”。
void change_passwd(int sfd, char *passwd)
该函数在用户输入命令“chgpsw xxx”后执行,完成用户密码的修改。“xxx”想要修改为的密码,作为参数传入该函数。
参数 含义 sfd 建立服务器与客户端时存入connfd[]数组的套接字的对应下标 passwd 想要修改为的密码 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”返回给客户端。
void send_all_msg(char *msg, int sfd)
该函数在用户输入命令“send -all xxx”后执行,完成用户信息的群发功能,“xxx”为发送的消息的内容。
参数 含义 msg 发送的消息的内容 sfd 建立服务器与客户端时存入connfd[]数组的套接字的对应下标 void send_chatroom_msg(char *msg, int sfd)
该函数在用户输入命令“send -chatroom xxx”后执行,完成用户消息在聊天室内的发送,“xxx”为发送的消息的内容。
参数 含义 msg 发送的消息的内容 sfd 建立服务器与客户端时存入connfd[]数组的套接字的对应下标 void get_online_users(int sfd)
该函数在用户输入命令“ls -users”后执行,完成查询所有在线用户的功能。
参数 含义 sfd 建立服务器与客户端时存入connfd[]数组的套接字的对应下标 void get_online_chatrooms(int sfd)
该函数在用户输入命令“ls -chatrooms”后执行,完成查询所有已创建房间的功能。
参数 含义 sfd 建立服务器与客户端时存入connfd[]数组的套接字的对应下标 void get_inroom_users(int sfd)
该函数在用户输入命令“ls -inrmusr”后执行,完成查询用户所加入聊天室的所有成员信息。
参数 含义 sfd 建立服务器与客户端时存入connfd[]数组的套接字的对应下标 void create_chatroom(char *name, char *passwd, int sfd)
该函数在用户输入命令“create chatroom passwd”后执行,完成创建一个叫做“chatroom”密码为“passwd”的聊天室。
参数 含义 name 聊天室的名字 passwd 聊天室的密码 sfd 建立服务器与客户端时存入connfd[]数组的套接字的对应下标 void join_chatroom(char *name, char *passwd, int sfd)
该函数在用户输入命令“join chatroom passwd”后执行,完成加入一个叫做“chatroom”加入密码为“passwd”的聊天室。
参数 含义 name 聊天室的名字 passwd 聊天室的加入密码 sfd 建立服务器与客户端时存入connfd[]数组的套接字的对应下标 void exit_chatroom(int sfd)
该函数在用户输入命令“exit”后执行,完成退出聊天室的功能。
参数 含义 sfd 建立服务器与客户端时存入connfd[]数组的套接字的对应下标 void quit_client(int n)
该函数在用户输入命令“quit”后执行,表示用户要退出客户端程序,服务器要关闭与该用户的套接字,同时修改该用户的状态为下线,退出与该用户连接时创建的线程。
参数 含义 sfd 建立服务器与客户端时存入connfd[]数组的套接字的对应下标 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功能模块流程图
用户管理
聊天室管理
聊天管理
系统管理
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 关键代码分析说明
以为聊天室的客户端和服务器连接是不能断开的,除非有一方程序停止运行,所以客户端和服务器之间的连接采用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);}
服务器对待每一个来自客户端的连接的处理方式是:服务器先从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);}
服务器中最重要的函数是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);}}} }
在服务器退出服务,执行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 程序调试分析
从文件读出数据时数据缺失,于用户输入的注册时输入的用户名和密码不一致。
一开始设计时存入文件的数据没有转行,每条数据用一个特殊字符结尾,然后读的时候读到特殊符号就停止,将该条数据读出,再读下一条数据,直到文件结尾。然而这样在读出数据时很容易出错,并且代码繁琐。
解决方法:一条数据占一行,写入文件时加入’\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++; }
当一个用户登录系统,然后下线,调用查看在线用户信息时,仍然能看到该用户的信息。
问题来源:结构体struct user_socket没有标识用户是否在线的标志,若一个用户退出聊天系统,只要还存在于struct user_socket数组中,就可以认为他还是在线的。
解决方法:在结构体struct user_socket添加一个状态status,标识用户是否在线。
struct user_socket {char username[20];int socketfd;int status; //标识是否在线 0:在线 -1:下线 };
在用户加入聊天室时,同时比较聊天室名、聊天室密码和用户输入的聊天室名、聊天室密码,但是这种比较方法在某些情况下会造成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语言实现聊天室(附源代码)——从铁矿到钢铁的打造相关推荐
- NIO网络编程实战之简单多人聊天室
NIO网络编程实战 利用NIO编程知识,实现多人聊天室. 1. NIO编程实现步骤 第一步:创建Selector 第二步:创建ServerSocketChannel,并绑定监听端口 第三步:将Chan ...
- 网络编程-基于MFC的仿QQ聊天室-2020
基于MFC的仿QQ聊天室(2020) 有幸学习过网络编程的一些知识,出于对编程的热爱,把曾经的一次简单实践编程作业进行了自定义的完成. 编程所需: 编程工具为VS 2010,需要掌握MFC的基本操作以 ...
- JAVA网络编程NIO实现简易多人聊天室
BIO模型 BIO即blocking IO,顾名思义是一种阻塞模型.当没有客户端连接时,服务端会一直阻塞,当有客户端新建连接时,服务端会新开一个线程去响应(不用多线程的话服务端同一时刻最多只能接收一个 ...
- 【Socket网络编程进阶与实战】-----简易聊天室案例
前言 本篇博客实现:简易聊天室 聊天室案例: 聊天室数据传输设计 必要条件:客户端.服务器 必要约束:数据传输协议 原理: 服务器监听消息来源,客户端链接服务器并发送消息到服务器.
- Linux网络编程:用C语言实现的聊天程序(同步通信)
通过TCP协议,用C语言实现的同步聊天程序,注释写的比较详细,个人觉得对字符串处理比较充分,能够正常编译运行,拿出来和大家分享一下! 1.客户端源代码: [cpp] view plaincopypri ...
- java 编程原理_Java网络编程 -- 网络编程基础原理
Hello,今天记录下 Java网络编程 --> 网络编程基础原理. 一起学习,一起进步.继续沉淀,慢慢强大.希望这文章对您有帮助.若有写的不好的地方,欢迎评论给建议哈! 初写博客不久,我是杨展 ...
- python网络编程基础百度云_PYTHON网络编程基础 PDF 下载
相关截图: 资料简介: <Python网络编程基础>全面介绍了使用Python语言进行网络编程的基础知识,主要内容包括网络基础知识.高级网络操作.Web Services.解析HTML和X ...
- 计算机网络(二) | 网络编程基础、Socket套接字、UDP和TCP套接字编程
目录 一.网络编程基础 1.1 为什么需要网络编程 1.2 什么是网络编程 1.3 网络编程中的基本概念 二.Socket套接字 2.1 概念 2.2 分类 2.3 Java数据报套接字通信模型 2. ...
- python网络编程基础(线程与进程、并行与并发、同步与异步、阻塞与非阻塞、CPU密集型与IO密集型)...
python网络编程基础(线程与进程.并行与并发.同步与异步.阻塞与非阻塞.CPU密集型与IO密集型) 目录 线程与进程并行与并发同步与异步阻塞与非阻塞CPU密集型与IO密集型 线程与进程 进程 前言 ...
最新文章
- [JQuery] jQuery选择器ID、CLASS、标签获取对象值、属性、设置css样式
- 深度 | 用代码构建机器心智,我们离这个目标还有多远?
- 开源Delphi:AutoCHM:CHM生成和还原Html工具
- 【转载】基于Linux命令行KVM虚拟机的安装配置与基本使用
- Some exceptional case in WebUI Component Repository Information System Design
- 【自适应(盲)均衡1】LMMSE、Godard、CMA常模、Sato等算法在信道均衡中的应用理论与MATLAB仿真
- TortoiseGit 更新远程仓库最新代码到本地仓库_入门试炼_05
- javascipt很有用的代码,实现全选与反选,还可以与struts2或sevelet交互使用
- 初创公司 经营_LibreCorps指导人道主义初创公司如何运行开源方式
- python百度知道_用Python写的一个【百度知道】自动点赞
- 各种机器学习和深度学习的中文微博情感分析
- jmter测试jmeter参数化(必须掌握)
- 输入输出知识点和问题超全总结(持续更新中)
- 数商云SCM供应链协同管理系统解决方案
- python中mid_Python生成音乐 之 mido库读取midi文件
- Carbon —— 代码分享利器
- 服务器软硬件安装和配置,Windows Server 2016-系统安装软硬件要求
- 喝酸奶竟然能预防霉菌性阴道炎
- 《中国图书馆图书分类法》(第五版)详表(中图分类号查询表)
- Java泛型方法的定义