Linux 嵌入式 C 实现简单的网络聊天室服务端

Linux 嵌入式 C 实现简单的网络聊天室

版权声明: https://blog.csdn.net/qq_39603089/article/details/85080802
使用C/S 模式,参考下图:

在这里插入图片描述

原代码可从直接下载,与所示代码稍有不同,有所改进与完善。
https://blog.csdn.net/qq_39603089/article/details/85327599

服务端无用户界面;
客户端情看: https://blog.csdn.net/qq_39603089/article/details/85080521

服务端代码:

/*-------------------------服务器端chat_server.c-------------------------------
	time:2018/12/18
	author:zyb-dy
*/
#include <signal.h>
#include <stdio.h>
#include <syslog.h>
#include <stdlib.h>
#include <sys/types.h> //数据类型定义
#include <sys/stat.h> //文件属性
#include <netinet/in.h> //定义数据结构sockaddr_in
#include <sys/socket.h> //提供socket函数和数据结构
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ipc.h>
#include <arpa/inet.h>
#include <errno.h>       
#include<sys/sem.h>
#include <time.h>
#include <pthread.h>  //线程
#define THREAD_NUMBER 30 //最大链接数
#define MYPORT 8787
#define BUFFER_SIZE 1024
#define WELCOME "Welcome"
struct Users{              //表示用户与线程之间的关联
	char name[50];
	pthread_t thread;
	char buf[BUFFER_SIZE];
	int client_fd;
	char address[20];
	int login;         //是否在线
}users[THREAD_NUMBER];
int sem_id; 
 
//返回系统时间
void get_now_time(char *nt){
	time_t tmpcal_ptr;
	struct tm *tmp_ptr = NULL;
	time(&tmpcal_ptr);
	tmp_ptr = localtime(&tmpcal_ptr);
	sprintf(nt,"%d:%d:%d", tmp_ptr->tm_hour, tmp_ptr->tm_min, tmp_ptr->tm_sec);	
}
								  
int init_sem(int sem_id, int init_value){
	union semun{
		int val;
		struct semid_ds *buf;
		unsigned short *array;
	};
	union semun sem_union;
	sem_union.val = init_value;
	if(semctl(sem_id, 0, SETVAL, sem_union) == -1){
		syslog(LOG_ERR, "Initialize semaphore");
		perror("Initialize semaphore");
		return -1;
	}
	return 0;
}
int del_sem(int sem_id){
	union semun{
		int val;
		struct semid_ds *buf;
		unsigned short *array;
	};
	union semun sem_union;
	if(semctl(sem_id, 1, IPC_RMID, sem_union)==-1){
		syslog(LOG_ERR, "Delete semaphore");
		perror("Delete semaphore");
		return -1;
	}
}
//P 操作函数
int sem_p(int sem_id){
	struct sembuf sem_b;
	sem_b.sem_num = 0;
	sem_b.sem_op = -1;
	sem_b.sem_flg = SEM_UNDO;
	if(semop(sem_id, &sem_b, 1)==-1){
		syslog(LOG_ERR, "P operation");
		perror("P operation");
		return -1;
	}
	return 0;
}
//V 操作函数
int sem_v(int sem_id){
	struct sembuf sem_b;
	sem_b.sem_num = 0;
	sem_b.sem_op = 1;
	sem_b.sem_flg = SEM_UNDO;
	if(semop(sem_id, &sem_b, 1) == -1){
		syslog(LOG_ERR, "V operation");
		perror("V operation");
		return -1;
	}
	return 0;
}



//发送给个人
void send_all(char loadsend[BUFFER_SIZE]){
	int j;
	for(j=0;j<THREAD_NUMBER;j++){
		if(users[j].login==1){
			send(users[j].client_fd, loadsend, strlen(loadsend), 0);		
		}
	}
}
//发送给在线的所有人
void send_only(char name[50],char loadsend[BUFFER_SIZE]){
	int j;
	for(j=0;j<THREAD_NUMBER;j++){
		if((users[j].login==1)&&(strcmp(users[j].name,name)==0)){
			send(users[j].client_fd, loadsend, strlen(loadsend), 0);		
		}
	}
}
//处理接受到的字符并调用相应的函数转发;
void strdeal(char s[],char se_name[]){  
	char sign[8];
	char name[56];
	char buf[BUFFER_SIZE];
	char send_buf[BUFFER_SIZE];
	memset(sign, 0, strlen(sign));
	memset(name, 0, strlen(name));
	memset(buf, 0, strlen(buf));
	memset(send_buf, 0, strlen(send_buf));
	char nt[10];
	int i=0;
	int n=0;
	int j=0;
	for(i;i<strlen(s);i++){
		if(n>1){
			buf[j]=s[i];
			j++;	
		}else if(n==1){
			if(s[i]==':'){
				n++;
				name[j]='\0';
				j=0;
				continue;
			}
			name[j]=s[i];
			j++;
		}else{
			if(s[i]==':'){
				n++;
				sign[j]='\0';
				j=0;
				continue;
			}
			sign[j]=s[i];
			j++;
		}	
	}
	if(strcmp(sign,"All")==0){
		get_now_time(nt);
		sprintf(send_buf,"%s用户< %s >群发消息->\t\t%s:\n\t%s","User:",se_name,nt,buf);
		send_all(send_buf);
	}else{
		get_now_time(nt);
		sprintf(send_buf,"%s用户< %s >------>\t\t%s:\n\t%s","User:",se_name,nt,buf);
		send_only(name,send_buf);
	}
		
	
}

