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

1. 需求

项目需求:

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

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

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

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

服务器代码实现

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

#define ERR_MSG(msg) do{\
         fprintf(stderr,"__%d__",__LINE__);\
         perror(msg);\
}while(0)
//定义数据包类型
typedef struct datapack 
{    
    char text[128] ; 
    char type; 
    char name[8]; 
}datapack;

//定义链表结构体
typedef struct node{

    union{
        int len;
        struct sockaddr_in sin;
    };
    struct node *next;
}linklist;
int sfd;//报式套接字存放处
//创建链表函数
linklist *create()
{
    linklist *p = (linklist *)malloc(sizeof(linklist));
    if(p == NULL)
    {
        printf("创建链表失败\n");
        return NULL;
    }

    //初始化头结点,防止野指针
    p->len = 0;
    p->next = NULL;

    return p;
}
//申请节点存放数据函数
linklist *apply(linklist *p, struct sockaddr_in sin)
{
    linklist *q = (linklist *)malloc(sizeof(linklist));
    if(NULL == q)
    {
        printf("申请节点失败\n");
        return NULL;
    }
    q->sin = sin;
    q->next = NULL;
    p->len++;
    linklist *q1 = p; //定义一个遍历指针将新用户的信息尾插到链表的最后一个
    while(q1->next != NULL)
    {
        q1 = q1->next;
    }
    q1->next = q;
 
    return p;
}
//删除节点函数
linklist *shanchu(linklist *p, struct sockaddr_in sin)
{
    linklist *q = p;
    linklist *q1 = NULL;
    while(q->next != NULL)
    {
        if(q->next->sin.sin_port == sin.sin_port)
        {
            q1 = q->next;
            q->next = q->next->next;
            free(q1);
            q1 = NULL;
            break;
        }
        q = q->next;
    }
}

linklist *p=NULL;

void *callBack(void *arg)//子线程执行区域
{
	datapack send;
	while(1)
	{
		bzero(&send,sizeof(send));
		fgets(send.text, sizeof(send.text), stdin);
		send.text[strlen(send.text)-1] = 0;       //把最后一位回车改成0
		send.type='C';
		strcpy(send.name,"system");
		linklist *q = p;
		while(q->next != NULL)
		{

			if(sendto(sfd,&send, sizeof(send), 0, (struct sockaddr *)&q->next->sin, sizeof(q->next->sin)) < 0)
			{
				ERR_MSG("sendto");
			}
				q = q->next;
		}
		q=p;
		if(!strcasecmp(send.text, "quit"))
			exit(0);
	}
}
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; 
	 } 
	 printf("socket create success\n");

	 struct sockaddr_in sin;
	 sin.sin_family         = AF_INET;
	 sin.sin_port           = htons(port);
	 sin.sin_addr.s_addr    = inet_addr(argv[1]); //将字符串式的点分十进制转换成网络字节序

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

	 //绑定服务器的地址信息结构体
	 if(bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
	 {
		 ERR_MSG("bind");
	 }
	 printf("绑定成功\n");
	 datapack rcv;//定义接收数据包的结构体
	 pthread_t pid;
	 p=create();
	 linklist *q=NULL;
	 if(pthread_create(&pid, NULL, callBack, NULL) != 0)//创建子线程用于发送消息
	 {
		 ERR_MSG("pthread");
		 return -1;
	 }
	 printf("创建子线程成功\n");

	 while(1)
	 {
		 bzero(&rcv,sizeof(rcv));
		 if(recvfrom(sfd,&rcv,sizeof(rcv),0,(struct sockaddr*)&rcv_addrmsg,&addrlen)<0)//接收客户端发送的数据
		 {
			 ERR_MSG("recvfrom");
			 return -1;
		 }


		 if(rcv.type=='L')//如果发送的是登录请求
		 {
			 printf("%s%s\n",rcv.name,rcv.text);
			 q=apply(p,rcv_addrmsg);//创建新节点


			 while(NULL!=q->next->next)
			 {

				 sin=q->next->sin;//取其他节点地址信息结构体给他们发送上线消息
				 if(sendto(sfd,&rcv,sizeof(rcv),0,(struct sockaddr*)&sin,sizeof(sin))<0)
				 {
					 ERR_MSG("sendto"); 
					 return -1; 
				 }
				 q=q->next;

			}
			q=p;//将q指针重新指向头结点等待重新利用
		}
		else if(rcv.type=='C')
		{
		printf("%s:%s\n",rcv.name,rcv.text);//打印出消息内容
		while(q->next!=NULL)
		{
			if(q->next->sin.sin_port != rcv_addrmsg.sin_port)//给非输入数据的端口发送消息内容
			{
				if(sendto(sfd,&rcv,sizeof(rcv),0,(struct sockaddr*)&sin,sizeof(sin))<0)
				{
					ERR_MSG("sendto"); 
					return -1; 
				}
			}
			q=q->next;
		}
		q=p;
	}
	else if(rcv.type=='Q')//接收到退出指令
	{
		printf("%s%s\n",rcv.name,"已退出");//打印出消息内容

		while(q->next!=NULL)
		{
			if(q->next->sin.sin_port != rcv_addrmsg.sin_port)//给非输入数据的端口发送消息内容
			{
				if(sendto(sfd,&rcv,sizeof(rcv),0,(struct sockaddr*)&sin,sizeof(sin))<0)
				{
					ERR_MSG("sendto"); 
					return -1; 
				}
			}
			else
			{
				shanchu(q,rcv_addrmsg);
				continue;
			}
			q=q->next;
		}
		q=p;

	}
	}
	return 0;
}

