网络编程 Linux聊天小程序

基于多线程TCP服务器的聊天小程序

记录网络程序设计课程设计,小组作业,本人完成的是1、2、4功能,有很多不足,谢谢!

实现功能

1、群聊
2、私聊
3、发送文件
4、成员的管理与维护

实现思路

本程序基于客户端/服务器模型进行实现,客户端加入聊天小程序,服务器进行成员的动态维护;客户端发送消息给服务器,服务器负责向群内成员进行转发。
数据结构设计:服务器使用结构体数组储存客户的用户名,状态和套接字描述符
多线程设计:服务器使用多线程接收新的客户端连接,并执行相应的功能,同时服务器启动一个线程进行成员的管理与维护。客户端每个功能对应一个接收信息的线程。
传输设计:客户端连接服务器,服务器储存用户名、状态和对应套接字描述符。服务器通过TCP套接字描述符可以向任何人转发消息,实现群聊。客户端通过筛选特定信息实现私聊。文件传输同理。
成员管理设计:服务器储存了用户名、状态和对应套接字描述符,通过遍历数组查询状态得到在线人数,记录在数组中的位置,可以使用shutdown函数关闭对应套接字实现下线。

具体实现

服务器

#include<stdio.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <pthread.h>

#define BUF_SIZE  20


struct{//储存客户端信息,最多连接20
	char name[20];
	int state;
	int client;
}identity[20];

