8.12 项目:基于UDP的网络聊天室

发布于:2023-01-20 ⋅ 阅读:(447) ⋅ 点赞:(0)

项目:基于UDP的网络聊天室

需求

项目需求

1.如果有用户登录,其他用户可以收到这个人的登录信息
2.如果有人发送信息,其他用户可以收到这个人的群聊信息
3.如果有人下线,其他用户可以收到这个人的下线信息
4.服务器可以发送系统信息

写项目的方法

1.画流程图
2.根据流程图写框架
3.将每个功能实现

流程图

客户端流程图

在这里插入图片描述

服务器流程图

在这里插入图片描述

代码示例

服务器端代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <linux/input.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#define ERR_MSG(msg) do{\
	fprintf(stderr,"__%d__",__LINE__);\
	perror(msg);\
}while(0);

int sfd;
//定义单链表存储客户端IP和端口
typedef struct Node
{
	union
	{
		int len; 					//头结点的数据域,记录个数
		struct sockaddr_in sin; 	//普通节点的数据域
	};
	struct Node* next;
}linkList;
//定义数据包的结构体
typedef struct
{
	char type; 					//L代表登录 Q代表退出 C代表通信
	char name[20]; 				//姓名
	char text[128]; 			//群聊消息
}Data;

Data msgdata;

//创建单链表
linkList* list_create()
{
	linkList* L = (linkList*)malloc(sizeof(linkList));
	if(NULL==L)
	{
		printf("创建失败!\n");
		return NULL;
	}
	//初始化
	L->len=0;
	L->next=NULL;
	//printf("创建成功!\n");
	return L;
}
//服务器接收
void* SerRcv(void* arg)
{
	linkList* L = *(linkList**)arg;
	while(1)
	{
		//申请节点
		linkList* p = (linkList*)malloc(sizeof(linkList));
		if(NULL == p)
		{
			printf("节点申请失败\n");
			return NULL;
		}
		p->next = NULL;
		//长度
		socklen_t addrlen = sizeof(p->sin);
		//接收
		bzero(msgdata.text,sizeof(msgdata.text));
		bzero(msgdata.name,sizeof(msgdata.name));
		ssize_t res = recvfrom(sfd,&msgdata,sizeof(msgdata),0,(struct sockaddr*)&(p->sin),&addrlen);
		if(res < 0)
		{
			ERR_MSG("recvfrom");
			break;
		}
		if(msgdata.type=='L')
		{
			//将存储地址信息的节点插入到单链表中
			//定义遍历节点
			linkList* q = L;
			while(q->next!=NULL)
				q=q->next;
			q->next = p;
			L->len++;
			//printf("长度:%d\n",L->len);
			printf("%s [%s:%d]:login\n",msgdata.name,inet_ntoa((p->sin).sin_addr),ntohs((p->sin).sin_port));
			//发送上线通知
			linkList* s = L;
			while(s->next!=NULL)
			{
				s=s->next;
				if(ntohs((s->sin).sin_port)!=ntohs((p->sin).sin_port))
				{
					//服务器将收到的信息转发给所有人                                                        
					if(sendto(sfd,&msgdata,sizeof(msgdata),0,(struct sockaddr*)&(s->sin),sizeof(s->sin))<0)
					{
						ERR_MSG("sendto");
						break;
					}
				}
			}
		}
		//printf("%c %s %s\n",msgdata.type,msgdata.name,msgdata.text);
		if(msgdata.type=='C')
		{
			linkList* s = L;
			if(strcasecmp(msgdata.text,"quit")==0)
			{
				msgdata.type='Q';
				printf("%s [%s:%d]:quit\n",msgdata.name,inet_ntoa((p->sin).sin_addr),ntohs((p->sin).sin_port));
				while(s->next!=NULL)
				{
					s=s->next;
					if(ntohs((s->sin).sin_port)!=ntohs((p->sin).sin_port))
					{
						//服务器将收到的信息转发给所有人
						if(sendto(sfd,&msgdata,sizeof(msgdata),0,(struct sockaddr*)&(s->sin),sizeof(s->sin))<0)
						{
							ERR_MSG("sendto");
							break;
						}
					}
					//printf("sendto success\n");
				}
				continue;
			}

			printf("%s [%s:%d]:chat\n",msgdata.name,inet_ntoa((p->sin).sin_addr),ntohs((p->sin).sin_port));
			while(s->next!=NULL)
			{
				s=s->next;
				if(ntohs((s->sin).sin_port)!=ntohs((p->sin).sin_port))
				{
					//服务器将收到的信息转发给所有人
					if(sendto(sfd,&msgdata,sizeof(msgdata),0,(struct sockaddr*)&(s->sin),sizeof(s->sin))<0)
					{
						ERR_MSG("sendto");
						break;
					}
				}
			}
		}
	}
	pthread_exit(NULL);
}
//服务器发送系统信息
void* SerSnd(void* arg)
{
	linkList* L = *(linkList**)arg;
	while(1)
	{
		bzero(msgdata.text, sizeof(msgdata.text));
		//printf("请输入>>>");      
		fgets(msgdata.text, sizeof(msgdata.text), stdin);
		msgdata.text[strlen(msgdata.text)-1] = 0;
		strcpy(msgdata.name,"System");
		msgdata.type = 'S';

		linkList* s = L;
		while(s->next!=NULL)
		{
			s=s->next;
			//服务器将收到的信息转发给所有人
			if(sendto(sfd,&msgdata,sizeof(msgdata),0,(struct sockaddr*)&(s->sin),sizeof(s->sin))<0)
			{
				ERR_MSG("sendto");
				break;
			}
		}
	}
	pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
	if(argc < 3)
	{
		fprintf(stderr,"请输入IP port\n");
		return -1;
	}
	//将获取到的端口号字符串转换成整形
	int port = atoi(argv[2]);
	if(port < 1024 || port > 49151)
	{
		fprintf(stderr,"port %d input error!! 1024~49151\n",port);
		return -1;
	}
	//创建报式套接字
	sfd = socket(AF_INET,SOCK_DGRAM,0);
	if(sfd<0)
	{
		ERR_MSG("socket");
		return -1;
	}
	//填充服务器的IP地址以及端口号
	struct sockaddr_in sin;
	sin.sin_family  	= AF_INET;
	sin.sin_port 		= htons(port);
	sin.sin_addr.s_addr = inet_addr(argv[1]);
	//绑定服务器的地址信息结构体
	if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
	{
		ERR_MSG("bind");
		return -1;
	}
	//创建单链表
	//创建头节点
	linkList* L=list_create();
	if(NULL == L)
	{
		return -1;
	}

	//创建线程
	pthread_t tid1,tid2;
	if(pthread_create(&tid1,NULL,SerRcv,(void*)&L)!=0)
	{
		ERR_MSG("pthread_create");
		return -1;
	}
	if(pthread_create(&tid2,NULL,SerSnd,(void*)&L)!=0)
	{
		ERR_MSG("pthread_create");
		return -1;
	}

	//主线程阻塞
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	//关闭套接字
	close(sfd);
	return 0;
}

