Linux系统下实现TCP网络聊天室

一、项目介绍

        该项目使用了基于TCP/IP协议的服务器/客户端模型,通过多路IO复用(该项目使用的是epoll机制)实现多用户登录。

epoll机制:

        epoll(事件轮询)是Linux内核提供的一种高效的I/O事件通知机制,用于处理大量的并发连接。它是基于文件描述符的I/O多路复用技术之一,可以监视多个文件描述符上的事件,当文件描述符就绪时,通知应用程序进行相应的处理。

epoll机制相比于传统的select和poll机制具有更高的性能和扩展性,主要体现在以下几个方面:

  1. 支持大量的并发连接:epoll使用红黑树来存储文件描述符,可以高效地处理大量的并发连接。

  2. 只通知就绪的事件:epoll只通知应用程序已经就绪的文件描述符,避免了遍历整个文件描述符集合的开销。

  3. 边缘触发(Edge-Triggered)模式:epoll可以以边缘触发的模式工作,只在状态变化时通知应用程序,而不是在状态保持时一直通知。

  4. 支持跨平台:epoll是Linux特有的机制,但是其他类似的机制也存在于其他操作系统上,如kqueue在FreeBSD上。

 服务器/客户端模型:

服务器框架:创建套接字,绑定IP端口信息,监听网络,允许连接,收/发消息,关闭套接字。

客户端框架:创建套接字,连接服务器,收/发消息,关闭套接字。

逻辑图:

数据库:

二、服务器代码实现

1、server.c

具体函数名及功能见注释

#include "server.h"
				
