Linux局域网多人聊天软件

本文介绍了一个简单的聊天系统的实现方法,包括客户端和服务端的功能介绍、主要知识点和技术细节。客户端能够完成登陆注册、在线用户列表展示等功能,而服务端则负责管理用户状态、聊天数据记录等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

功能介绍

  1. 客户端:登陆及注册;列出当前在线用户列表、发送聊天消息、传输文件等。
  2. 服务端:记录注册及在线用户链表、记录用户聊天数据、显示用户的登陆退出等。

主要知识点:socket套接字、链表用户管理、线程创建管理、IO复用、select监听套接字、文件读写等。


程序下载

https://download.youkuaiyun.com/download/mrhjlong/10337335


服务端主函数代码:client.c

/*************************************************************************
	> File Name: client.c
	> Author: mrhjlong
	> Mail: mrhjlong@163.com 
	> Created Time: 2016年08月01日 星期一 14时40分39秒
 ************************************************************************/

#include "userlist.h"

void *func_ttl(void *arg)
{
	int fd = (int)arg;
	while(1)
	{
		cli_TTL(fd);
		sleep(20);
	}
	return NULL;
}

int main(int argc, char *argv[])
{	
	int fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0)
		err_sys("socket error");
	
	struct sockaddr_in des_addr;
	des_addr.sin_family = AF_INET;
	des_addr.sin_port = htons(9999);
	inet_pton(AF_INET, "127.0.0.1", &des_addr.sin_addr);
	
	//连接客户端
	int ret = connect(fd, (struct sockaddr *)&des_addr, sizeof(des_addr));
	if(ret == -1)
		err_sys("connect error");
	
	//登陆和注册登陆
	ret = cli_REG_LOG(fd);
	if(ret == -1)	//放弃登陆或注册,直接退出
	{
		close(fd);
		return 0;
	}
	
	//创建线程发送保活信息
	pthread_t pid;
	ret = pthread_create(&pid, NULL, func_ttl, (void *)fd);
	if(ret != 0)
		err_sys("pthread create error");
	
	MSG msgdata;
	fd_set read_set;
	int n;
	int flag = 0;
	char recv_buf[BUFSIZE] = {0};
	char fname[50] = {0};
	
	while(1)
	{
		FD_ZERO(&read_set);
		if(flag == 0)
		{
			printf("Input a command: s-send | f-file | l-list | q-quit...\n");
			FD_SET(0, &read_set);
		}
		FD_SET(fd, &read_set);
		
		select(fd + 1, &read_set, NULL, NULL, NULL);
		
		if(FD_ISSET(0, &read_set))	//输入响应
		{
			bzero(&msgdata, sizeof(msgdata));
			fgets(msgdata.cmd, 50, stdin);
			if(strcmp(msgdata.cmd, "q\n") == 0)	//退出
			{
				pthread_cancel(pid);	//关闭线程
				flag = 1;
				shutdown(fd, SHUT_WR);
				FD_CLR(0, &read_set);
				continue;
			}
			else if(strcmp(msgdata.cmd, "l\n") == 0)	//发送命令,列出当前在线用户
			{
				cli_LIST(fd);
				continue;
			}
			else if(strcmp(msgdata.cmd, "s\n") == 0)
			{
				cli_SEND(fd, &msgdata);
			}
			else if(strcmp(msgdata.cmd, "f\n") == 0)	//发送FILE命令
			{
				bzero(fname, 50);
				ret = cli_FILE(fd, &msgdata, fname);
				if(ret == 0)
					flag = 1;
				continue;
			}
			else
			{
				printf("#################WARNING#################\n");
				printf("Input error! Please try again!\n");
				printf("#########################################\n");
				continue;
			}
		}
		
		if(FD_ISSET(fd, &read_set))		//接收信息
		{
			bzero(recv_buf, BUFSIZE);
			bzero(&msgdata, sizeof(msgdata));
			n = recv(fd, recv_buf, BUFSIZE, 0);
			if(n == 0)		//关闭
			{
				printf("closed!\n");
				break;
			}
			
			read_XML(recv_buf, &msgdata);
			if(strcmp(msgdata.cmd, "LISTD") == 0)
			{
				printf("**************Online users:**************\n");
				printf("%s\n", msgdata.text);
				printf("*****************************************\n");
				continue;
			}
			else if(strcmp(msgdata.cmd, "RECV") == 0)
			{
				printf("*****************RECV...*****************\n");
				printf("FROM:%s\n", msgdata.name);
				printf("MSG:%s\n", msgdata.text);
				printf("*****************************************\n");
			}
			else if(strcmp(msgdata.cmd, "NOUSR") == 0)
			{
				printf("#################WARNING#################\n");
				printf("User:%s is offline! Please try later.\n", msgdata.text);
				printf("#########################################\n");
				flag = 0;
			}
			else if(strcmp(msgdata.cmd, "LEAVE") == 0)
			{
				pthread_cancel(pid);	//关闭线程
				printf("cmd closed!\n");
				break;
			}
			else if(strcmp(msgdata.cmd, "FCNT") == 0)	//建立传输文件连接,发送
			{
				cli_FCNT(&msgdata, fname);
				flag = 0;
			}
			else if(strcmp(msgdata.cmd, "FLSN") == 0)	//建立传输文件监听, 接收信息
			{
				cli_FLSN(fd, &msgdata);
			}
		}
	}
	close(fd);
	return 0;
}


服务端主函数代码:server.c

/*************************************************************************
	> File Name: server.c
	> Author: mrhjlong
	> Mail: mrhjlong@163.com 
	> Created Time: 2016年08月01日 星期一 14时13分10秒
 ************************************************************************/

