[Linux C] Simple group chat room 1.0

Chat Room Introduction

​ This chat room is written based on LinuxC, using knowledge such as tcp protocol, multi-threading, mutex, condition variables, etc., to realize a group chat room that can access a maximum of 20 users; after the server runs, the user runs User terminal access, enter the user name to access; when a new user accesses or goes offline, a group message will be sent to remind other online users; the operation effect is as follows:

insert image description here

insert image description here

Due to time constraints, this chat room is version 1.0, and there are still many areas that need to be optimized, such as concurrency mechanism, number of access clients, multiplexing of numbers after clients go offline (you can consider using a linked list instead of an array), code simplicity, etc. The problem will be optimized and updated in the future;

Chat room source code

Server

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>

#define BUFSIZE 512
#define ClientMax 20

int s_fd;
int c_fd[ClientMax] = {
    
    0};	//最大可接收20个客户端的套接字

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  w_msg = PTHREAD_COND_INITIALIZER;

char sendBuf[BUFSIZE] = {
    
    0};

//构造一个结构体用于存储客户端用户的名字及套接字编号
struct Userinfo
{
    
    
	int num;
	char username[20];
};



//用于发送信息的线程函数
void *sendmsg_func(void *p)
{
    
    
	int i;
	printf("启动信息发送线程:\n");
	while(1)
	{
    
    
		pthread_mutex_lock(&lock);
		//利用条件变量,当收到任一客户端的信息后,就群发该信息到全体客户端中
		pthread_cond_wait(&w_msg,&lock);
	
		//给所有在线的客户端发送信息
		for(i = 0;c_fd[i] != 0 && i < ClientMax;i++)
		{
    
    
			if (c_fd[i] == -1)
			{
    
    
				continue;	//如果是已退出的客户端,则不发送信息
			}
			else
			{
    
    
				if(write(c_fd[i],sendBuf,BUFSIZE) < 0 )
   				{
    
    
        			perror("write");
        			exit(-1);
    			}
			}
		}
		pthread_mutex_unlock(&lock);	
	}
}
 
//用于接收客户端信息的函数
void *recv_func(void *p)
{
    
    
	//将传递进来的用户姓名、socket编号存到局部变量中,方便使用
	int tmp_cnt = ((struct Userinfo *)p)->num;
	char tmp_username[20] = {
    
    0};
	strcpy(tmp_username,((struct Userinfo *)p)->username);

	char readBuf[BUFSIZE] = {
    
    0};
	int n_read = 0;
	printf("启动%d号线程用于接收信息\n",tmp_cnt);
	
	//通知聊天室内所有用户有新用户上线
	pthread_mutex_lock(&lock);
    memset(sendBuf,0,BUFSIZE);
	sprintf(sendBuf,"%s上线了\n",tmp_username);
    pthread_cond_signal(&w_msg);
    pthread_mutex_unlock(&lock);
	
	//不断接收对应套接字用户发来的信息	
	while(1)
	{
    
    
		memset(readBuf,0,BUFSIZE);
		n_read = read(c_fd[tmp_cnt],readBuf,sizeof(readBuf));
		if(n_read == -1)
		{
    
    
			perror("read");
			exit(1);
		}
		else if(n_read == 0)
		{
    
    
			//用户下线了,群发信息告知其他用户
			pthread_mutex_lock(&lock);
       		memset(sendBuf,0,BUFSIZE);
        	sprintf(sendBuf,"%s下线了\n",tmp_username);
        	pthread_cond_signal(&w_msg);
        	pthread_mutex_unlock(&lock);

			c_fd[tmp_cnt] = -1;		//如果对应的客户端退出,则令对应的c_fd的值为-1,表示掉线
			pthread_exit(NULL);	//如果对方关闭,结束线程
		}
    	else 
   		{
    
    
        	printf("#%s\n",readBuf);	//将用户发送的信息打印在服务端,若有数据库,这里可以将聊天记录存在数据库
		}
		//给群发信息的线程发送信号,群发用户发送进来的消息
		pthread_mutex_lock(&lock);
		memset(sendBuf,0,BUFSIZE);
		strcpy(sendBuf,readBuf);
		pthread_cond_signal(&w_msg);
		pthread_mutex_unlock(&lock);
	}
}