int main()
{

	log_t =0;
	fu_t = 0;
	fr_t = 0;
	//初始化数据库
	ret = mysql_init(&mysql);
	if(ret == NULL)
	{
		perror("mysql_init");
		return -1;
	}
	//链接数据库
	ret = mysql_real_connect(ret,"localhost","root","1","chat",0,0,0);
	if(ret == NULL)
	{
		perror("mysql_real_connect");
		return -1;
	}
	
	//创建套接字
	int rt;
	sock = socket(AF_INET,SOCK_STREAM,0);
	if(sock < 0)
	{
		perror("sock");
		return -1;
	}
	//设置套接字多路复用
	int flag =1;
	setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));

	//绑定端口
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(6666);
	seraddr.sin_addr.s_addr = inet_addr("192.168.202.137");
	rt = bind(sock,(struct sockaddr *)&seraddr,sizeof(seraddr));
	if(rt < 0)
	{
		perror("bind");
		return -1;
	}

	//监听网络
	rt = listen(sock,20);
	if(rt < 0)
	{
		perror("listen");
		return -1;
	}

	//epoll
	struct sockaddr_in cliaddr;
	len = sizeof(cliaddr);
	struct sockaddr_in addr_c[25];
	//创建一个epoll文件描述符,用来监听其他文件描述符(套接字)
	epid = epoll_create(1);

	//将服务器的套接字加入epid监听
	evt.events = EPOLLIN;
	evt.data.fd = sock;
	epoll_ctl(epid,EPOLL_CTL_ADD,sock,&evt);

	while(1)
	{
		//监听文件描述符是否异动,返回移动端个数
		count = epoll_wait(epid,myepoll,20,-1);

		for(int i = 0; i < count; i++)
		{
			//是否产生异动
			if(myepoll[i].events == EPOLLIN)
			{
				//是不是服务器本身的套接字产生异动,是则允许客户端链接,不是则处理客户请求
				if(myepoll[i].data.fd == sock)
				{
					sock_c = accept(sock,(struct sockaddr *)&cliaddr,&len);
					//将客户的套接字加入监听
					evt.data.fd = sock_c;
					evt.events = EPOLLIN;
					addr_c[sock_c]=cliaddr;
					epoll_ctl(epid,EPOLL_CTL_ADD,sock_c,&evt);
				}
				else
				{
					Inf inf;
					memset(&inf,0,sizeof(inf));
					//接收客户端消息,存到inf消息结构体中
					if(read(myepoll[i].data.fd,&inf,sizeof(inf)) == 0)
					{
						//客户下线
						close(myepoll[i].data.fd);

						//数组内的套接字设置为0,表示用户下线,不能收到群聊消息
						for(int j=0;j<log_t;j++)
						{
							if(loguse[j].sock == myepoll[i].data.fd)
							{
								for(int x=j;x<=log_t;x++)
								{
									loguse[x] = loguse[x+1];
								}
							}
						}
						log_t--;
						//将下线的客户从数组中移出,方便判断是哪个用户正在发送文件
						for(int j=0;j<fu_t;j++)
						{
							if(file_use[j].sock == myepoll[i].data.fd)
							{
								for(int x=j;x<=log_t;x++)
								{			
									file_use[x] = file_use[x+1];
								}
							}
						}
						fu_t--;
						//将下线的客户从数组中移出,方便判断要接受文件的用户是否在线
						for(int j=0;j<fr_t;j++)
						{
							if(file_recv[j].sock == myepoll[i].data.fd)
							{
								for(int x=j;x<=log_t;x++)
								{
									file_recv[x] = file_recv[x+1];
								}
							}
						}
						fr_t--;
						//将下线的客户从epid中移出,不在监听
						epoll_ctl(epid,EPOLL_CTL_DEL,myepoll[i].data.fd,NULL);
					}
					else
					{
						//判断操作
						if(inf.flg == '2')//注册
						{
							Register(ret,myepoll,addr_c[myepoll[i].data.fd],inf,i);
						}
						else if(inf.flg == '1')//登录
						{
							login(ret,myepoll,addr_c[myepoll[i].data.fd],inf,i);
						}
						else if(inf.flg == '3')//注销
						{
							logout(ret,myepoll,inf,i);
							for(int j=0;j<log_t;j++)
							{
								if(loguse[j].sock == myepoll[i].data.fd)
								{
									for(int x=j;x<=log_t;x++)
									{
										loguse[x] = loguse[x+1];
									}
								}
							}
							log_t--;
							for(int j=0;j<fu_t;j++)
							{
								if(file_use[j].sock == myepoll[i].data.fd)
								{
									for(int x=j;x<=log_t;x++)
									{
										file_use[x] = file_use[x+1];
									}
								}
							}
							fu_t--;
							for(int j=0;j<fr_t;j++)
							{
								if(file_recv[j].sock == myepoll[i].data.fd)
								{
									for(int x=j;x<=log_t;x++)
									{
										file_recv[x] = file_recv[x+1];
									}
								}
							}
							fr_t--;
						
						}
						else if(inf.flg == '4')//私聊
						{
							memset(&send_s,0,sizeof(send_s));
							int k,j;
							//遍历在线用户数组,找到自身,将自己的用户名一起发给私聊用户,在客户端显示昵称
							for ( k=0; k<log_t; k++)
							{
								if(loguse[k].sock==myepoll[i].data.fd)
								{
									sprintf(send_s.user,"%s",loguse[k].user);
									sprintf(send_s.msg,"%s",inf.msg);
									break;
								}
						        }
							//通过指定的用户名找到给客户发消息
							for(j=0;j<log_t;j++)
							{
								if(strcmp(loguse[j].user, inf.user)==0)
								{
									send_s.flg = 1;
									write(loguse[j].sock,&send_s,sizeof(send_s));
									break;
								}

							}
							//数组遍历完了,没有找到对应的用户,则证明用户不在线。
							if(j==log_t)
							{
								send_s.flg = 1;
								sprintf(send_s.msg,"用户%s不在线,输入quit退出",inf.user);
								write(myepoll[i].data.fd,&send_s,sizeof(send_s));
							}
						}

						else if(inf.flg == '5')//群聊
						{
							memset(&send_s,0,sizeof(send_s));
							//通过套接字找到自己,群聊不给自己发
							for (int k=0; k<log_t; k++)
							{
								if(loguse[k].sock==myepoll[i].data.fd)
								{
									sprintf(send_s.user,"%s",loguse[k].user);
									sprintf(send_s.msg,"%s",inf.msg);
									break;
								}
							}	
							//遍历数组,给每一个在线的用户都发消息
							for(int j=0; j<log_t;j++)
							{
								if((loguse[j].sock==myepoll[i].data.fd) || (loguse[j].sock == 0))
								{
									continue;
								}
								send_s.flg =2;
								write(loguse[j].sock,&send_s,sizeof(send_s));
							}
						}
						//文件传输功能,一次1024字节
						else if(inf.flg == '8')
						{
							rs_file(myepoll,inf,i);
						}
					}
				}
			}

		}
	}
	return 0;
}

