网络编程8/11——基于UDP的网络聊天室

目录

需求

 代码

服务器

客户端


需求

项目需求:

  1. 如果有用户登录,其他用户可以收到这个人的登录信息

  2. 如果有人发送信息,其他用户可以收到这个人的群聊信息

  3. 如果有人下线,其他用户可以收到这个人的下线信息

  4. 服务器可以发送系统信息

流程图

 

 代码

服务器

#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<netinet/in.h>

#define ERR_MSG(msg) do{\
	fprintf(stderr," __%d__",__LINE__);\
	perror(msg);\
}while(0);

struct sockaddr_in sin1,cin;
socklen_t addrlen=sizeof(struct sockaddr_in);

//用户信息结构体
typedef struct{
	char name[20];
	char msg[128];
	int flag;
}INFO;
int len_info=sizeof(INFO);

//客户端信息链表结构体
typedef struct usr_list{
	struct sockaddr_in usr_addr;
	struct usr_list *next;
}List;
List *list;

//链表初始化
void init()
{
	list=(List *)malloc(sizeof(List));
	list->next=NULL;
}

//链表插入(头插)
void insert_list(struct sockaddr_in usr_addr)
{
	List *p=list;
	List *node;
	node=(List*)malloc(sizeof(List));
	node->usr_addr=usr_addr;
	node->next=p->next;
	p->next=node;
}

//删除
void del_list(struct sockaddr_in usr_addr)
{
	List *q=list;//前驱节点
	List *p=list->next;//要删除的节点
	while (p && p->next!=NULL)
	{
		if (p->usr_addr.sin_port==usr_addr.sin_port)
		{
			break;
		}
		p=p->next;
		q=q->next;
	}
	q->next=p->next;
	free(p);
	
}

//发送登录信息
void send_login(int sfd,INFO info,struct sockaddr_in cin)
{
	if(list == NULL)
	{
		printf("用户列表为空\n");
	}
	List *p=list->next;
	char buf[30]="";
	strcat(buf,info.name);
	strcat(buf,"<<<已登录");
	printf(">>>%s\n",buf);
	while (p)
	{
		//sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->usr_addr),addrlen);
		sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->usr_addr),sizeof(cin));
		p=p->next;
	}
	insert_list(cin);
}

//发送聊天信息
void send_msg(int sfd,INFO info,struct sockaddr_in cin)
{
	List *p=list->next;
	char buf[128]="";
	strcat(buf,info.name);
	strcat(buf,":");
	strcat(buf,info.msg);
	buf[strlen(buf)-1]=0;
		while (p)
	{
		if(p->usr_addr.sin_port != cin.sin_port || (p->usr_addr.sin_addr.s_addr  != cin.sin_addr.s_addr))
		sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->usr_addr),sizeof(cin));
		p=p->next;
	}

}

//退出

void send_off(int sfd,INFO info,struct sockaddr_in cin){
    List *p = list->next;
    char buf[128]= {0};
    strcat(buf,info.name);
    strcat(buf,"退出聊天室"); //将用户昵称+Offline保存到buf缓冲区进行群发
    //buf[strlen(buf)] = '\0';
    printf("%s退出聊天室\n",info.name);
    while(p){
        sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->usr_addr),addrlen);
        p = p->next;
    }
    del_list(cin); //删除退出用户节点
}
//服务器向客户端发送信息
void serv_send(int sfd,char *buf)
{
	List *p=list->next;
	printf("向客服端发送消息\n");
	printf("函数测试%s\n",buf);
	while(p)
	{
		sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->usr_addr),addrlen);
		//sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->usr_addr),addrlen);
		p=p->next;
	}
}