//线程函数
void *thrd_func(void *arg)     
{
	long i = (long)arg;	
	int recvbytes;
	char nt[10];
	while(1){
		memset(users[i].buf , 0, sizeof(users[i].buf));
		if ((recvbytes = recv(users[i].client_fd, users[i].buf, BUFFER_SIZE, 0)) <= 0)//结束当前线程
		{
			char end[100];
			memset(end, 0, 100);
			get_now_time(nt);
			sprintf(end,"%s%s%s\n用户:%s%s\n","Inform:",nt,"-通知:",users[i].name,"退出聊天室");  // 添加时间:
			send_all(end);			
			users[i].login = 0;
			sem_v(sem_id);
		        close(users[i].client_fd);
			int n=0;
			int j=0;
			for(j;j<THREAD_NUMBER;j++){
				if(users[j].login==0)
					n++;		
			}		
			printf("%s用户退出,还可以上线%d个\n",users[i].name, n);
			pthread_exit(0);   //结束线程
		}
		strdeal(users[i].buf,users[i].name);
		
		
	}
}


//端口绑定函数。创建套接字,并绑定到指定端口
int bindPort(unsigned short int port)
{
	int sockfd, sendbytes;
	struct sockaddr_in my_addr;
	if ((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1)//创建基于流套接字
	{
		syslog(LOG_ERR, "socket");
		perror("socket");
	}

	/*设置sockaddr_in 结构体中相关参数*/   
	bzero(&my_addr, sizeof(my_addr));
	my_addr.sin_family = AF_INET; //IPV4协议族
	my_addr.sin_port = htons(port); //转换端口为网络字节序
	my_addr.sin_addr.s_addr = INADDR_ANY;
	memset(&(my_addr.sin_zero),0,8);

	/* 允许重复使用本地地址与套接字进行绑定 */
	int i = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
	if (bind(sockfd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == -1)
	{
		syslog(LOG_ERR, "fail to bind");
		perror("fail to bind");
	}
 	printf("success!\n");
	return sockfd;
}
/*自定义信号处理函数*/
void my_func(int sign_no)
{	
	char loadsend[100];
	sprintf(loadsend,"%s%s","Inform:","over");   //发送通知 over 客户端收到后自动退出
	send_all(loadsend);
	printf("即将退出服务器\n");
	fflush(stdout);
	sleep(1);
	exit(0);
}
int main(int argc, char *argv[])
{
	
	int sockfd,recvbytes;
	char loadsend[100];
	long i=0;  //循环来做连接
	int sendbytes, res;
	struct sockaddr_in client_sockaddr;
	int sin_size;
	int repetition,j,n;
	sockfd = bindPort(MYPORT);
	for(i=0;i<THREAD_NUMBER;i++)
		users[i].login=0;
	i=0;
	if (listen(sockfd, THREAD_NUMBER) == -1)
	{	
		syslog(LOG_ERR, "listen");
		perror("listen");
	}
	printf("Listening....\n");
	sem_id = semget(ftok("/", 1), 1, 0666|IPC_CREAT);
	init_sem(sem_id, THREAD_NUMBER);
	char nt[10];
	openlog("daemon_syslog", LOG_PID, LOG_DAEMON);   //打开系统日志服务
	//捕捉信号量,并跳转到信号处理函数处 
	signal(SIGINT, my_func);
	signal(SIGQUIT, my_func);
	signal(SIGTSTP,my_func);
	while(1){
		sem_p(sem_id);
		while(i<THREAD_NUMBER){                       //遍历列表
			if(users[i].login==0)                  //找到可用资源  0 表示可用  1 不可用
				break;
			i++;
			if(i==THREAD_NUMBER){                 //循环查找
				i=0;	
				}
			}
		n=0;
		j=0;
		for(j;j<THREAD_NUMBER;j++){
			if(users[j].login==1)
				n++;		
		}		
		printf("已经上线%d个用户,还可以上线%d个\n",n,THREAD_NUMBER-n);
		if(users[i].login==0){
			printf("等待第下个连接\n");
			/*调用accept()函数,等待客户端的连接*/			
			if ((users[i].client_fd = accept(sockfd,(struct sockaddr*)&client_sockaddr, &sin_size)) == -1){
				syslog(LOG_ERR, "accept");				
				perror("accept");
			
			}
			//获取客户端的IP
			inet_ntop(AF_INET, &client_sockaddr.sin_addr, users[i].address, sizeof(users[i].address));
			if ((recvbytes=recv(users[i].client_fd, users[i].name, BUFFER_SIZE, 0)) <= 0){
				sem_v(sem_id);
				continue;			
			};
			printf("本次连接的是用户:%s\n",users[i].name);
			j=0;	
			repetition=0;//判断是否重名		
			for(j=0;j<THREAD_NUMBER;j++){
				if(users[j].login==1)	//从连接的用户寻找
				if(strcmp(users[i].name,users[j].name)==0){
					repetition=1;	
				}
			}
			if(repetition==1){
				send(users[i].client_fd, "g", strlen("g"), 0);
				sem_v(sem_id);
				continue;		
			}else{
				users[i].login = 1;
				send(users[i].client_fd, WELCOME, strlen(WELCOME), 0);
			}			
			//广播上线消息。
			memset(loadsend, 0, 100);
			get_now_time(nt);
			sprintf(loadsend,"%s%s%s\n用户:%s%s\n","Inform:",nt,"-通知:",users[i].name,"-上线了!大家欢迎!");
			send_all(loadsend);
			res = pthread_create(&users[i].thread, NULL, thrd_func, (void*)i);  //开启线程
			if (res != 0)
			{	
				syslog(LOG_ERR, "Create thread failed");
				perror("Create thread failed");
				users[i].login = 0;
				sem_v(sem_id);
			}
		}
	}
	close(sockfd);
	return 0;
}

编译:

gcc chat_server.c -lpthread

效果如下:

在这里插入图片描述

  • 0
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值