2、server.h

具体结构体定义,全局变量定义,结构体定义。(功能见注释)

#ifndef _SERVER_H_
#define _SERVER_H_

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/epoll.h>
#include <mysql/mysql.h>

//收消息结构体
typedef struct {
	char flg;
	char file_flg;
	long size;
	char log[20];
	char pas[20];
	char user[20];
	char msg[100];
	char filename[100];
	char filemsg[1025];
}Inf;

//登录用户的结构体
typedef struct {
	int sock;
	char user[20];
}Loguse;

//发聊天消息的结构体
typedef struct {
	int flg;
	char user[20];
	char msg[150];
}SEND;

//登录函数
void login(MYSQL * ret,struct epoll_event *myepoll,struct sockaddr_in cliaddr,Inf inf,int index);
//注册函数
void Register(MYSQL * ret,struct epoll_event *myepoll,struct sockaddr_in cliaddr,Inf inf,int x);
//注销函数
void logout(MYSQL * ret,struct epoll_event *myepoll,Inf inf,int index);
//文件传输
void rs_file(struct epoll_event *myepoll,Inf inf,int index);
//套接字
int sock,sock_c;
//服务器的IP端口信息结构体
struct sockaddr_in seraddr;
socklen_t len;
//产生异动的套接字结构体数组
struct epoll_event myepoll[20];
//epid的信息结构体
struct epoll_event evt;
//epoll文件描述符
int epid;
//产生异动的文件描述符个数
int count;

MYSQL_RES * res;//数据库结构体,输出的信息
MYSQL mysql;//初始化数据库的核心结构体
MYSQL *ret;//数据库的核心结构体
int row,line;//数据库的行、列数

Loguse loguse[20];//在线用户结构体数组
int log_t;//数组下标

//给客户端发消息的结构体
SEND send_s;

Loguse file_use[20];//发文件用户结构体数组
int fu_t;
Loguse file_recv[20];//收文件用户结构体
int fr_t;

#endif

3、func_ser.c

功能函数的实现:

登录函数:用户登录,查询数据库,比对账户密码,登录成功则将用户的信息(套接字,用户名)存入信息结构体;登录失败则给客户端返回失败信息;

注册函数:将用户的账户,密码,用户名,IP地址,存入数据库。存入前判断先查询数据库,判断用户是否存在,并给客户端返回相应信息。

注销函数:账户注销,将用户的信息从结构体中删除,并给客户端返回信息。

文件传输:判断用户是要接收或是发送文件,接收文件则将用户信息存入相应的结构体数组;发送也是如此,并且遍历数组,通过用户名找到相应的套接字,实现用户间的文件传输。

私聊群聊:此功能在server.c文件中实现,具体介见注释。

#include "server.h"

//注册函数
void Register(MYSQL * ret,struct epoll_event *myepoll,struct sockaddr_in cliaddr,Inf inf,int x)
{

	struct sockaddr_in cli=cliaddr;
	char query[100];
	//拼接mysql命令,查询数据库
	sprintf(query,"select * from chat_user where 账户=\"%s\" or 用户名=\"%s\";",inf.log,inf.user);
	mysql_query(ret,query);

	//储存结果集合
	res = mysql_store_result(ret);
	if(res == NULL)
	{
		perror("mysql_store_result");
		return ;
	}
	//获取行,获取到了,则用户存在
	row = mysql_num_rows(res);
	if(row != 0)
	{
		write(myepoll[x].data.fd,"该账户存在",20);	
		mysql_free_result(res);	
	}
	//不存在则插入数据库
	else
	{
		mysql_free_result(res);

		char query_[100];
		sprintf(query_,"insert into chat_user values(\"%s\",\"%s\",\"%s\",\"%s\");",inf.log,inf.pas,inet_ntoa(cli.sin_addr),inf.user);
		mysql_query(ret,query_);
		write(myepoll[x].data.fd,"注册成功",20);
	}
}

