最近Linux网络程序设计课程大作业,要求设计一个简易的网络聊天室,功能如下:
- 网络聊天室
功能要点:
(1)用户管理:注册、修改密码;
(2)聊天室管理:用户登录、创建聊天室、设置聊天室密码;
(3)聊天管理:在同一聊天室里,用户所发送的消息每位在线用户都可以收到,也可以单独给某位在线用户发消息;可以查询聊天室在线用户信息;
(4)系统管理:显示所有在线用户;显示所有聊天室;给所有在线用户群发消息;提供命令帮助,让用户了解命令的格式:
例如 send user1 message1表示给用户user1发送消息message1等。
本人编程学的比较烂,但是还是勉强凑出了这些功能,在这也分享一下我的代码,代码是一小部分非原创,因为是老师上课讲的一部分代码,剩下绝大部分均为自己写的,在这里分享一下。
首先server.c
#include
#include // exit
#include
#include // bind listen
#include // time(NULL) ctime(&ticks)
#include
#include // 必须包含,用于inet_ntop
#include
#define PORT 8000
#define MAXMEM 10
#define BUFFSIZE 128
//#define DEBUG_PRINT 1 // 宏定义 调试开关
#ifdef DEBUG_PRINT
#define DEBUG(format, ...) printf("FILE: "__FILE__", LINE: %d: "format"\n", __LINE__, ##__VA_ARGS__)
#else
#define DEBUG(format, ...)
#endif
FILE *fp;
int listenfd, connfd[MAXMEM];
struct client{ /* 结构体存储一组用户名和对应的套接字 */
char name[20]; /* 用户名 */
int socket_name; /* 套接字 */
char chatroom[20]; /* 加入的聊天室 */
};
struct client info_class[MAXMEM]; /* 定义结构体数组 */
void quit(); /*输入quit时退出服务器*/
void rcv_snd(int n);
void regist(int n); /* 注册账户 */
void login(int n); /* 登录 */
void create_chatroom(int n); /* 创建聊天室函数 */
void join_chatroom(int n); /* 加入聊天室函数 */
void show(int n); /* 服务命令处理函数 */
int main()
{
struct sockaddr_in serv_addr, cli_addr;
int i;
time_t ticks;
pthread_t thread;
char buff[BUFFSIZE];
printf("running...\n(Prompt: enter command ""quit"" to exit server)\n");
DEBUG("=== initialize..."); // 初始化填充服务端地址结构
/*将server_addr指向内存的前sizeof(struct sockaddr_in)字节清零*/
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);
DEBUG("=== socket...");
/*socket 创建服务器端的监听套接字*/
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd < 0)
{
perror("fail to socket");
exit(-1);
}
DEBUG("=== bind...");
/*bind 将套接字与填充好的地址结构进行绑定*/
if(bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
{
perror("fail to bind");
exit(-2);
}
DEBUG("=== listening...");
/*listen 将主动连接套接字变为被动倾听套接字*/
listen(listenfd, MAXMEM);
/* === 创建一个线程,对服务器程序进行管理,调用quit函数 === */
pthread_create(&thread, NULL, (void *)(quit), NULL);
// 将套接字描述符数组初始化为-1,表示空闲
for(i=0; i
/* === 针对当前套接字创建一个线程,对当前套接字的消息进行处理 === */
pthread_create(malloc(sizeof(pthread_t)), NULL, (void *)(&rcv_snd), (void *)i);
}
return 0;
}
void quit()
{
char msg[10];
while(1)
{
scanf("%s", msg); // scanf 不同于fgets, 它不会读入最后输入的换行符
if(strcmp(msg, "quit") == 0)
{
printf("Byebye... \n");
close(listenfd);
exit(0);
}
}
}
void rcv_snd(int n)
{
int len, i;
char buf[BUFFSIZE] ,mytime[32], v[5]; /*v用接受服务命令字符*/
time_t ticks;
int ret;
write(connfd[n], "---Service select(1/2)---\n\t1.User registration\n\t2.User login\n",
strlen("---Service select(1/2)---\n\t1.User registration\n\t2.User login\n"));
len = read(connfd[n], v, 5); /* 读取客户端输入的1或者2命令 */
if(len > 0)
v[len-1] = '\0'; /* 去除换行符 */
if(strcmp(v,"1") == 0){ /*如果选择注册服务*/
strcpy(buf,"---User registration---\n");
/*在客户端显示当前状态*/
write(connfd[n],buf,strlen(buf));
regist(n);
}else if(strcmp(v,"2") == 0){ /*如果选择登录服务*/
strcpy(buf,"---User login---\n");
/*在客户端显示当前状态*/
write(connfd[n],buf,strlen(buf));
login(n);
}
else{
write(connfd[n], "Invalid command, enter again.\n",
strlen("Invalid command, enter again.\n"));
rcv_snd(n); /* 输入其他值时重新进入服务选项 */
}
/* 登陆成功后服务提示,这里就是大厅 */
write(connfd[n], "---Service select(1/2/3/4)---\n\t1.Show online users\n\t2.Show all chatrooms\n\t3.Create a chatroom\n\t4.Join a chatroom\n",
strlen("---Service select(1/2/3/4)---\n\t1.Show online users\n\t2.Show all chatrooms\n\t3.Create a chatroom\n\t4.Join a chatroom\n"));
show(n);
while(1)
{
char temp[BUFFSIZE];
char *str;
if((len=read(connfd[n], temp, BUFFSIZE)) > 0) // 检查如果有消息
{
temp[len-1] = '\0';
/* 当用户输入bye时,当前用户退出 */
if(strcmp(temp, "bye") == 0)
{
close(connfd[n]);
connfd[n] = -1;
pthread_exit(&ret);
}
else if(strcmp(temp, "help") == 0){
memset(buf, 0, sizeof(buf));
strcpy(buf, "\tYou can try these commands.\n");
write(connfd[n], buf, strlen(buf));
strcpy(buf, "[send to user1]: Send the following message to user1.\n");
write(connfd[n], buf, strlen(buf));
strcpy(buf, "[back]: Back to hall.\n");
write(connfd[n], buf, strlen(buf));
strcpy(buf, "[bye]: Exit the client.\n");
write(connfd[n], buf, strlen(buf));
}
else if(strcmp(temp, "back") == 0){
write(connfd[n], "---Service select(1/2/3/4)---\n\t1.Show online users\n\t2.Show all chatrooms\n\t3.Create a chatroom\n\t4.Join a chatroom\n",
strlen("---Service select(1/2/3/4)---\n\t1.Show online users\n\t2.Show all chatrooms\n\t3.Create a chatroom\n\t4.Join a chatroom\n"));
memset(info_class[n].chatroom, 0, sizeof(info_class[n].chatroom)); /* 返回大厅后即退出聊天室 */
show(n);
}
/* 如果当前客户端在聊天室内 */
else if(strlen(info_class[n].chatroom) != 0){
/* 如果发现关键字符串"send to",从聊天室转入私聊程序段 */
if((str = strstr(temp, "send to ")) != 0){
int x; /* 查找send to后跟用户名的开始下标值 */
char des[10]; /* 用来存储目标用户的名称,从send to user中获取 */
x = strspn(temp, "send to "); /* send to 后面用户名字符串开始的索引 */
strncpy(des, temp + x, strlen(temp)-x+1);
strcat(des, "\n");
for(i=0; i 0){
temp[len-1] = '\0';
if(strcmp(temp, "back") == 0){ /* 如果收到back字段,则返回群聊 */
write(connfd[n], "You have returned the chatroom, send a message or use 'back' again.\n",
strlen("You have returned the chatroom, send a message or use 'back' again.\n"));
break;
}
else{
ticks = time(NULL);
strftime(mytime,sizeof(mytime),"[%H:%M:%S]",localtime(&ticks));
strcpy(buf, mytime); // 显示时间
strcat(buf, "\t");
strcat(buf, info_class[n].name); // 显示用户名
buf[strlen(buf)-1] = '\0';
strcat(buf, ":\t");
strcat(buf, temp); // 客户端消息内容
strcat(buf, "\n");
write(connfd[i], buf, strlen(buf));
write(connfd[n], buf, strlen(buf));
}
}
}
}
}
}
else if(strcmp(temp, "show member") == 0){ /* show member显示聊天室内成员 */
char member[50];
strcpy(member, "---Current members---\n");
for(int i=0; i 0){
temp[len-1] = '\0';
if(strcmp(temp, "back") == 0){ /* 如果收到back字段,则返回大厅 */
write(connfd[n], "You are in the hall now, send to everyone or 'back' to hall.\n",
strlen("You are in the hall now, send to everyone or 'back' to hall.\n"));
break;
}
else{
ticks = time(NULL);
strftime(mytime,sizeof(mytime),"[%H:%M:%S]",localtime(&ticks));
strcpy(buf, mytime); // 显示时间
strcat(buf, "\t");
strcat(buf, info_class[n].name); // 显示用户名
buf[strlen(buf)-1] = '\0';
strcat(buf, ":\t");
strcat(buf, temp); // 客户端消息内容
strcat(buf, "\n");
write(connfd[i], buf, strlen(buf));
write(connfd[n], buf, strlen(buf));
}
}
}
}
}
}
/* 默认状态下在大厅消息将发送给所有在线用户 */
else{
ticks = time(NULL);
strftime(mytime,sizeof(mytime),"[%H:%M:%S]",localtime(&ticks));
strcpy(buf, mytime); // 显示时间
strcat(buf, "\t");
strcat(buf, info_class[n].name); // 显示用户名
buf[strlen(buf)-1] = '\0';
strcat(buf, ":[Global message]\t");
strcat(buf, temp); // 客户端消息内容
strcat(buf, "\n");
/* 发给在线所有用户 */
for(i=0; i 0) /* 读注册时从客户端传回来的用户名 */
{
fp = fopen("UserInfo.txt","a+");
// strcpy(temp, info);
while(!feof(fp)){ /* 一直读到文件结束 */
fgets(temp,50,fp); /* 读取一行 */
if(strcmp(temp, info) == 0){ /* 如果存在某一行的值与输入用户名相同,则提示已注册 */
strcpy(buf,"User exist! Please select another name!\n");
write(connfd[n], buf, strlen(buf));
regist(n); /* 如果用户已存在就跳转到函数开始重新注册 */
}
}
strcpy(buf,"Enter User password:");
write(connfd[n], buf, strlen(buf));
if((len = read(connfd[n], temp, 50)) > 0) /* 读注册时从客户端传回来的密码 */
{
strcat(info, temp);
if(fp = fopen("UserInfo.txt","a+")) /* 以追加方式打开用户信息文件,第一次不存在会创建 */
{
fputs(info, fp);
fclose(fp); /* 写入后关闭文件内容才能显示 */
}
}
}
strcpy(buf,"Success, Go to login\n");
write(connfd[n],buf,strlen(buf));
login(n); /* 注册成功后跳转到登录 */
}
/* 登录函数 */
void login(int n){
char buf[BUFFSIZE],temp[50],info[50];
int len;
strcpy(buf,"User name:");
write(connfd[n], buf, strlen(buf));
if((len = read(connfd[n], temp, 50)) > 0){
//temp[len-1] = '\0';
fp = fopen("UserInfo.txt","r");
while(!feof(fp)){ /* 一直读到文件结束 */
fgets(info, 50, fp); /* 读取一行 */
if(strcmp(temp, info) == 0){ /* 如果存在某一行的值与输入用户名相同,则用户已经注册,可以正常登录*/
char str[50];
/* 如果账户存在,那么下一行就是本账户的密码 */
fgets(str, 50, fp); /* 再读一行,将密码读取出来 */
strcat(info, str);
fclose(fp);
break;
}
}
strcpy(buf,"Enter User password:");
write(connfd[n],buf,strlen(buf));
char str[50];
if((len = read(connfd[n], str, 50)) > 0) /* 读注册时从客户端传回来的密码 */
{
//temp[len-1] = '\0';
strcpy(info_class[n].name, temp); /* 将客户名赋给结构体name */
strcat(temp, str);
if(strcmp(temp, info) == 0){
info_class[n].socket_name = connfd[n]; /* 登陆成功后将客户套接字赋给结构体的套接字成员变量 */
strcpy(buf,"Login success.\n");
write(connfd[n],buf,strlen(buf));
}
else{
strcpy(buf,"User not exist or Password error! Enter again.\n");
write(connfd[n],buf,strlen(buf));
login(n);
}
}
}
}
/* 服务端接收处理大厅相应客户端命令 */
void show(int n){
char buf[BUFFSIZE], temp[5], info[50];
int len;
if((len = read(connfd[n], temp, 5)) > 0){
temp[len-1] = '\0'; /* 去除换行符 */
if(strcmp(temp, "1") == 0){ /* 显示在线用户 */
strcpy(info, "---Current online users---\n");
for(int i=0; i 0){ /* 接收聊天室名字 */
fp = fopen("ChatInfo.txt", "a+");
while(!feof(fp)){ /* 一直读到文件结束 */
fgets(temp,50,fp); /* 读取一行 */
if(strcmp(temp, info) == 0){ /* 如果存在某一行的值与输入用户名相同,则提示聊天室已被创建 */
strcpy(buf,"Same name chatroom exist! Please select another name!\n");
write(connfd[n], buf, strlen(buf));
create_chatroom(n); /* 如果聊天室已存在就跳转到函数开始重新创建 */
}
}
strcpy(buf, "Set a password for the chatroom:");
write(connfd[n], buf, strlen(buf));
if((len = read(connfd[n], temp, 50)) > 0){ /* 接收聊天室密码 */
strcpy(info_class[n].chatroom, info); /* 创建完成后自动加入聊天室 */
strcat(info, temp);
if(fp = fopen("ChatInfo.txt","a+")) /* 将聊天室信息添加到文件中保存 */
{
fputs(info, fp);
fclose(fp); /* 写入后关闭文件内容才会最终保存 */
}
}
}
strcpy(buf, "Success, now you are in the chatroom.\n");
write(connfd[n], buf, strlen(buf));
write(connfd[n], "You can send message in the chatroom or 'back' to hall.\n",strlen("You can send message in the chatroom or 'back' to hall.\n"));
}
/* 加入聊天室函数 */
void join_chatroom(int n){
char buf[BUFFSIZE], temp[50], info[20];
int len;
strcpy(buf, "Enter the name of chatroom you want to join:");
write(connfd[n], buf, strlen(buf));
if((len = read(connfd[n], temp, 50)) > 0){
fp = fopen("ChatInfo.txt", "r");
while(!feof(fp)){
fgets(info, 20, fp); /* 读取一行 */
if(strcmp(temp, info) == 0){ /* 如果存在某一行的值与输入相同,则存在聊天室,可以正常加入 */
char str[50];
/* 如果聊天室存在,那么下一行就是本聊天室的密码 */
fgets(str, 50, fp); /* 再读一行,将密码读取出来 */
strcat(info, str);
fclose(fp);
break;
}
}
strcpy(buf, "Enter the password before you join in:");
write(connfd[n], buf, strlen(buf));
char str[50];
if((len = read(connfd[n], str, 50)) > 0) /* 读创建时从客户端传回来的聊天室密码 */
{
strcpy(info_class[n].chatroom, temp); /* 将聊天室名赋给结构体chatroom成员变量 */
strcat(temp, str);
if(strcmp(temp, info) == 0){
strcpy(buf, "Joined, now you can send a message in the chatroom.\n");
write(connfd[n], buf, strlen(buf));
}
else{
strcpy(buf, "Chatroom not exist or Password error! Enter again.\n");
write(connfd[n],buf,strlen(buf));
join_chatroom(n);
}
}
}
}
下面为client.c
#include
#include
#include
#include
#define BUFFSIZE 128
#define HOST_IP "127.0.0.1"
#define PORT 8000
int sockfd;
void snd();
int main()
{
pthread_t thread; /*pthread_t 线程,gcc编译时需加上-lpthread*/
struct sockaddr_in serv_addr; // struct sockaddr_in
char buf[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) // connect
{
perror("fail to connect");
exit(-2);
}
/* === 主进程接收数据,从此处开始 程序分做两个线程 === */
// 创建发送消息的线程,调用发送消息的函数snd
pthread_create(&thread, NULL, (void *)(&snd), NULL); // pthread_create
// 接收消息的线程
while(1)
{
int len;
if((len=read(sockfd, buf, BUFFSIZE)) > 0) // read 读取通信套接字
{
buf[len] = '\0'; // 添加结束符,避免显示缓冲区中残留的内容
printf("\n%s", buf);
fflush(stdout); // fflush 冲洗标准输出,确保内容及时显示
}
}
return 0;
}
/*发送消息的函数*/
void snd()
{
char temp[32], buf[BUFFSIZE];
fgets(temp, 32, stdin); // fgets 会读取输入字符串后的换行符
write(sockfd, temp, strlen(temp)); // write 写入通信套接字
while(1)
{
fgets(buf, BUFFSIZE, stdin);
write(sockfd, buf, strlen(buf));
/* 客户端输入bye则退出 */
if(strcmp(buf, "bye\n") == 0) // 注意此处的\n
exit(0);
}
}
命令行编译时使用如下命令:
gcc -o server server.c -lpthread
gcc -o client client.c -lpthread
运行结果我就不放图了,大家也可以跟我交流一下。