//线程函数,实现服务器消息转发
void *ThreadFunc(void *arg)
{	
	int number;//记录本线程接待的客户的位置
	int ret=-1;
	int iClient=*(int *)arg;
	char name[20];
	ret=recv(iClient,name,20,0);//接收客户端姓名
	if(ret<=0)
		{
			printf("recv error!----\r\n");
			close(iClient);
			pthread_exit("thread exit");
		}else{
			for(int i=0;i<20;i++)//遍历,状态为0则储存消息
			{
                            if(identity[i].state==0)
			    {
			       strcpy(identity[i].name,name);
			       identity[i].state=1;
			       number=i;
			       identity[i].client=iClient;
			       printf("Welcome %s to join the server\r\n",identity[i].name);
			       break;
		            }
		  	    if(i==19)//人数满了
			    {
			       printf("The number of people is full\r\n");
			       close(iClient);
			       pthread_exit("thread exit");
			    }
			}	
		}
	//接收客户端选择的功能,使用whlie switch
	char a[1];
	char b;
	char buf[BUF_SIZE]={0};//接收客户端消息使用
	char bufname[50]={0};//转发消息使用
	int flag=-1;
	while(1){
		
		ret=recv(iClient,a,1,0);
		if(ret<=0)//出错则需要初始化数组信息,并结束
					{
						printf("User %s goes offline!----\r\n",identity[number].name);
						memset(identity[number].name,0,20);
						identity[number].state=0;
						identity[number].client=-1;
						close(iClient);
						pthread_exit("thread exit");
					}
		b=a[0];
		switch(b){
			case '1'://群聊全部转发
					printf("%s Joins Group Chat\r\n",identity[number].name);
					while(1)
					{
						//recv
						memset(buf,0,20);
						memset(bufname,0,50);
						ret=recv(iClient,buf,BUF_SIZE,0);
						if(ret<=0)
						{
							printf("User %s goes offline!----\r\n",identity[number].name);
							memset(identity[number].name,0,20);
							identity[number].state=0;
							identity[number].client=-1;
							close(iClient);
							pthread_exit("thread exit");
						}
						if(buf[0]=='$'){//客户端退出功能,服务器对应
							printf("%s quit group chat\r\n",identity[number].name);
							break;}
						printf("Message from %s(%d):%s\r\n",identity[number].name,number,buf);
						//遍历状态为1,构造消息格式全部发送消息
						for(int i=0;i<20;i++)
						{
						if(identity[i].state==1){
						sprintf(bufname,"%s%c%s",identity[number].name,':',buf);
						send(identity[i].client,bufname,50,0);}
						}
					}
				break;
			case '2'://私聊单独转发
				printf("%s Joins Private Chat\r\n",identity[number].name);
				memset(name,0,20);
				ret=recv(iClient,name,20,0);//接收私聊的对象
				if(ret<=0)
						{
							printf("User %s goes offline!----\r\n",identity[number].name);
							memset(identity[number].name,0,20);
							identity[number].state=0;
							identity[number].client=-1;
							close(iClient);
							pthread_exit("thread exit");
						}
				printf("%s\r\n",name);
				for(int i=0;i<20;i++)//看私聊的对象是否在线
				{
					if(strncmp(name,identity[i].name,15)==0&&identity[i].state==1)
					{send(iClient,"1",1,0);
					flag=i;
					break;}
					if(i==19)
					send(iClient,"0",1,0);
				}
				if(flag!=-1){//在线则进行消息转发
						while(1)
						{
						//recv
						memset(buf,0,20);
						memset(bufname,0,50);
						ret=recv(iClient,buf,BUF_SIZE,0);
						if(ret<=0)
						{
							printf("User %s goes offline!----\r\n",identity[number].name);
							memset(identity[number].name,0,20);
							identity[number].state=0;
							identity[number].client=-1;
							close(iClient);
							pthread_exit("thread exit");
						}
						if(buf[0]=='$'){
							printf("%s quit private chat\r\n",identity[number].name);
							break;}
						printf("Message from %s(%d):%s\r\n",identity[number].name,number,buf);
						//send
						sprintf(bufname,"%s%s%s",identity[number].name,"(Private):",buf);
						send(identity[flag].client,bufname,50,0);
	
						}
					   }

				break;
			case '3'://转发文件
				printf("%s Joins send files\r\n",identity[number].name);
				int j=0;
				char buf1[BUF_SIZE]={0};
				while(1){
					ret=recv(iClient,buf,BUF_SIZE,0);//USER NAME
					if(buf[0]=='$'){
							printf("%s quit send files\r\n",identity[number].name);
							break;}
					if(ret<=0)
						{
							printf("User %s goes offline!----\r\n",identity[number].name);
							memset(identity[number].name,0,20);
							identity[number].state=0;
							identity[number].client=-1;
							close(iClient);
							pthread_exit("thread exit");
						}
					if(-1==ret){
						printf("user can not recv\r\n");
					}
							for(int i=0;i<20;i++){
							if(identity[i].state!=1||strncmp(identity[i].name,buf,15!=0)){
							buf1[0]='1';
							}
							else{
							buf1[0]='0';
							j=i;
							break;}
							}
						send(iClient,buf1,BUF_SIZE,0);
						if(buf1[0]=='0'){
							
							memset(buf,0,BUF_SIZE);
							send(identity[j].client,"0",BUF_SIZE,0);
							recv(iClient,buf,BUF_SIZE,0);//文件名 
							if(buf[0]=='$'){
							printf("%s quit send files\r\n",identity[number].name);
							break;}
							send(identity[j].client,buf,BUF_SIZE,0);
							recv(iClient,buf,BUF_SIZE,0);//文件大小
							int size = atoi(buf);
							send(identity[j].client,buf,BUF_SIZE,0); 
							while(size>0){
								ret=recv(iClient,buf,BUF_SIZE,0);
								send(identity[j].client,buf,ret,0);
								size-=BUF_SIZE;
							}
							printf("send file to %s success\r\n",identity[j].name);
						}
						else{
							
							printf("user can not find!\r\n");	
							send(identity[j].client,"1",BUF_SIZE,0);						
							exit(1);
						}
						}
				break;
			case '4'://未实现的功能,转发文件夹,思路:可以将文件夹中文件逐个转发
				printf("send \r\n");
				break;
			default:printf("input error");
				break;
		}
		
		
	}
}

//线程管理成员
void *ThreadFunc1(void *arg){
	int a;
	int b;
	while(1){
		scanf("%d",&a);
		if(a==1){//遍历在线人数
			printf("Number of people online:\r\n");
			for(int i=0;i<20;i++)
			{
			if(identity[i].state==1)
			printf("%s(%d)--",identity[i].name,i);
			}
		}
		if(a==2){//踢人
			printf("Please enter the user number you want to offline:\r\n");
			scanf("%d",&b);
			memset(identity[b].name,0,20);
			identity[b].state=0;
			shutdown(identity[b].client,SHUT_RDWR);//先踢掉,再修改套接字
			identity[b].client=-1;
		}	
	}
	
}