//登录函数
void login(MYSQL * ret,struct epoll_event *myepoll,struct sockaddr_in cliaddr,Inf inf,int index)
{
	int x = index;
	struct sockaddr_in cli=cliaddr;
	Inf inf_c = inf;
	SEND se;

	//拼接字符串,通过用户名和密码查询数据库。
	char query[100]={0};
	sprintf(query,"select * from chat_user where 账户=\"%s\" and  密码=\"%s\";",inf_c.log,inf_c.pas);
	mysql_query(ret,query);

	//储存结果集合
	res = mysql_store_result(ret);
	if(res == NULL)
	{
		perror("mysql_store_result");
		return ;
	}

	row = mysql_num_rows(res);
	
    //执行查询语句后,查询到了。则登录成功。
	if(row != 0)
	{
		//获取当前行的第四个元素,存的是用户名,
		MYSQL_ROW myrow;
		myrow=mysql_fetch_row(res);
		sprintf(inf_c.user,"%s",myrow[3]);

		mysql_free_result(res);
		//给客户端返回信息,“登录成功+用户名””
		sprintf(se.msg,"%s","登录成功");
		sprintf(se.user,"%s",inf_c.user);
		write(myepoll[x].data.fd,&se,sizeof(se));

		//更新ip地址
		char que[100]={0};
		sprintf(que,"update chat_user set ip地址=\"%s\" where 账户=\"%s\";",inet_ntoa(cli.sin_addr),inf_c.log);	
		mysql_query(ret,que);
		//将登录的用户加入登录用户数组,方便实现私聊群聊
		sprintf(loguse[log_t].user, "%s", inf_c.user);
		loguse[log_t].sock = myepoll[x].data.fd;
		log_t++;	
	}
	else
	{
		mysql_free_result(res);
		sprintf(se.msg,"%s","账户或密码错误");
		write(myepoll[x].data.fd,&se,sizeof(se));
	}
}

//注销函数
void logout(MYSQL * ret,struct epoll_event *myepoll,Inf inf,int index)
{
	int x = index;

	char query[100]={0};
	sprintf(query,"select * from chat_user where 账户=\"%s\" and  密码=\"%s\";",inf.log,inf.pas);
	mysql_query(ret,query);

	//储存结果集合
	res = mysql_store_result(ret);
	if(res == NULL)
	{
		perror("mysql_store_result");
		return ;
	}

	row = mysql_num_rows(res);
	if(row != 0)
	{
		mysql_free_result(res);
	
		char que[100]={0};
		sprintf(que,"delete from chat_user  where 账户=\"%s\";",inf.log);
		mysql_query(ret,que);
		write(myepoll[x].data.fd,"账户已注销",20);
	}
	else
	{
		write(myepoll[x].data.fd,"账户不存在",20);
		mysql_free_result(res);
	}
}


