基于UDP协议构建简易聊天室

客户端

#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>

//打印错误的宏函数
#define ERR_MSG(msg) 					\
do 										\
{ 										\
	fprintf(stderr,"__%d__",__LINE__); 	\
	perror(msg); 						\
}while(0)

#define PROT 8848 				//服务器端口号
#define IP "192.168.31.213" 	//服务器IP地址

//保存信息结构体
typedef struct
{
	char type; 		//协议:L登录;M普通;Q退出
	char name[30]; 	//用户名
	char msg[256]; 	//消息内容
}INFO;

//消息写入
INFO creat_msg(INFO info,char*name)
{
	strcpy(info.name,name);   //用户名存入结构体

	fgets(info.msg,sizeof(info.msg),stdin);
	info.msg[strlen(info.msg)-1]=0;

	if(strcasecmp(info.msg,"quit")==0) 	//判断是否与退出消息一致
		info.type='Q'; 	//如一致,将协议改为退出消息
	else
		info.type='M'; 	//否则,将协议改为普通消息
	
	return info; 	//返回结构体
}

//接收线程
void* rcv(void*arg)
{
	//存储接收到的数据包来源
	struct sockaddr_in cin;
	socklen_t addrlen=sizeof(cin);

	char buf[300]="";
	int sfd=*(int*)arg;

	//接收
	while(1)
	{
		bzero(buf,sizeof(buf));
		if(recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,&addrlen)<0)
		{
			ERR_MSG("recvfrom");
			return NULL;
		}

		if(strcmp(buf,"repeat")==0) 	//用户名重复,则退出进程
		{
			printf("用户名重复\n");
			exit(0);
		}
		printf("%s\n",buf);
	}
}

//发送线程
void* sed(void*arg)
{
	//填充服务器IP地址和端口号
	struct sockaddr_in sin;
	sin.sin_family 		=AF_INET;
	sin.sin_port 		=htons(PROT); 	//端口号转网络字节序
	sin.sin_addr.s_addr =inet_addr(IP); //IP地址转网络字节序
	
	char name[30];
	int sfd=*(int*)arg;
	INFO info;
	
	//第一次输入为登录信息
	bzero(&info,sizeof(info));
	info.type='L';
	
	//结构体内只填用户名信息
	printf("输入用户名登录:");
	fgets(name,sizeof(name),stdin);
	name[strlen(name)-1]=0;
	strcpy(info.name,name);

	if(sendto(sfd,&info,sizeof(info),0,(struct sockaddr*)&sin,sizeof(sin))<0)
	{
		ERR_MSG("sendto");
		return NULL;
	}

	while(1)
	{
		//输入消息
		info=creat_msg(info,name);
		
		if(sendto(sfd,&info,sizeof(info),0,(struct sockaddr*)&sin,sizeof(sin))<0)
		{
			ERR_MSG("sendto");
			return NULL;
		}
		if(info.type=='Q') 	//协议为退出,则退出进程
			exit(0);
	}
}