int main()
{
	for(int i=0;i<20;i++){//数组初始化
		identity[i].state=0;
		identity[i].client=-1;}
	//1 socket
	int iServer=socket(AF_INET,SOCK_STREAM,0);
	if(-1==iServer)
	{
		printf("create socket error!-------\r\n");
		return -1;
	}
	printf("1:create socket ok!----------iServer=%d\r\n",iServer);
	int opt=SO_REUSEADDR;
	setsockopt(iServer,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
	//2 bind
	struct sockaddr_in stServer;
	memset(&stServer,0,sizeof(struct sockaddr_in));
	stServer.sin_family=AF_INET;
	stServer.sin_port=htons(8888);
	stServer.sin_addr.s_addr=inet_addr("127.0.0.1");
	int ret=bind(iServer,(struct sockaddr *)&stServer,sizeof(struct sockaddr));
	if(-1==ret)
	{
		printf("bind error!----------\r\n");
		return -1;
	}
	printf("2:bind ok!---------------\r\n");
	//3 listen
	ret=listen(iServer,5);
	if(-1==ret)
	{
		printf("listen error!---------\r\n");
		return -1;
	}
	printf("3:listen ok!--------------\r\n");
	//4 accept
	struct sockaddr_in stClient;
	socklen_t len=sizeof(struct sockaddr_in);
	char buf[BUF_SIZE];
	pthread_t tid=-1;
	if(0!=pthread_create(&tid,NULL,ThreadFunc1,NULL))//管理线程,需要持续开启
		{
			printf("create thread error!----------\r\n");
		}		
	
	while(1)
	{
		memset(buf,0,BUF_SIZE);
		memset(&stClient,0,sizeof(struct sockaddr_in));
		int iClient=accept(iServer,(struct sockaddr *)&stClient,&len);
		if(-1==iClient)
		{
			continue;
		}
		printf("4:accept ok!--------------\r\n");
		//pthread
		pthread_t tID=-1;
		if(0!=pthread_create(&tID,NULL,ThreadFunc,&iClient))
		{
			printf("create thread error!----------\r\n");
			close(iClient);
			continue;
		}		
	}
	return 0;
}


客户端

#include<stdio.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 20

struct STR{//私聊传参使用
	int client;
	char pname[20];
}str;

struct{//登录使用(本地登录)
	char admin[50];
	char passwd[50];
	int flag; 
}admin[100];
void *ThreadFunc(void *arg)//多线程接收服务器数据(群聊天)
{
	
	int iClient=*(int *)arg;
	char bufname[50]={0};
	int ret;
	while(1)
	{
	ret=recv(iClient,bufname,50,0);
	if(ret<=0)
	{
		printf("recv error!-------\r\n");
		exit(1);
	}
	printf("-------%s\r\n",bufname);
	memset(bufname,0,50);
	}
}

void *ThreadFunc2(void *arg)//多线程接收服务器数据(私聊天)
{
	
	struct STR info;
	info.client=((struct STR *)arg)->client;
	strcpy(info.pname, ((struct STR *)arg)->pname);
	char bufname[50]={0};
	char bianbie[30];
	int ret;
	while(1)
	{
	ret=recv(info.client,bufname,50,0);
	if(-1==ret)
	{
		printf("recv error!-------\r\n");
	}
	sprintf(bianbie,"%s%s",info.pname,"(Private):");
	if(strncmp(bianbie,bufname,10)==0)//如果是 姓名(Private)格式则打印
		printf("-------%s\r\n",bufname);
	memset(bufname,0,50);
	}
}

void *ThreadFunc1(void *arg)//接收文件
{
	
	int iClient=*(int *)arg;
	char buf[BUF_SIZE]={0}; 
	int ret;
	ret=recv(iClient,buf,BUF_SIZE,0);
	if(ret<=0)
	{
		printf("recv error!-------\r\n");
		exit(1);
	}
	if(buf[0]=='0'){
	while(1)
	{
	ret=recv(iClient,buf,BUF_SIZE,0);
	if(-1==ret){
		printf("file name recv error\r\n");
		}
	int dest=open(buf,O_CREAT|O_WRONLY|O_TRUNC,0666);
	if(-1==dest)
	{
		printf("open dest file error!\r\n");
		return -1;
	}
	memset(buf,0,BUF_SIZE);
	ret=recv(iClient,buf,BUF_SIZE,0);
	if(-1==ret){
		printf("file size recv error\r\n");
		}
	int size = atoi(buf);
		while(size>0){
		ret=recv(iClient,buf,BUF_SIZE,0);
		write(dest,buf,ret);
		size-=BUF_SIZE;
		}
	printf("transmission success\r\n");
	}
}
}



int main()
{ 
	admin[0].flag=1;
	strcpy(admin[0].admin,"admin");
	strcpy(admin[0].passwd,"passwd");
	char user[50];
	char password[50];
	printf("Welcome to authentication system!\r\n");
	printf("please input your admin:\r\n");
	scanf("%s",user);
	printf("please input your password:\r\n");
	scanf("%s",password);
	if(strncmp(user,admin[0].admin,10)!=0||strncmp(password,admin[0].passwd,10)!=0){
		printf("your admin or password error\r\n");
		exit(1); 
	}
	else{
		printf("welcome!\r\n");
	}
	char a[1];//用来给服务器发送选择的结果
	char b;//记录选择的结果
	pthread_t tID1,tID2,tID3,tID4;
	char name[20];
	struct stat sta;
	printf("Please enter your user name:\r\n");
	scanf("%s",name);
	//1 socket
	int iClient=socket(AF_INET,SOCK_STREAM,0);
	if(-1==iClient)
	{
		printf("create socket error!-----\r\n");
		return -1;
	}
	printf("1:create socket ok!---------------\r\n");
	//2 connect
	struct sockaddr_in stServer;
	memset(&stServer,0,sizeof(struct sockaddr_in));
	stServer.sin_family=AF_INET;
	stServer.sin_port=htons(8888);
	stServer.sin_addr.s_addr=inet_addr("127.0.0.1");
	int ret=connect(iClient,(struct sockaddr *)&stServer,sizeof(struct sockaddr_in));
	if(-1==ret)
	{
		printf("connect error!---------\r\n");
		return -1;
	}
	printf("2:connect server ok!----------\r\n");
	//3 io-->send/recv
	ret=send(iClient,name,20,0);
	char buf[BUF_SIZE]={0};
	if(0!=pthread_create(&tID3,NULL,ThreadFunc1,&iClient))
			{
				printf("create thread error!----------\r\n");
				close(iClient);
					}
	
	
	while(1){
		printf("Please enter your choice: 1 Group chat, 2 Private chat, 3 Send files,4 Send Folder \r\n");
		setbuf(stdin, NULL);
		b=getchar();
		getchar();
		a[0]=b;
		ret=send(iClient,a,1,0);//发送选择的结果,send要字符数组
		switch(b){
			case '1':
						printf("Welcome to join the chat group\r\n");
						if(0!=pthread_create(&tID1,NULL,ThreadFunc,&iClient))//创建线程接收数据
						{
						printf("create thread error!----------\r\n");
						close(iClient);
						}	
						while(1){//循环发送数据
						setbuf(stdin, NULL);//fgets函数前清理缓冲
						fgets(buf,BUF_SIZE,stdin);
						ret=send(iClient,buf,BUF_SIZE,0);
						if(-1==ret)
						{
							printf("send error!-----\r\n");

						}
						if(buf[0]=='$'){
			
							ret = pthread_cancel(tID1);
    						if (ret != 0) {
        						printf("Failed to terminate thread\n");
       						 return 0;
    						}
							pthread_join(tID1,NULL);
							break;}
						
						memset(buf,0,BUF_SIZE);
								}
					 break;
			case '2':
						printf("Welcome to join the Private chat\r\n");
						printf("please choice your peer user name:\r\n");
						setbuf(stdin, NULL);
						scanf("%s",name);
						ret=send(iClient,name,20,0);
						memset(buf,0,20);
						ret=recv(iClient,buf,1,0);
						if(-1==ret)
						{
							printf("recv error!-------\r\n");
						}
						if(buf[0]=='1'){
							printf("%s Users online,please enter information:\r\n",name);
							str.client=iClient;
							strcpy(str.pname, name);
							if(0!=pthread_create(&tID2,NULL,ThreadFunc2,(void *)&str))//创建线程接收数据
							{
							printf("create thread error!----------\r\n");
							close(iClient);
							}	
							while(1){//循环发送数据
							setbuf(stdin, NULL);//fgets函数前清理缓冲
							fgets(buf,BUF_SIZE,stdin);
							ret=send(iClient,buf,BUF_SIZE,0);
							if(-1==ret)
							{
								printf("send error!-----\r\n");

							}
							if(buf[0]=='$'){
			
							ret = pthread_cancel(tID2);
    							if (ret != 0) {
        							printf("Failed to terminate thread\n");
       								 return 0;
    									}
							pthread_join(tID2,NULL);
							break;}
							memset(buf,0,BUF_SIZE);
								}
								}
						if(buf[0]=='0')
							printf("User is not online!");
				     break;
			case '3':
				printf("please input you want to send name of user\r\n");
				scanf("%s",buf);
				ret=send(iClient,buf,BUF_SIZE,0);
				if(-1==ret){
					printf("user name send error\r\n");
				}
				if(buf[0]=='$'){
			
						ret = pthread_cancel(tID3);
    						if (ret != 0) {
        						printf("Failed to terminate thread\n");
       							 return 0;
    								}
						pthread_join(tID3,NULL);
						break;}
				memset(buf,0,BUF_SIZE);
				recv(iClient,buf,BUF_SIZE,0);
				if(buf[0]=='1'){
					printf("user can not  found!\r\n");
					exit(1);
				}	
				else{	printf("user exit!\r\n");}
						while(1){
						printf("input you want to send the name of file\r\n");
							scanf("%s",buf);
							
							ret=send(iClient,buf,BUF_SIZE,0);
							if(-1==ret){
								printf("file name send error\r\n");
							}
							if(buf[0]=='$'){
			
							ret = pthread_cancel(tID3);
    							if (ret != 0) {
        							printf("Failed to terminate thread\n");
       								 return 0;
    									}
							pthread_join(tID3,NULL);
							break;}
							int src=open(buf,O_RDONLY,0666);
							if(-1==src)
								{
									printf("open src file error!\r\n");
									return -1;
								}
							printf("open src file ok\r\n",src);
							stat(buf,&sta);
							int size = sta.st_size;
							snprintf(buf,BUF_SIZE,"%ld",sta.st_size);
							ret=send(iClient,buf,BUF_SIZE,0);					
								if(-1==ret){
								printf("file size send error\r\n");
							}						
								while(size>0){
								ret=read(src,buf,BUF_SIZE);
								send(iClient,buf,ret,0);
								size-=BUF_SIZE;
							}
							printf("transmission success!\r\n");

								}
				break;
			case '4':break;
			default:printf("Please enter the correct number:\r\n");
					break;
		}
			
	}
	

	close(iClient);
	return 0;
}

我对一些线程函数了解还不够,bug有点多,而且只是记录一次作业,应该到此为止了
文件资源我已上传,包含源码、可执行文件和说明
说明如下(需要的可以去下载):

编译1.c 2.c 生成两个可执行文件server client,或者直接执行12(可执行文件 Linux下)
gcc 1.c -o server -lpthread
gcc 2.c -o client -lpthread
./server 启动服务器
./client 启动客户端

客户端输入账号 admin 密码passwd,证明身份,然后输入用户名,选择功能13,发送文件夹功能未实现
功能1 群聊  客户端输入消息,其他进入群聊的客户端可以接收到消息
功能2 私聊  客户端输入消息,只有私聊的对象才能接收到消息(对方在群聊模式下也可见)
功能3 发送文件 bug有点多,只能在一开始,一方选择3,另一方不进行选择的情况下能正常传输。

客户端输入$退出当前模式

服务器输入1打印在线人数
输入2可以让指定客户端下线,下线输入的为数组位置,即括号里的数字(0),有点小bug

11.c  和22.c 只完成了私聊和群聊,是前期完成的,比较稳定无bug,可以在此基础上修改完成
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值