void rs_file(struct epoll_event *myepoll,Inf inf,int index)
{
	int x = index;
	Inf send_i;
	if(inf.file_flg == '1')//发送文件
	{
		//遍历数组,找到正在发送用户的用户名;
		file_use[fu_t].sock = myepoll[x].data.fd;
		for(int j = 0;j<log_t;j++)
		{
			if(myepoll[x].data.fd == loguse[j].sock)
			{
				strcpy(file_use[fu_t].user,loguse[j].user);
				fu_t++;
				strcpy(send_i.user,loguse[j].user);
				break;
			}
		}
		int k;	
		//遍历数组,找到正要接收文件的用户的id(套接字);用户不在数组内,则还没做好接收准备,不传输。
		for( k = 0;k<fr_t;k++)
		{
			if(strcmp(inf.user,file_recv[k].user) == 0)
			{
				strcpy(send_i.filename,inf.filename);//文件名
				strcpy(send_i.filemsg,inf.filemsg);//一次传输的文件内容
				send_i.size = inf.size;//传输的大小
				//一次传输1024字节,
				if(inf.size<1024)
				{
					//最后一次的时候,给客户端回复
					strcpy(send_i.msg,"文件接收成功");
					write(myepoll[x].data.fd,&send_i,sizeof(send_i));
				}
				write(file_recv[k].sock,&send_i,sizeof(send_i));
				break;
			}
		}
		if(k == fr_t)
		{
			strcpy(send_i.msg,"用户不在线");
			write(myepoll[x].data.fd,&send_i,sizeof(send_i));
		}

	}
	else if(inf.file_flg == '2')//接收文件
	{
		//将信息存入数组,等待接收文件
		file_recv[fr_t].sock = myepoll[x].data.fd;
		for(int j = 0;j<log_t;j++)
		{
			if(myepoll[x].data.fd == loguse[j].sock)
			{
				strcpy(file_recv[fr_t].user,loguse[j].user);
				break;
			}
		}
		fr_t++;	
	}
	//发送文件的客户出问题,则中断传输,并回复接收文件的客户
	else if(inf.file_flg == '0')
	{
		strcpy(send_i.msg,"用户接收失败");
		for(int j;j<fu_t;j++)
		{
		
			if(inf.user == file_use[j].user)
			{
				write(file_recv[j].sock,&send_i,sizeof(send_i));
			}
		}
	}
}

三、客户端代码实现

1、client.h

#ifndef _CLIENT_H_
#define _CLIENT_H_

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
typedef struct {
	char flg;
	char file_flg;
	long size;
	char log[20];
	char pas[20];
	char user[20];
	char msg[100];
	char filename[100];
	char filemsg[1025];
}Inf;//消息结构体,与服务端进行交互时使用

typedef struct {
	int flg;
	char user[20];
	char msg[150];
}REVC;//消息结构体,承接服务端的返回信息,输出服务端的返回信息时用。

//可视化菜单函数
void meu_1();
void meu_2();


void Register(int sock,char ch);//登录
void login(int sock,char ch);//注册
void logout(int sock,char c);//注销

//私聊,群聊实现函数
void change();//功能选择
void *myfun(void *arg);//线程回调函数,群聊
void *myfun_1(void *arg);//线程回调函数,私聊
void union_chat(char ch);//群聊功能
void ind_chat(char ch);//私聊功能
//查看本地聊天记录
void check_i();
void check_u();

//文件收发
void rs_file(char ch);
void send_file(char ch,char avl);
void recv_file(char ch,char avl);

int sock;//套接字
struct sockaddr_in seraddr;//

pthread_t pid, pid_1;//多线程文件描述符

REVC revc_s;
char user_f[20];//文件收发用户

//
int flag;
int flag_1;
#endif

2、client.c

#include "client.h"

int main()
{
	flag =0;
	flag_1 =0;
	int ret;

	//创建套接字
	sock = socket(AF_INET,SOCK_STREAM,0);
	if(sock < 0)
	{
		perror("socket");
		return -1;
	}

	//链接服务器
	seraddr.sin_family = AF_INET;
	seraddr.sin_port= htons(6666);
	seraddr.sin_addr.s_addr = inet_addr("192.168.202.137");
	ret = connect(sock,(struct sockaddr *)&seraddr,sizeof(seraddr));
	if(ret < 0)
	{
		perror("connect");
		return -1;
	}

	char ch;
	while(1)
	{
		meu_1();
		scanf("%c",&ch);
		getchar();
		switch(ch)
		{
			case '1'://登录
				login(sock,ch);
				break;
			case '2'://注册

				Register(sock,ch);
				break;
			case '3'://注销
				logout(sock,ch);
				break;	
			case '0'://退出
				return 0;
			default:
				break;
		}
	}
	close(sock);
	return 0;
}

