简易聊天室

Linux系统项目之简易聊天室

聊天室简介

通过C语言写了一段代码,实现在一个局域网下实现了群聊和私聊,可以通过输入switch实现群聊和私聊的切换,通过输入seeyou来退出聊天室。本文存在的问题,当昵称相同时输入需要私聊的昵称时只能和第一个进入聊天室的人私聊。

客户端

客户端通过TCP通讯协议和服务器端进行通讯,首先产生套接字然后与服务器建立连接。进入时输入昵称并且把昵称发送给服务器。手动输入switch来切换群聊私聊,输入seeyou实现客户端的退出。通过一个线程来接收来自群聊或者私聊的消息。代码如下:

/*************************************************************************
  > 文件路径:client.c
  > 作者: Moliam
  > 邮箱: 2515826079@qq.com 
  > 文件创建时间: 2019年03月17日 星期日 17时26分02秒
 ************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>

int sockfd;//客户端sockfd
char *IP = "192.168.1.190";//服务器IP,这个IP可以在服务器终端输入ifconfig查询
short PORT = 9709;//端口号,
typedef struct sockaddr AD;//方便阅读和书写的类型定义
char name[30];//客户端名字
int flag_chat;//群聊与私聊标志
int text_fd;//用来承接函数返回值,判断是否出错

void err(const char *str,int line);//出错函数,并退出客户端
void Init(void);//根据TCP协议初始化客户端
void start(void);//
void *recv_pthread(void *p);//接收线程服务函数

int main(void)
{
	Init();
	memset(name,0,sizeof(name));
	printf("请输入你的昵称:\n");
	scanf("%s",name);
	start();
	return 0;
}
void start(void)
{
	char buf2[120]={0};
	int send_fd;
	pthread_t id;
	pthread_create(&id,0,recv_pthread,0);//创建线程用来接收消息
	text_fd = send(sockfd,name,strlen(name),0);
	if (text_fd == -1)
		err("send",__LINE__);
	printf("连接成功!聊天过程中可以按""switch""切换私聊和群聊\n");//与服务器链接成功
	while(1)
	{
		char buf[100] = {0};//群聊状态
all:	
		scanf("%s",buf);//输入需要发送的消息
		text_fd = send(sockfd,buf,strlen(buf),0);
		if (text_fd == -1)
			err("send",__LINE__);
		if(strcmp(buf,"seeyou") == 0)//如果发送的是seeyou则退出服务器
		{
			text_fd = send(sockfd,"seeyou",strlen("seeyou"),0);
			if (text_fd == -1)
				err("send",__LINE__);
			break;
		}
		if((strcmp(buf,"switch") == 0) && (flag_chat == 0))//如果输入switch且为群聊状态
		{
			printf("请输入你想要私聊的昵称:\n");
			scanf("%s",name);
			text_fd = send(sockfd,name,strlen(name),0);
			if (text_fd == -1)
				err("send",__LINE__);
			printf("%s\n",buf2);
			if(strcmp(buf2,"error") == 0)
			{
				printf("昵称输入错误,已自动回到群聊\n");
				memset(buf,0,sizeof(buf));
				goto all;
			}
			else 
			{
				printf("切换成功\n");
				printf("请输入聊天内容:\n");
				memset(buf,0,sizeof(buf));
			}
			flag_chat = 1;//切换成功,私聊标志
		}
		if((strcmp(buf,"switch") == 0) && (flag_chat == 1))//如果输入switch且为私聊状态
		{
			printf("你已回到群聊状态\n");
			flag_chat = 0;//群聊标志
		}	
	}
	close(sockfd);//关闭套接字
}
void *recv_pthread(void *p)//接收线程
{
	while(1)
	{
		char buf[100] = {0};
		if(recv(sockfd,buf,sizeof(buf),0) <=0 )
			return (void *)0;
		printf("%s\n",buf);
	}
}
void Init(void)
{
	sockfd = socket(PF_INET,SOCK_STREAM,0);
	if(sockfd == -1)
		err("socket",__LINE__);
	struct sockaddr_in addr;
	addr.sin_family = PF_INET;
	addr.sin_port = htons(PORT);
	addr.sin_addr.s_addr = inet_addr(IP);
	if(connect(sockfd,(AD*)&addr,sizeof(addr)) == -1)
		err("connect",__LINE__);
	printf("客户端初始化成功!\n");
}
void err(const char *str,int line)
{
	fprintf(stderr,"出错行号为%d\n",line);//格式化输出
	perror(str);
	exit(-1);
}

服务器端

服务器端通过接收来自客户端的信号,接收客户端的昵称并存放在指针数组中,当有客户端连接时,为该客户端开辟一个线程,进入线程服务函数,当客户端发出的消息为switch时,该线程转化为群聊或者私聊,当客户端打出seeyou退出,然后下一个客户端连接时,取代该客户端的i值与指针。

/*************************************************************************
  > 文件路径: service.c
  > 作者: Moliam
  > 邮箱: 2515826079@qq.com 
  > 文件创建时间: 2019年03月18日 星期日 15时08分00秒
 ************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>

#define MAX_CLI 256//设置最大连接数为256
#define SET_PORT 9709//设置端口号值
int sockfd;//服务器socket
int fds[MAX_CLI] = {0};//客户端socketfd
int size = MAX_CLI;//聊天室容量
char *IP = "192.168.1.190";//服务器IP地址,ifconfig查询
short PORT = SET_PORT;//端口号
int count = 0;//计算当前在线人数
//int flag_chat;//私聊 or 群聊
char *name[MAX_CLI];//指针数组存放名字;
char **n = name;//双重指针取昵称
int text_fd ;//承接函数返回值

typedef struct sockaddr AD;//类型定义减少繁琐重复内容

void err(const char *buf,int line);//出错打印函数
void Init(void);//服务器初始化,然后进入服务模式
void Service(void);//服务器模式
void *service_thread(void* p);//线程服务函数
void SendMsgToAll(char * buf);//给所有人发消息
void SendMsgToSin(int name,char *buf);//私聊消息

int main(void)
{
	Init();
	Service();
	return 0;
}
//TCP协议前面的步骤初始化
void Init(void)
{
	sockfd = socket(PF_INET,SOCK_STREAM,0);//IPV4
	if(sockfd == -1)
		err("socket",__LINE__);
	struct sockaddr_in addr;
	addr.sin_family = PF_INET;
	addr.sin_port = htons(PORT);//端口号
	addr.sin_addr.s_addr = inet_addr(IP);//服务器为本机
	if(bind(sockfd,(AD *)&addr,sizeof(addr)) == -1)
		err("bind",__LINE__);
	if(listen(sockfd,MAX_CLI) == -1)
		err("listen",__LINE__);
	printf("服务器初始化成功\n");
}
void Service(void)
{
	char buf_join[130];
	while(1)
	{
		printf("%d inline\n",count);
		struct sockaddr_in fromaddr;
		int len = sizeof(fromaddr);
		int fd = accept(sockfd,(AD*)&fromaddr,&len);
		if(fd == -1)
		{	//err("accept",__LINE__);
			printf("CONNECT ERROR!\n");
			continue;
		}
		char *buf1;
		buf1 = (char *)malloc(30);
		char buf[30];
		memset(buf,0,sizeof(buf));
		text_fd = recv(fd,buf,sizeof(buf),0);//接收到的name*
		if(text_fd == -1)
			err("recv",__LINE__);
		strcpy(buf1,buf);
		int i = 0;
		for(i = 0;i<size;i++)//
		{
			if(fds[i] == 0)//查询没有使用的数,fds数组为存放fd,没使用的fd值为0
			{
				fds[i] = fd;//新建立连接的客户端fd
				name[i] = buf1;//name定义为 char *name[size];char **n=name;
				sprintf(buf_join,"%s加入聊天室!",*(n+i));
				SendMsgToAll(buf_join);//群发消息
				pthread_t id;
				pthread_create(&id,0,service_thread,&fd);//为该客户端开辟一个线程
				count++;
				break;
			}
		}
		if(size ==  i)//此时已经没有空余
		{
			char *str = "FULL WITH CLIENTS!";//给客户端发消息客户达到最大值
			text_fd = send(fd,str,sizeof(str),0);
			if(text_fd == -1)
				err("send",__LINE__);
			close(fd);
		}
	}
}
void *service_thread(void* p)
{
	int fd = *(int *)p;
	char name_buf[30]= {0};
	char buf[100]={0};//存放接收到的数据
	int i,j;//i记录自己fd所在位置,j记录私聊对象fd所在位置
all:	
	for(i = 0;i < size;i++)//群聊状态
	{
		if(fds[i] == fd)//找到当前客户所在的位置
			break;
	}
	while(1)
	{
		int recv_fd;
		text_fd = recv(fd,buf,sizeof(buf),0);
		if (text_fd <= 0)//消息出错,减去其客户
		{
			fds[i]==0;
			count--;
			printf("%d inline\n",count);
			pthread_exit((void *)i);
		}
		if (strcmp(buf,"seeyou") == 0)//接收到seeyou消息时结束该线程
		{
			char buf_exit[128] = {0};
			sprintf(buf_exit,"%s退出了聊天室!",*(n+i));
			SendMsgToAll(buf_exit);
			fds[i]==0;//将该客户fd退掉
			count--;
			printf("%d inline\n",count);
			pthread_exit((void *)i);//退出该线程
		}
		if (strcmp(buf,"switch") == 0)//接收到switch时进入到私聊
		{
			goto sin;
		}
		char buf_all[128] = {0};
		sprintf(buf_all,"%s:%s",*(n+i),buf);
		SendMsgToAll(buf_all);//没出错,发送消息
	}
sin:
	text_fd = recv(fd,name_buf,sizeof(name_buf),0);
	if (text_fd == -1)
		err("recv",__LINE__);
	for(j = 0;j < size;j++)//找到该私聊昵称的fd,记录下位置
	{
		if((fds[j] != 0)&&(strcmp(*(n+j),name_buf) == 0))//找到该昵称所对应的fd
		{
			break;
		}
	}
	if(size == j)//未找到,继续回到群聊状态
	{
		text_fd = send(fd,"error",sizeof("error"),0);
		if (text_fd == -1)
			perror("send");//不能调用err函数,err函数会退出进程
		printf("昵称输入错误!\n");
		goto all;
	}
	text_fd = send(fd"correct",sizeof("correct"),0);
	if (text_fd == -1)
		perror("send");
	while(1)
	{
		int recv_sin_fd;
		char buf_sin_msg[100] = {0};//接收到的私聊信息
		char buf_sin[131]={0};//将名字与信息放在一起
		recv_sin_fd = recv(fd,buf_sin_msg,sizeof(buf_sin_msg),0);//接收私聊信息
		printf("%s\n",buf_sin_msg);
		if (recv_sin_fd == -1)
			err("recv",__LINE__);
		if (strcmp(buf_sin_msg,"switch") == 0)
		{
			goto all;//接收到switch继续回到群聊
		}
		sprintf(buf_sin,"来自%s私聊:%s",*(n+i),buf_sin_msg);//将来自i的消息存放,i为私聊的客户fd,j为被私聊的客户fd
		SendMsgToSin(j,buf_sin);//发送给j
	}
}
void SendMsgToSin(int name,char *buf)
{
	text_fd = send(fds[name],buf,strlen(buf),0);
	if (text_fd == -1)
		err("send",__LINE__);
}
void SendMsgToAll(char * buf)
{
	int i;
	for(i = 0;i < size;i++)
	{
		if (fds[i] != 0)
		{
			text_fd = send(fds[i],buf,strlen(buf),0);
			if (text_fd == -1)
				err("send",__LINE__);
		}
	}
}
void err(const char *buf,int line)
{
	fprintf(stderr,"出错行号为 : %d",line);
	perror(buf);
	exit(-1);
}

makefile

由于我是用的ubuntu系统,所以并没有线程的库,多以每次编译时总是有些麻烦,所以我写了一个makefile。

all:
	@gcc 3vservice.c -lpthread -o s
	@gcc 3vclient.c -lpthread -o c
clean:
	@rm *.out s c -rf
#带@为只执行不回显,-lpthread链接线程库

总结

该简易聊天室在首先说的不足之处在我写的下篇文章关于银行管理系统有解决,通过服务器的链表来存储账号密码,这样不会出现账号重复问题,该聊天室还可以客户端发送昵称通过指针数组中有没有该昵称来解决。

  • 8
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值