//线程收发客户端来的消息
void *func(void *arg)
{
	pthread_detach(pthread_self());
	int sfd=*(int *)arg;
	ssize_t res;
	while(1)
	{
		//接受信息保存在info中
		INFO info;
		if ((res=recvfrom(sfd,&info,sizeof(info),0,(struct sockaddr*)&cin,&addrlen))<0)
		if(res<0)
		{
			ERR_MSG("recvfrom");
			break;
		}
		printf("接收来自 %s 的信息\n",info.name);

		switch(info.flag)
		{
			case 1:
				{
					send_login(sfd,info,cin);
					break;
				}
			case 2:
				{
					send_msg(sfd,info,cin);
					break;
				}
			case 3:
				{
					send_off(sfd,info,cin);
					break;
				}
		}

	}
}
int main(int argc, const char *argv[])
{
	if (argc<3)
	{
		fprintf(stderr,"请输入IP port\n");
		return -1;
	}
	
	init();//初始化链表

	//将获取到的端口字符串转换成整形
	int port =atoi(argv[2]);
	if (port<1024 || port >49151)
	{
		fprintf(stderr,"port %d input error!! 1024~49151\n",port);
		return -1;
	}

	//创建套接字
	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;
	sin1.sin_family        =AF_INET;
	sin1.sin_port          =htons(atoi(argv[2]));
	sin1.sin_addr.s_addr   =inet_addr(argv[1]);
	
	//绑定服务器的地址信息结构体
	if(bind(sfd,(struct sockaddr*)&sin1,sizeof(sin1))<0)
	{
		ERR_MSG("bind");
		return -1;
	}
	printf("bind success\n");

	//创建子线程
	pthread_t tid;
	pthread_create(&tid,NULL,func,&sfd);

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

	//char buf[128]="";
	while (1)
	{
		char buf[64]="";
		char msg[48]="";
		//strcat(buf,"system:");
		strcpy(buf,"system msg:");
		fgets(msg,48,stdin);
		msg[strlen(msg)-1]=0;
		strcat(buf,msg);
		printf("test %s\n",buf);
		//serv_send(sfd,buf);
		List *p=list->next;
		printf("向客服端发送消息\n");
		//printf("函数测试%s\n",buf);
		while(p)
		{
			sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->usr_addr),addrlen);
			p=p->next;
		}

		if(strcmp(msg,"exit")==0)
			exit(0);
		//memset(msg,0,48); //清空
        //memset(buf,0,64); //清空


	}
	//关闭套接字
	close(sfd);
	return 0;
}

客户端

#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>

#define ERR_MSG(msg) do{\
	fprintf(stderr," __%d__",__LINE__);\
	perror(msg);\
}while(0);

typedef struct{
	char name[20];
	char msg[128];
	int flag;
}INFO;

INFO create_msg(INFO info,char *name)
{
	char msg[64];
	//printf("请输入>>>");
	fgets(msg,sizeof(msg),stdin);
	strcpy(info.name,name);
	strcpy(info.msg,msg);
	if (strcmp(msg,"exit\n")==0)
	{
		info.flag=3;//退出协议
	}else
		info.flag =2;
	return info;
}

void *rcv_func(void *arg)//接受服务器信息线程
{
	pthread_detach(pthread_self());
	int sfd=*(int *)arg;
	
	while (1)
	{
		char buf[256]="";
		//接收服务器发送过来的包
		//if(recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,&addrlen)<0)
		if(recvfrom(sfd,buf,sizeof(buf),0,NULL,NULL)<0)
		{
			ERR_MSG("recvfrom");
			return NULL;
		}
		if (strcmp(buf,"system msg:exit")==0)
		{
			printf("服务器下线,聊天室关闭\n");
			exit(0);
		}
		printf("%s\n",buf);
		
	}
}


int main(int argc, const char *argv[])
{
	INFO info;
	ssize_t res;
	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]);
	int len_s=sizeof(sin);
	//创建套接字
	int sfd = socket(AF_INET,SOCK_DGRAM,0);
	if (sfd<0)
	{
		ERR_MSG("socket");
		return -1;
	}
	printf("socket success\n");

	pthread_t tid;//接收线程
	pthread_create(&tid,NULL,rcv_func,&sfd);
	pthread_t tid2;//发送线程
	pthread_create(&tid2,NULL,rcv_func,&sfd);

	char name[20];
	printf("请输入用户名>>>");
	fgets(name,sizeof(name),stdin);
	name[strlen(name)-1]=0;
	strcpy(info.name,name);
	info.flag=1;//登录协议
	sendto(sfd,&info,sizeof(info),0,(struct sockaddr*)&sin,len_s);
	
	printf("***************************************************\n");
		
	//struct sockaddr_in rcv_addrmsg;            //存储接收到的数据包来自哪里
	//socklen_t addrlen=sizeof(rcv_addrmsg);

	char buf[64]="";
	while (1)
	{
		//bzero(buf,sizeof(buf));
		//发送
		//info =create_msg(info,name);
		INFO info;
		char msg[64];
		//printf("请输入>>>");
		fgets(msg,sizeof(msg),stdin);
		strcpy(info.name,name);
		strcpy(info.msg,msg);
		//msg[strlen(msg)-1]=0;
		if (strcmp(msg,"exit\n")==0)
		{
			info.flag=3;//退出协议
		}else
			info.flag =2;
		//return info;
		//info.flag=2;
	//	printf("发送信息:%s\n",info.msg);
		res=sendto(sfd,&info,sizeof(info),0,(struct sockaddr*)&sin,sizeof(sin));
		if (res<0)
		{
			ERR_MSG("sendto");
			return -1;
		}
		//printf("sendto success\n");

		if (info.flag==3)
		{
			break;
		}
		memset(&info,0,sizeof(info));
		//printf("[%s:%d]:%s\n",inet_ntoa(sin.sin_addr),ntohs(sin.sin_port),buf);
	}
	//关闭套接字
	close(sfd);
	return 0;
}

运行截图

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值