3.fun_cli.c

#include "client.h"

void meu_1()
{
	printf("================\n");
	printf("=====0.退出=====\n");
	printf("=====1.登录=====\n");
	printf("=====2.注册=====\n");
	printf("=====3.注销=====\n");
	printf("================\n");
}

void meu_2()
{
	printf("====================\n");
	printf("===    0.退出    ===\n");
	printf("===    4.私聊    ===\n");
	printf("===    5.群聊    ===\n");
	printf("===6.私聊聊天记录===\n");
	printf("===7.群聊聊天记录===\n");
	printf("===  8.文件传输  ===\n");
	printf("====================\n");
}

//注册
void Register(int sock,char ch)
{

	char pas_1[20];
	char buf[20];
	Inf inf;

	inf.flg=ch;
	printf("请输入账号:\n");
	scanf("%s",inf.log);
	getchar();
	printf("请输入用户名:\n");
	scanf("%s",inf.user);
	getchar();
	printf("请输入密码:\n");
P:	scanf("%s",inf.pas);
	getchar();
	printf("请确认密码:\n");
	scanf("%s",pas_1);
	getchar();
	if(strcmp(pas_1,inf.pas)==0)
	{
		write(sock,&inf,sizeof(inf));
		read(sock,buf,20);
		printf("%s\n",buf);
	}
	else
	{
		printf("请重新输入密码:\n");
		goto P;
	}
}

//登录
void login(int sock,char ch)
{
	REVC re;
	Inf inf;

	inf.flg = ch;
	printf("请输入账号:\n");
	scanf("%s",inf.log);
	getchar();
	printf("请输入密码:\n");
	scanf("%s",inf.pas);
	getchar();

	write(sock,&inf,sizeof(inf));

	read(sock,&re,sizeof(re));
	sprintf(user_f,"%s",re.user);
	printf("%s\n",re.msg);
	if(strcmp(re.msg,"登录成功")==0)
	{
		change();
	}
}

//注销
void logout(int sock,char c)
{
	char buf[20];
	Inf inf;

	inf.flg = c;
	printf("请输入要注销的账号:\n");
	scanf("%s",inf.log);
	getchar();
	printf("请输入密码:\n");
	scanf("%s",inf.pas);
	getchar();

	write(sock,&inf,sizeof(inf));

	read(sock,buf,20);
	printf("%s\n",buf);
}
	
//聊天功能选择
void  change()
{
	while(1)
	{
		meu_2();
		char ch;
		scanf("%c",&ch);
		getchar();
		switch(ch)
		{
			case '0':
				return;
			case '4':
				ind_chat(ch);
				break;
			case '5':
				union_chat(ch);
				break;
			case '6':
				check_i();
				break;
			case '7':
				check_u();
				break;
			case '8':
				rs_file(ch);
				break;
			default:
				break;
		}
	}
}

//线程收群消息
void *myfun(void *arg)
{
	while(1)
	{
		memset(&revc_s,0,sizeof(revc_s));
		if(read(sock,&revc_s,sizeof(revc_s))==0)
		{
			printf("服务器出错\n");
			break;
		}
		if(revc_s.flg == 2)
		{
			FILE *fd = NULL;
			fd = fopen("./union_chat.txt","a+");
			fprintf(fd,"%s %s %s\n",revc_s.user,":",revc_s.msg);
			fclose(fd);
			
			printf("%s:%s\n",revc_s.user,revc_s.msg);	
		}
		else if((flag == 1)&&(revc_s.flg==1))
		{
                         FILE *fid = NULL;
                         fid = fopen("./chat_s.txt","a+");
                         fprintf(fid,"%s %s %s\n",revc_s.user,":",revc_s.msg);
                         fclose(fid);
                }

	}
	
}