客户端代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h> 
//打印错误新的宏函数
#define ERR_MSG(msg)  do{\
	fprintf(stderr, " __%d__ ", __LINE__);\
	perror(msg);\
}while(0)
int sfd;
typedef struct
{
	char type; 				//L代表login Q代表quit C代表Chat
	char name[20]; 			//姓名
	char text[128]; 		//消息
}Data;
Data msgdata;
char name[20];
//客户端发送
void* CliSnd(void* arg)
{
	struct sockaddr_in sin = *(struct sockaddr_in*)arg;
	while(1)
	{
		bzero(msgdata.text, sizeof(msgdata.text));                                                     
		//printf("请输入>>>");		
		fgets(msgdata.text, sizeof(msgdata.text), stdin);
		msgdata.text[strlen(msgdata.text)-1] = 0;
		msgdata.type = 'C';
		strcpy(msgdata.name,name);
		//将数据包发送给服务器,所以地址信息结构体需要填服务器的信息
		if(sendto(sfd, &msgdata, sizeof(msgdata), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
		{
			ERR_MSG("sendto");
			break;
		}
		//printf("sendto success\n");
		if(strcasecmp(msgdata.text,"quit")==0)
			exit(0);
	}
	pthread_exit(NULL);
}
//客户端接收
void* CliRcv(void* arg)
{      
	while(1)
	{
		//接收
		bzero(msgdata.text, sizeof(msgdata.text));
		//接收服务器发送过来的数据包
		if(recvfrom(sfd, &msgdata, sizeof(msgdata), 0,NULL,NULL) < 0)
		{
			ERR_MSG("recvfrom");
			break;
		}
		//printf("test:%c\n",msgdata.type);
		if(msgdata.type=='L')
			printf("-----%s已上线-----\n",msgdata.name);
		else if(msgdata.type=='Q')
			printf("-----%s已下线-----\n",msgdata.name);
		else if(msgdata.type=='C')
			printf("%s:%s\n", msgdata.name,msgdata.text);
		else
			printf("%s:%s\n", msgdata.name,msgdata.text);
	}
	pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
	if(argc < 3)
	{
		fprintf(stderr, "请输入IP port\n");
		return -1;
	}
	//将获取到的端口号字符串,转换成整形
	int port = atoi(argv[2]);
	if(port < 1024 || port > 49151)
	{
		fprintf(stderr, "port %d input error!! 1024~49151\n", port);
		return -1;
	}
	//填充服务器的IP地址以及端口号 -->因为客户端要主动发送数据包给服务器
	struct sockaddr_in sin;                                               
	sin.sin_family      = AF_INET;
	sin.sin_port        = htons(port);
	sin.sin_addr.s_addr = inet_addr(argv[1]);
	//创建报式套接字
	sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}
	//输入姓名登录
	printf("请输入姓名登录>>>");
	fgets(msgdata.name, sizeof(msgdata.name), stdin);
	msgdata.name[strlen(msgdata.name)-1] = 0;
	msgdata.type = 'L';
	strcpy(name,msgdata.name);
	if(sendto(sfd, &msgdata, sizeof(msgdata), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
		ERR_MSG("sendto");
		exit(0);
	}
	printf("-------登录成功-------\n");
	//创建线程
	pthread_t tid1,tid2;
	if(pthread_create(&tid1,NULL,CliSnd,(void*)&sin)!=0)
	{
		ERR_MSG("pthread_create");
		return -1;
	}
	if(pthread_create(&tid2,NULL,CliRcv,NULL)!=0)
	{
		ERR_MSG("pthread_create");
		return -1;                                
	}
	//主线程阻塞
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	//关闭套接字
	close(sfd);
	return 0;
}

运行结果

在这里插入图片描述

完整代码链接

https://gitee.com/benson_lin/network-chat-room-based-on-udp

本文含有隐藏内容,请 开通VIP 后查看