Linux+C实现简易聊天室

最近Linux网络程序设计课程大作业,要求设计一个简易的网络聊天室,功能如下:

  • 网络聊天室

功能要点:
(1)用户管理:注册、修改密码;
(2)聊天室管理:用户登录、创建聊天室、设置聊天室密码;
(3)聊天管理:在同一聊天室里,用户所发送的消息每位在线用户都可以收到,也可以单独给某位在线用户发消息;可以查询聊天室在线用户信息;
(4)系统管理:显示所有在线用户;显示所有聊天室;给所有在线用户群发消息;提供命令帮助,让用户了解命令的格式:
例如 send user1 message1表示给用户user1发送消息message1等。

本人编程学的比较烂,但是还是勉强凑出了这些功能,在这也分享一下我的代码,代码是一小部分非原创,因为是老师上课讲的一部分代码,剩下绝大部分均为自己写的,在这里分享一下。

首先server.c

#include <stdio.h>
#include <stdlib.h>     // exit
#include <string.h>
#include <unistd.h>     // bind listen
#include <time.h>       // time(NULL)   ctime(&ticks)
#include <netinet/in.h>
#include <arpa/inet.h>  // 必须包含,用于inet_ntop
#include <pthread.h>

#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<MAXMEM; i++)
        connfd[i] = -1;

    while(1)
    {
        int len;  		// = sizeof(cli_addr);
        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");
            //      continue;       // 此句可以不用,accept会阻塞等待
        }
        ticks = time(NULL);
        //sprintf(buff, "%.24s\r\n", ctime(&ticks));
        printf("%.24s\n\tconnect from: %s, port %d\n",
               ctime(&ticks), inet_ntop(AF_INET, &(cli_addr.sin_addr), buff, BUFFSIZE),
               ntohs(cli_addr.sin_port));      // 注意 inet_ntop的使用,#include <arpa/inet.h>

        /* === 针对当前套接字创建一个线程,对当前套接字的消息进行处理 === */
        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<MAXMEM; i++){
						if(strcmp(des, info_class[i].name) == 0){	/* 遍历匹配user1的姓名 */
							while(1){
								if((len=read(connfd[n], temp, BUFFSIZE)) > 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<MAXMEM; i++)
					{
						if(strcmp(info_class[i].chatroom, info_class[n].chatroom) == 0){
							strcat(member, info_class[i].name);	/* 将在线用户名添加到info */
						}
					}
					write(connfd[n], member, strlen(member));
				}
				else{  /* 不是私聊那就是在聊天室内群发 */
					for(i=0; i<MAXMEM; i++){
						/* 寻找结构体中聊天室名相同的发送信息 */
						if(strcmp(info_class[i].chatroom, info_class[n].chatroom) == 0){
							char chatroom[20];
							strcpy(chatroom, info_class[n].chatroom);
							chatroom[strlen(chatroom)-1] = '\0';

							ticks = time(NULL);
							strftime(mytime,sizeof(mytime),"[%H:%M:%S] [",localtime(&ticks));
							strcpy(buf, mytime);  				// 显示时间
							strcat(buf, chatroom);				// 显示群聊名称
							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));		/* 将消息发送给聊天室内的每一个人 */
						}
					}
				}
            }
            /* 如果发现关键字符串"send to",转入私聊程序段 */
            else 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<MAXMEM; i++){
					if(strcmp(des, info_class[i].name) == 0){	/* 遍历匹配user1的姓名 */
						while(1){
							if((len=read(connfd[n], temp, BUFFSIZE)) > 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<MAXMEM; i++)
				{
					if(connfd[i] != -1)
						write(connfd[i], buf, strlen(buf));
				}
            }
        }
    }

}

/* 注册函数 */
void regist(int n){
	char buf[BUFFSIZE],temp[50],info[20];
	int len,i;

	strcpy(buf,"Enter User name:");
	write(connfd[n],buf,strlen(buf));
	if((len = read(connfd[n], info, 20)) > 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<MAXMEM; i++)
			{
				if(connfd[i] != -1){
					strcat(info, info_class[i].name);	/* 将在线用户名添加到info */
				}
			}
			write(connfd[n], info, strlen(info));
			write(connfd[n], "Not sure what to do next, try 'help' command.\n",strlen("Not sure what to do next, try 'help' command.\n"));
		}
		else if(strcmp(temp, "2") == 0){				/* 显示所有聊天室 */
			strcpy(info, "---Current chatrooms---\n");
			for(int i=0; i<MAXMEM; i++)
			{
				if(connfd[i] != -1){
					for(int j=0; j<i; j++){
						if(strcmp(info_class[i].chatroom, info_class[j].chatroom) == 0){/* 同一个聊天室的名称只算作一次 */
							strcat(info, info_class[i].chatroom);/* 将正在使用的聊天室名附加到info */
						}
					}
				}
			}
			write(connfd[n], info, strlen(info));
			write(connfd[n], "Not sure what to do next, try 'help' command.\n",strlen("Not sure what to do next, try 'help' command.\n"));
		}
		else if(strcmp(temp, "3") == 0){				/* 创建聊天室 */
			create_chatroom(n);
		}
		else if(strcmp(temp, "4") == 0){				/* 加入聊天室 */
			join_chatroom(n);
		}
		else{
			strcpy(buf,"Invalid command, input again.\n");
			write(connfd[n], buf, strlen(buf));
			show(n);									/* 无效命令码,重新输入 */
		}
	}
}

/* 创建聊天室函数 */
void create_chatroom(int n){
	char buf[BUFFSIZE], temp[50], info[20];
	int len;

	strcpy(buf, "Give the chatroom a name:");
	write(connfd[n], buf, strlen(buf));
	if((len = read(connfd[n], info, 20)) > 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>

#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

运行结果我就不放图了,大家也可以跟我交流一下。

猜你喜欢

转载自blog.csdn.net/MARS_098/article/details/106890789