//信号中断函数,若本程序被外来信号打断,可以及时清理现场,释放资源
void int_handler(int s)
{
    
    
	int i;
	pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&w_msg);
	for(i=0;c_fd[i] != 0 && i < ClientMax;i++)
	{
    
    
		if(c_fd[i] == -1)
			continue;
		else
			close(c_fd[i]);
	}
    close(s_fd);
    exit(0);
}

void main()
{
    
    
	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr[ClientMax];
	pthread_t tid[ClientMax] = {
    
    0};
	int addr_len = sizeof(struct sockaddr_in);
	int err;	
	int c_cnt = 0;
	struct Userinfo userinfo[ClientMax] = {
    
    0};	

	//创建socket
	s_fd = socket(AF_INET,SOCK_STREAM,0);	
	if (s_fd < 0)
	{
    
    
		perror("socket");
		exit(1);
	}
	
	//取消关闭socket后的wait等待
	int val =1;
	if (setsockopt(s_fd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val)) < 0)
	{
    
    
		perror("setsockopt");
		exit(1);
	}

	//bind()
	s_addr.sin_family = AF_INET;
	s_addr.sin_port = htons(8899);		//host to net short
	inet_aton("0.0.0.0",&s_addr.sin_addr);	//sin_addr即为in_addr格式
	
	if(bind(s_fd,(struct sockaddr*)&s_addr,sizeof(s_addr)) == -1)
	{
    
    
		perror("bind");
		exit(-1);
	} 
	
	//listen()
	if(listen(s_fd,200) < 0)	//暴露s_fd指向的socket给客户端连接,最多接受200个连接
	{
    
    
		perror("listen");
		exit(-1);
	}

	//创建一个线程用来发送消息给所有客户端
	pthread_t send_tid;
	err = pthread_create(&send_tid,NULL,sendmsg_func,NULL);
	if(err)
	{
    
    
		fprintf(stderr,"Create pthread fail:%s\n",strerror(err));
		exit(1);
	}
	sleep(1);	//让发送信息线程有足够时间加锁
	signal(SIGINT,int_handler);	


	//不断等待是否有新客户端接入
	while(1)
	{
    
    
		userinfo[c_cnt].num = c_cnt;	//给新接入的客户端套接字分配一个编号

		//等待新客户端接入
		c_fd[c_cnt] = accept(s_fd,(struct sockaddr*)(c_addr+c_cnt),&addr_len);	//接收客户端的连接
		if (c_fd[c_cnt] < 0)
		{
    
    
			perror("accept()");
			exit(-1);
		}
		printf("get connect %s\n",inet_ntoa(c_addr[c_cnt].sin_addr));
		
		//将客户端发送来的姓名存入userinfo结构体中
		err = read(c_fd[c_cnt],userinfo[c_cnt].username,sizeof(userinfo[c_cnt].username));
		if(err == -1)
        {
    
    
            perror("read");
            exit(1);
        }

		//创建一个新线程用来发送信息
		err = pthread_create((tid+c_cnt),NULL,recv_func,userinfo+c_cnt);
		if (err)
		{
    
    
			fprintf(stderr,"Create pthread fail:%s\n",strerror(err));
			exit(1);
		}	
		c_cnt++;	//客户端计数器+1
	}
	//close()
	//回收资源
	close(s_fd);
	pthread_mutex_destroy(&lock);
	pthread_cond_destroy(&w_msg);
	exit(0);
}

client

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <time.h>
#define BUFSIZE 512

int c_fd;
int n_read;
char readBuf[BUFSIZE] = {
    
    0};