//群聊函数
void union_chat(char ch)
{
	Inf inf;
	char use[20];
	char c[4];
	flag = 1;

	printf("您已进入群聊模式\n输入AED退出群聊\n");
	if(flag_1 == 1)
	{
		FILE *p = fopen("./chat.txt","r");
		if(p==NULL)
		{
			goto P1;
		}
		while(fscanf(p,"%s %s %s",use,c,inf.msg) == 3)
		{
			printf("%s %s %s\n",use,c,inf.msg);
		}
		fclose(p);
		remove("chat.txt");
		memset(inf.msg,0,sizeof(inf.msg));
	}
P1:	flag_1 =2;

	pthread_create(&pid,NULL,myfun,NULL);
	while(1)
	{
		inf.flg =ch;
p:		scanf("%s",inf.msg);
		getchar();
		if(strlen(inf.msg) > 99)
		{
			printf("消息超过字数限制,发送失败\n");
			goto p;
		}
		else if(strcmp("AED",inf.msg) == 0)
		{
			flag_1 = 1;
			pthread_cancel(pid);
			pthread_join(pid,NULL);
			return;
		}
		else 
		{
			FILE *fp = fopen("./union_chat.txt","a+");
			fprintf(fp,"%s %s %s\n",user_f,":",inf.msg);
			fclose(fp);
			write(sock,&inf,sizeof(inf));
		}
	}
}


//线程收私聊消息
void *myfun_1(void *arg)
{
	while(1)
	{
		memset(&revc_s,0,sizeof(revc_s));
		if(read(sock,&revc_s,sizeof(revc_s))==0)
		{
			printf("服务器出错\n");
			break;
		}
		if(revc_s.flg == 1)
		{
			FILE *fd = NULL;
			fd = fopen("./ind_chat.txt","a+");
			fprintf(fd,"%s %s %s\n",revc_s.user,":",revc_s.msg);
			fclose(fd);

			printf("%s:%s\n",revc_s.user,revc_s.msg);
		}
		else if((flag_1 == 1)&&(revc_s.flg ==2))
		{
			FILE *fid = NULL;
			fid = fopen("./chat.txt","a+");
			fprintf(fid,"%s %s %s\n",revc_s.user,":",revc_s.msg);
			fclose(fid);
		}
	}
}

//私聊函数
void ind_chat(char ch)
{
	Inf inf;
	char use[20];
	char c[4];
	flag_1 =1;

	printf("您已进入私聊模式\n");

	if(flag ==1)
	{
		FILE * p = fopen("chat_s.txt","r");
		if(p==NULL)
		{
			goto F;
		}
		while(fscanf(p,"%s %s %s",use,c,inf.msg) == 3)
		{
			printf("%s %s %s\n",use,c,inf.msg);
		}
		fclose(p);
		remove("chat_s.txt");
		memset(inf.msg,0,sizeof(inf.msg));
	}
F:	flag = 2;
	pthread_create(&pid_1,NULL,myfun_1,NULL);
	while(1)
	{
		inf.flg =ch;
		printf("输入要私聊的用户名\n(AED退出)\n");
		scanf("%s",inf.user);
		getchar();
		if(strcmp(inf.user, "AED")==0)
		{
			flag = 1;
			pthread_cancel(pid_1);
			pthread_join(pid_1,NULL);
			return;
		}
		while(1)
		{
			printf("请输入(quit退出):\n");
p:			scanf("%s",inf.msg);
			getchar();
			
			if(strlen(inf.msg) > 99)
			{
				printf("消息超过字数限制,发送失败\n");
				goto p;
			}
			else if(strcmp("quit",inf.msg) == 0)
			{
				break;
			}
			else
			{
				FILE *fp = fopen("./ind_chat.txt","a+");
				fprintf(fp,"%s %s %s\n",user_f,":",inf.msg);
				fclose(fp);
				write(sock,&inf,sizeof(inf));
			}
		}
	}
}


//查看群聊聊天记录
void check_u()
{
	char ch[4];
	REVC re;
	FILE *fd = NULL;
	fd = fopen("./union_chat.txt","r");
	if(fd == NULL)
	{
		perror("fopen");
	}
	while(fscanf(fd,"%s %s %s",re.user,ch,re.msg) ==3)
	{
		printf("%s%s%s",re.user,ch,re.msg);
		printf("\n");
	}
	fclose(fd);
}