客户端代码实现

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>

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


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;
	}		

	//创建报式套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}
	printf("socket create success\n");
	
	

	//绑定客户端自身的地址信息结构体 ---> 非必须绑定 
	
	//填充服务器的IP地址以及端口号 -->因为客户端要主动发送数据包给服务器
	struct sockaddr_in sin;
	sin.sin_family 		= AF_INET;
	sin.sin_port 		= htons(port);
	sin.sin_addr.s_addr = inet_addr(argv[1]);


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

	typedef struct datapack
	{	
		char text[128] ;
		char type;
		char name[8];
	}datapack;
	datapack user;
	datapack rcv;
	printf("输入登录的用户名>>>");
	scanf("%s",user.name);
	getchar();
	printf("录入名字成功\n");

	char buf[137] = "";
	size_t size =sizeof(user);
	printf("_____\n");
	user.type='L';
	strcpy(user.text,"已上线");
	if(sendto(sfd,&user, sizeof(user), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
			ERR_MSG("sendto");
			return -1;
	}


	pid_t pid = fork();
	if (pid>0)//父进程进行发送
	{
	while(1)
	{
	
		char st;
		bzero(user.text, sizeof(user.text));
		printf("请输入>>>");//录入聊天信息内容
	//	getchar();
	//	printf("__________%d",__LINE__);
		fgets(user.text, sizeof(user.text), stdin);
		user.text[strlen(user.text)-1] = 0;
		if(strcasecmp("quit",user.text)!=0)
		{
			user.type='C';
		}
		else
		{
			user.type='Q';
		}
		//将数据包发送给服务器,所以地址信息结构体需要填服务器的信息
		if(sendto(sfd,&user, sizeof(user), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
		{
			ERR_MSG("sendto");
			return -1;
		}
		if(strcmp(user.text,"quit")==0)
		{
			printf("______");
			exit(0);
		}

		printf("sendto success\n");
	}
	}
	if(pid==0)//子进程接收
	{
		while(1)
		{
		//接收
		bzero(user.text, sizeof(user.text));
		addrlen = sizeof(sin);
		//接收服务器发送过来的数据包
		//if(recvfrom(sfd, buf, sizeof(buf), 0, NULL, NULL) < 0)

		if(recvfrom(sfd, &rcv, sizeof(rcv), 0, (struct sockaddr*)&sin, &addrlen) < 0)
		{
			ERR_MSG("recvfrom");
			return -1;
		}
		if(rcv.type=='L')
		{
		printf("%s%s\n",rcv.name,"已上线");
		
		}
		else if(rcv.type=='C')
		{
			if(0==strcasecmp(rcv.text,"quit"))
			{
				kill(getppid(),2);
				printf("服务器已退出");
				exit(0);
			}
			printf("%s:%s\n",rcv.name,rcv.text);
		}
		else if(rcv.type=='Q')
		{
			printf("%s%s\n",rcv.name,"已下线");
		}
		}
	}
	//关闭套接字
	close(sfd);
	return 0;
}

运行结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值