//用来发送信息的线程函数
void *rcvmsg_func(void *p)
{
    
    
	while(1)
	{
    
    
		memset(readBuf,0,BUFSIZE);	//清空readBuf
		//读取来自服务端的信息
		n_read = read(c_fd,readBuf,sizeof(readBuf));
		if(n_read == -1)
		{
    
    
			perror("read");
			exit(1);
		}
		else if(n_read == 0) 
		{
    
    
			pthread_exit(NULL);	//如果对方或者自己关闭套接字,则退出 
		}
		else
		{
    
    
			printf("#%s\n",readBuf); 
		}
	}
}
 

//该函数用于组合待发送的信息
void msg_merge(char *sendBuf,char *username,char *time,char *str)
{
    
    
	char title[20] = {
    
    "骚气聊天室 "};
	char say[10] = {
    
    "说:"};
	strcpy(sendBuf,title);
	strcat(sendBuf,time);
	strcat(sendBuf,username);	
	strcat(sendBuf,say);
	strcat(sendBuf,str);
}

void main()
{
    
    

	struct sockaddr_in c_addr;
	int addr_len = sizeof(struct sockaddr_in);
	char str[BUFSIZE] = {
    
    0};		//存放输入的信息
    char sendBuf[BUFSIZE] = {
    
    0};
	int err;
	char username[20] = {
    
    0};
	struct tm *timeptr;       //关于日期的一个结构体指针
	time_t timeval;          //关于时间的一个结构体变量	
	char tm[50];



	//创建socket
	c_fd = socket(AF_INET,SOCK_STREAM,0);	
	if (c_fd < 0)
	{
    
    
		perror("socket");
		exit(1);
	}

	c_addr.sin_family = AF_INET;
	c_addr.sin_port  = htons(8899);
	inet_aton("127.0.0.1",&c_addr.sin_addr);


	//输入用户姓名
	printf("欢迎来到骚气聊天室,输入你的英文名:\n");
	scanf("%s",username);
    getchar();		//吸收掉scanf的换行符

	//connect()
	if((connect(c_fd,(struct sockaddr*)&c_addr,sizeof(c_addr))) == -1)
	{
    
    
		perror("connect");
		exit(-1);
	} 
	  	

	//创建一个线程用于接收信息
	pthread_t tid;
	err = pthread_create(&tid,NULL,rcvmsg_func,NULL);
	if (err)
	{
    
    
		fprintf(stderr,"Create pthread fail:%s\n",strerror(err));
		exit(1);
	}
	
	//send you name to server
	if((err = write(c_fd,username,sizeof(username))) < 0 )
    {
    
    
        perror("write");
        exit(1);
    }

	printf("连接成功,退出聊天室请输入“quit”\n");
	printf("聊天可直接输入信息:\n");


	//while循环用于发送信息
	while(1)
	{
    
    
		//清空buf中的信息	                                           
    	memset(str,0,BUFSIZE);                  
    	memset(sendBuf,0,BUFSIZE); 	     	
	    
		//获取当前时间
		(void)time(&timeval);
        strcpy(tm,ctime(&timeval));			
	
		fgets(str,BUFSIZE,stdin);	//接收用户输入的信息
		
		//判断是否为退出命令
		if(strcmp(str,"quit\n") == 0)
		{
    
    
			printf("退出聊天室");
			break;	
		}
		msg_merge(sendBuf,username,tm,str);     //将姓名、时间、发送的信息组合到sendBuf中        	         	
   		
		//发送信息到socket中	                                
    	if((err = write(c_fd,sendBuf,BUFSIZE)) < 0 )    
    	{
    
    
			perror("write");
			exit(1);
		}
		else if (err == 0)
		{
    
    
			break;
		}		
	}
		
	//close()
	close(c_fd);
	exit(0);
}

Guess you like

Origin blog.csdn.net/weixin_44517500/article/details/128343584