#include "userlist.h"

int main(void)
{
	struct list_head list;	//新建在线用户链表
	INIT_LIST_HEAD(&list);	//初始化链表头
	
	struct list_head usrList;	//新建已注册用户链表 
	INIT_LIST_HEAD(&usrList);
	
	FILE *fp = fopen("regUser.txt", "r");
	if(fp == NULL)
		err_sys("open regUser.txt error!\n");
	//获取已注册用户链表
	get_user_list(fp, &usrList);	
	
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0)
		err_sys("socket error");
	
	int optval = 1;
	int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));
	if(ret == -1)
		err_sys("setsockopt error!");
	
	//服务器地址端口设置
	struct sockaddr_in my_addr;
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(9999);
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	//绑定服务器
	ret = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
	if(ret != 0)
		err_sys("bind error");
	//监听
	ret = listen(sockfd, 10);
	if(ret != 0)
		err_sys("listen error");
	printf("listening...\n");
	
	fd_set read_set;
	MSG msgdata;
	char recv_buf[BUFSIZE] = {0};
	int n;
	struData_t *p = NULL;
	struct list_head *pos = NULL;
	
	struct timeval timeout;
	LD ldata;

	while(1)
	{
		FD_ZERO(&read_set);
		FD_SET(sockfd, &read_set);
		FD_SET(0, &read_set);
		//遍历链表,添加套接字到select
		list_for_each(pos, &list)
		{
			p = list_entry(pos, struData_t, list);
			FD_SET(p->sockfd, &read_set);
		}
		//设置select
		timeout.tv_sec = 3;		//阻塞3秒
		timeout.tv_usec = 0;
		if(pos->prev == &list)
			ret = select(sockfd + 1, &read_set, NULL, NULL, &timeout);
		else
		{
			p = list_entry(pos->prev, struData_t, list);
			ret = select(p->sockfd + 1, &read_set, NULL, NULL, &timeout);
		}
		//超时返回,检测客户保活信息
		if(ret == 0)		
		{
			chk_ttl(&list);
			continue;
		}
		
		//服务端退出
		if(FD_ISSET(0, &read_set))
		{
			char cmd_quit[50] = {0};
			fgets(cmd_quit, 50, stdin);
			if(strcmp(cmd_quit, "q\n") == 0)	//输入q退出
			{
				ser_quit(&usrList, &list);
				printf("Closing server!\n");
				break;
			}
			else if(strcmp(cmd_quit, "s\n") == 0)	//输入s显示聊天记录
			{
				FILE *fp = fopen("Chatlog.txt", "r");
				char buffer[100] = {0};
				if(fp == NULL)
					printf("open Chatlog.txt error!\n");
				
				printf("*****************Chat record:******************\n");
				fgets(buffer, 100, fp);
				while(strlen(buffer) >= 8)
				{
					printf("%s", buffer);
					bzero(buffer, 100);
					fgets(buffer, 100, fp);
				}
				printf("***********************************************\n");
			}
			else if(strcmp(cmd_quit, "l\n") == 0) 	//输入l获取已注册用户信息
			{
				FILE *fp = fopen("regUser.txt", "r");
				if(fp == NULL)
					err_sys("open regUser.txt error!\n");
				//获取已注册用户链表
				get_user_list(fp, &usrList);
			}
			else
				printf("Input error! Please try again!\n");
			
			continue;
		}
		
		//监听套接字响应
		if(FD_ISSET(sockfd, &read_set))	
		{	
			ldata.sockfd = sockfd;
			ldata.usrList = &usrList;
			ldata.list = &list;
			//创建处理注册和登陆操作的线程
			pthread_t pid;
			int n = pthread_create(&pid, NULL, listen_reg_log, (void *)&ldata);
			if(n != 0)
				err_sys("pthread create error");
			n = pthread_detach(pid);
			if(n != 0)
				err_sys("pthread detach error");
			
			usleep(200);	//等待线程accept,否则会重复创建线程
			ret--;
		}
		//客户端套接字响应,接收到信息
		if(ret > 0)							
		{
			//遍历链表,找到响应的套接字
			list_for_each(pos, &list)		
			{
				p = list_entry(pos, struData_t, list);
				if(FD_ISSET(p->sockfd, &read_set))
					break;
			}
			
			bzero(recv_buf, BUFSIZE);
			n = recv(p->sockfd, recv_buf, BUFSIZE, 0);
			//客户端主动关闭,服务端释放内存
			if(n == 0)		
			{
				printf("User:%s is leaving...\n", p->client_name);
				close(p->sockfd);
				list_del(pos);
				free(p);
				continue;
			}
			
			bzero(&msgdata, sizeof(msgdata));
			read_XML(recv_buf, &msgdata);	//解析XML数据
			
			if(strcmp(msgdata.cmd, "LIST") == 0)		//LIST命令处理
			{
				ser_LIST(p->sockfd, &list, &msgdata);
				continue;
			}
			else if(strcmp(msgdata.cmd, "TTL") == 0)	//TTL命令处理
			{
				time(&(p->ttl));	//重置保活时间
				continue;
			}
			else if(strcmp(msgdata.cmd, "SEND") == 0)	//SEND命令处理
			{
				ser_SEND(p->sockfd, p->client_name, &list, &msgdata);
				continue;
			}
			else if(strcmp(msgdata.cmd, "FILE") == 0)
			{
				ser_FILE(p->sockfd, p->client_name, &list, &msgdata);
			}
		}	
	}
	close(sockfd);
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值