int main(int argc, const char *argv[])
{
	system("clear");
	printf("***************聊天室****************\n\n");
	//创建报式套接字
	int sfd=socket(AF_INET,SOCK_DGRAM,0);
	if(sfd<0)
	{
		ERR_MSG("socket");
		return -1;
	}

	pthread_t tidrcv,tidsed;
	//创建接收线程
	if(pthread_create(&tidrcv,NULL,rcv,&sfd)!=0)
	{
		ERR_MSG("pthread_create");
		return -1;
	}

	//创建发送线程
	if(pthread_create(&tidsed,NULL,sed,&sfd)!=0)
	{
		ERR_MSG("pthread_create");
		return -1;
	}

	pthread_join(tidrcv,NULL);
	pthread_join(tidsed,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>

//打印错误的宏函数
#define ERR_MSG(msg) 					\
do 										\
{ 										\
	fprintf(stderr,"__%d__",__LINE__); 	\
	perror(msg); 						\
}while(0)

#define PORT 8848 				//服务器端口号
#define IP "192.168.31.213" 	//服务器IP地址

//保存信息结构体
typedef struct
{
	char type; 		//协议:L登录;M普通;Q退出
	char name[30]; 	//用户名
	char msg[256]; 	//消息内容
}INFO;

//创建用户信息链表
typedef struct Node
{
	char name[30]; 	//用户名
	struct sockaddr_in user; 	//用户地址信息
	struct Node* next;
}Link;
Link* list; 	//创建全局链表

//链表
//初始化链表
Link* list_create(Link*list)
{
	list=(Link*)malloc(sizeof(Link));
	if(NULL==list)
	{
		printf("链表创建失败\n");
		exit(0);
	}

	list->next=NULL;
	bzero(&list->name,sizeof(list->name));
	bzero(&list->user,sizeof(list->user));

	return list;
}

//尾插
int lstk_insert(Link*list,struct sockaddr_in cin,char* name)
{
	Link*p=(Link*)malloc(sizeof(Link));
	p->next=NULL;
	p->user=cin;
	strcpy(p->name,name);

	Link* q=list;
	while(q->next!=NULL)
	{
		q=q->next;
	}
	q->next=p;

	return 0;
}

//删除指定链表数据
int list_del(Link*list,struct sockaddr_in cin)
{
	Link*q=list;
	Link*p=q->next;

	while(q->next!=NULL)
	{
		if(p->user.sin_port==cin.sin_port) //对比端口号
		{
			q->next=p->next;
			free(p);
			p->next=NULL;
			break;
		}
		q=q->next;
		p=q->next;
	}
	return 0;
}

//转发消息
void transpond(int sfd,char* buf,struct sockaddr_in cin,int len)
{
	Link*p=list->next;
	while(p!=NULL)
	{
		//遍历链表成员发送,除了发送者
		if(p->user.sin_port!=cin.sin_port) //对比端口号
			sendto(sfd,buf,len,0,(struct sockaddr*)&(p->user),sizeof(p->user));
		p=p->next;
	}
}

//全体发送
void send_all(int sfd,char* buf,int len)
{
	Link*p=list->next;
	while(p!=NULL)
	{
		//遍历链表成员发送
		sendto(sfd,buf,len,0,(struct sockaddr*)&(p->user),sizeof(p->user));
		p=p->next;
	}
}

//判断用户名是否重复
int repeat(char* name,Link*list)
{
	if(list->next==NULL) 	//表空
		return 0;
	
	Link*q=list->next; 
	while(q!=NULL)
	{
		if(strcmp(name,q->name)==0) 	//判断用户名是否相同
			return -1;
		q=q->next;
	}
	return 1;
}


//登录消息
void send_log(int sfd,INFO info,struct sockaddr_in cin)
{
	char buf[50]="";
	int len; 	//字符串长度
	bzero(buf,sizeof(buf));

	if(repeat(info.name,list)<0) //判断用户名是否重复
	{
		strcpy(buf,"repeat");
		//如重复发送"repeat"给发送者
		sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,sizeof(cin));
		printf("用户名%s重复\n",info.name); 	//服务器终端输出重复信息
		return;
	}

	lstk_insert(list,cin,info.name); 	//将用户信息存入链表
	
	//需要转发的内容
	strcat(buf,"******");
	strcat(buf,info.name);
	strcat(buf,"已上线******");
	len=strlen(buf);
	
	//转发消息
	transpond(sfd,buf,cin,len);
}

//普通消息
void send_msg(int sfd,INFO info,struct sockaddr_in cin)
{
	char buf[300];
	int len;
	bzero(buf,sizeof(buf));

	//需要转发的内容
	strcat(buf,info.name);
	strcat(buf,":");
	strcat(buf,info.msg);
	len=strlen(buf);

	//转发消息
	transpond(sfd,buf,cin,len);
}

//退出消息
void send_quit(int sfd,INFO info,struct sockaddr_in cin)
{
	char buf[50]="";
	int len;
	bzero(buf,sizeof(buf));

	//需要转发的内容
	strcat(buf,"******");
	strcat(buf,info.name);
	strcat(buf,"已下线******");
	len=strlen(buf);

	//转发消息
	transpond(sfd,buf,cin,len);

	//删除下线用户链表内信息
	list_del(list,cin);
}

//服务器发送全体消息线程
void* ser_sed(void*arg)
{
	int sfd=*(int*)arg;
	char buf[300];
	char arr[255];
	int len;
	bzero(buf,sizeof(buf));

	scanf("%s",arr);
	getchar();
	strcat(buf,"server:");
	strcat(buf,arr);
	len=strlen(buf);
	
	//全体发送
	send_all(sfd,buf,len); 
}

int main(int argc, const char *argv[])
{
	system("clear");
	printf("***************聊天室****************\n\n");
	list=list_create(list); //初始化链表	
	//创建报式套接字
	int sfd=socket(AF_INET,SOCK_DGRAM,0);
	if(sfd<0)
	{
		ERR_MSG("socket");
		return -1;
	}

	//允许端口快速重用
	int reuse=1;
	if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0)
	{
		ERR_MSG("setsockopt");
		return -1;
	}

	//填充服务器IP地址和端口号
	struct sockaddr_in sin;
	sin.sin_family 		=AF_INET;
	sin.sin_port 		=htons(PORT); 	//端口号转网络字节序
	sin.sin_addr.s_addr =inet_addr(IP); //IP地址转网络字节序

	//绑定服务器地址信息结构体
	if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
	{
		ERR_MSG("bind");
		return -1;
	}
	printf("绑定成功\n");

	//存储接收到的数据包来源
	struct sockaddr_in cin;
	socklen_t addrlen=sizeof(cin);

	INFO info;

	pthread_t tid;
	//创建发送线程
	if(pthread_create(&tid,NULL,ser_sed,&sfd)!=0)
	{
		ERR_MSG("pthread_create");
		return -1;
	}

	//接收并转发消息
	while(1)
	{
		if(recvfrom(sfd,&info,sizeof(info),0,(struct sockaddr*)&cin,&addrlen)<0)
		{
			ERR_MSG("recvfrom");
			return -1;
		}
		printf("[%s:%d]%s:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),info.name,info.msg);
		switch(info.type)
		{
		case 'L':
			send_log(sfd,info,cin); 	//登录消息
			break;
		case 'M':
			send_msg(sfd,info,cin); 	//普通消息
			break;
		case 'Q':
			send_quit(sfd,info,cin); 	//退出消息
			break;
		}
	}


	close(sfd);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值