//查看私聊聊天记录
void check_i()
{
	char ch[4];
	REVC re;
	FILE *fd = NULL;
	fd = fopen("./ind_chat.txt","r");
	if(fd == NULL)
	{
		perror("fopen");
		return;
	}
	while(fscanf(fd,"%s %s %s",re.user,ch,re.msg) ==3)
	{
		printf("%s%s%s",re.user,ch,re.msg);
		printf("\n");
	}
	fclose(fd);
}

void rs_file(char ch)
{
	char avl;
	char ch1 =ch;
	printf("1发送文件\n");
	printf("2接收文件\n");
	scanf("%c",&avl);
	getchar();

	switch(avl)
	{
		case '1':
			send_file(ch1,avl);
			break;
		case '2':
			recv_file(ch1,avl);
			break;
		default:
			break;
	}
}

//文件传输
void send_file(char ch, char avl)
{
	Inf inf;
	char bot[30];
	char buf[150];
	long read_count;

	memset(&inf,0,sizeof(inf));
	inf.flg = ch;
	inf.file_flg = avl;

	printf("请输入要传输的用户名:\n");
	scanf("%s",inf.user);
	getchar();
	printf("请输入要传输的文件名:\n");
	scanf("%s",inf.filename);
	getchar();
	printf("请输入文件的路径:\n");
	scanf("%s",bot);
	getchar();
	sprintf(buf,"%s%s",bot,inf.filename);

	FILE * fp = fopen(buf,"rb");
	if(fp == NULL)
	{
		perror("fopen");
		return;
	}
	while(1)
	{
		read_count = fread(inf.filemsg,1,1024,fp);
		printf("%s\n",inf.filemsg);
		inf.size = read_count;
		write(sock,&inf,sizeof(inf));
		if(read_count < 1024)
		{
			break;
		}

	}
	read(sock,&inf,sizeof(inf));
	printf("%s\n",inf.msg);
	fclose(fp);
}

void recv_file(char ch,char avl)
{
	char buf[150];
	Inf inf;
	char bot[30];

	inf.flg = ch;
	inf.file_flg = avl;
	write(sock,&inf,sizeof(inf));
	printf("请输入你想存在哪个目录:\n");
	scanf("%s",bot);
	getchar();
	while(1)
	{

		read(sock,&inf,sizeof(inf));

		sprintf(buf,"%s%s",bot,inf.filename);
		FILE * fp = fopen(buf,"ab+");
		if(fp == NULL)
		{
			Inf sd;
			perror("fopen");
			sd.file_flg = '0';
			strcpy(sd.user,inf.user);
			write(sock,&sd,sizeof(sd));
			break;
		}
		fwrite(inf.filemsg,inf.size,1,fp);

		if(inf.size < 1024)
		{
			printf("%s\n",inf.msg);
			fclose(fp);
			break;
		}	
	}
}

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这段代码是一个简单的Linux网络编程聊天TCP实现。它包括了服务器端和客户端的代码。 服务器端(service function)通过socket函数创建一个套接字,然后绑定到指定的IP地址和端口上。接下来进入一个循环中,等待客户端的连接请求。当有客户端连接时,服务器会记录客户端的socket,并启动一个线程为该客户端提供服务。同时,服务器会提示其他客户端有新用户上线,并发送相应的信息给已连接的其他客户端。 客户端(main function)也通过socket函数创建一个套接字,然后与服务器建立连接。客户端需要输入一个用户名,并将其发送给服务器。之后,客户端进入一个循环中,等待用户输入消息。当用户输入"bye"时,客户端会发送该消息给服务器,并关闭套接字,结束程序。 整个程序还存在一些不足之处。首先,发送数据时无法识别空格。其次,查询聊天记录的功能还需完善,目前只能显示前100个字符。如果还有其他不足之处,可以继续完善。希望这段代码对您有所帮助。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [linux网络编程TCP多人聊天](https://blog.csdn.net/qq_44891751/article/details/95067267)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值