【Linux C】简易群聊 聊天室1.0

聊天室简介

​ 本聊天室基于LinuxC进行编写,使用到的有tcp协议、多线程、互斥量、条件变量等知识,实现一个最大可接入20个用户的群聊聊天室;服务端运行后,用户运行用户端接入,输入用户名即可接入;用新用户接入或者下线时,均会群发消息提醒其他在线用户;运行效果如下所示:

在这里插入图片描述

在这里插入图片描述

由于时间仓促,本聊天室为1.0版本,仍有许多地方需要优化,如并发机制、接入客户端数量、客户端下线后编号的复用(可考虑用链表代替数组)、代码简洁性等问题,后续会再优化更新;

聊天室源码

服务端

#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);
}

客户端

#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);
}

猜你喜欢

转载自blog.csdn.net/weixin_44517500/article/details/128343584