基于UDP的网络聊天室

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

程序流程图示意图

在这里插入图片描述

服务器端

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

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

#define PORT 6667               //1024~49151
#define IP  "192.168.31.115"    //IP地址,本机IP ifconfig 

struct cli_msg  //协议
{
	char type; 		//L C Q
	char name[20];
	char text[128];
};

//创建链表存储客户端信息
typedef struct node
{
	//数据域
	union{
		int len;//头节点的数据域:链表长度
		struct sockaddr_in cin; //链表存储多个客户端信息,并不是协议数据
	};
	//指针域,存储下一个节点的地址
	struct node *next;
}*LinkList, Node;



//创建链表
LinkList LinklistCreateHead()
{
	LinkList L=(LinkList)malloc(sizeof(Node));
	if(L==NULL)
		return NULL;
	//节点创建成功
	L->len=0; //链表长度为0,链表为空
	L->next=NULL;//链表指针域为空
	return L;
}

//创建普通节点
LinkList LinklistCreateNode()
{
	LinkList p=(LinkList)malloc(sizeof(Node));
	if(p==NULL)
		return NULL;
	//创建成功
	p->next=NULL;//表示创建新节点指针域为空
	return p;
}


int do_login(struct cli_msg rcvbuf, int sfd, struct sockaddr_in cin, LinkList L)
{
	//转发登录信息  (先遍历链表,再将新加入的用户信息发送给所有人)
	LinkList p_temp = L;
	for(int i = 0; i < L->len; i++)
	{
		p_temp = p_temp->next;
		sendto(sfd, &rcvbuf, sizeof(rcvbuf), 0, (struct sockaddr*)&(p_temp->cin), sizeof(p_temp->cin));
		//printf("name = =%s\n",rcvbuf.name);
	}

	//将当前登录的用户的地址信息结构体存储起来
	LinkList p = LinklistCreateNode();
	p->cin = cin;  //新节点的数据域赋值
	//指针域的指向
	p->next=L->next;
	L->next=p;
	L->len++;

	return 0;
}

int do_chat(LinkList L, int sfd, struct cli_msg rcvbuf, struct sockaddr_in cin)
{
	/*
	LinkList p_temp = L;
	for(int i = 0; i < L->len; i++)
	{
		p_temp = p_temp->next;
		sendto(sfd, &rcvbuf, sizeof(rcvbuf), 0, (struct sockaddr*)&(p_temp->cin), sizeof(p_temp->cin));
	}
	*/

	//转发群聊信息,将群聊消息发送给其他客户端
	LinkList p_temp = L;

	while(p_temp->next != NULL)
	{
		p_temp = p_temp->next;

		if(memcmp(&(cin), &(p_temp->cin), sizeof(cin)))
		{
			//printf("==========%d\n", __LINE__);
			sendto(sfd, &rcvbuf, sizeof(rcvbuf), 0, (struct sockaddr*)&(p_temp->cin), sizeof(p_temp->cin));
			//printf("行号:%d :[ %s ] : %s\n",__LINE__, rcvbuf.name, rcvbuf.text);
		}
	}

	
	return 0;
}

int do_quit(LinkList L, int sfd, struct cli_msg rcvbuf, struct sockaddr_in cin)
{
	//转发下线信息
	LinkList p_temp = L;
	while(p_temp->next != NULL)
	{
		if(memcmp(&(p_temp->next->cin), &cin, sizeof(cin)))
		{
			p_temp = p_temp->next;
			sendto(sfd, &rcvbuf, sizeof(rcvbuf), 0, (struct sockaddr*)&(p_temp->cin), sizeof(p_temp->cin));
			//printf("行号:%d :[ %s ] : %s\n",__LINE__, rcvbuf.name, rcvbuf.text);
		}
		else	//将当前下线客户端的地址信息结构体从存储位置删除
		{
			LinkList temp = p_temp->next;
			p_temp->next = temp->next;
			free(temp);
			temp = NULL;
		}
	}

	return 0;
}


int main(int argc, const char *argv[])
{
	//创建报式套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}
	printf("socket create success  sfd=%d __%d__\n", sfd, __LINE__);

	//填充服务器自身的地址信息结构体 AF_INET : man 7 IP
	struct sockaddr_in sin;
	sin.sin_family 		= AF_INET; 	//必须填AF_INET;
	sin.sin_port 		= htons(PORT); 	//服务器绑定的端口,网络字节序
	sin.sin_addr.s_addr = inet_addr(IP); 	//服务器绑定的IP,本机IP ifconfig

	//绑定--->必须绑定
	if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
		ERR_MSG("bind");
		return -1;
	}
	printf("bind success __%d__\n", __LINE__);

	char buf[128] = "";
	ssize_t res = 0;

	struct sockaddr_in cin; 	//存储数据包从谁哪里过来
	socklen_t addrlen = sizeof(cin);
	LinkList L = LinklistCreateHead();  //创建链表头结点,存储客户端信息

	struct cli_msg rcvbuf;   //接收数据的结构体存储位置 [存储在结构体]

	pid_t cpid = fork();  //创建子进程

	if(cpid > 0)  //父进程
	{
		//接收数据
		while(1)
		{
			res = recvfrom(sfd, &rcvbuf, sizeof(rcvbuf), 0, (struct sockaddr*)&cin, &addrlen);
			printf(" 行号: %d  %c [%s] %s\n",__LINE__, rcvbuf.type, rcvbuf.name, rcvbuf.text);
			if(res < 0)
			{
				ERR_MSG("recvfrom");
				return -1;
			}
			switch(rcvbuf.type)
			{
			case 'L': 	
				do_login(rcvbuf, sfd, cin, L);
				break;
			case 'C':
				do_chat(L, sfd, rcvbuf, cin);
				break;
			case 'Q':
				do_quit(L, sfd, rcvbuf, cin);
				break;
			default:
				printf("协议 %c 错误 __%s__ __%d__\n", rcvbuf.type, __FILE__, __LINE__);
			}

		}
	}
	else if(0 == cpid)  //子进程
	{
		//服务器可以发送系统消息,另,把接收到的消息发送给自己,相当于这也是个客户端
		rcvbuf.type = 'C';
		strcpy(rcvbuf.name, "系统消息");
		//发送数据
		while(1)
		{
			//printf("%d----\n", __LINE__);
			//bzero(rcvbuf.text, sizeof(rcvbuf.text));
			memset(rcvbuf.text, 0, sizeof(rcvbuf.text));
			//printf("%d----\n", __LINE__);

			fgets(rcvbuf.text, sizeof(rcvbuf.text), stdin);
			//printf("%d----\n", __LINE__);

			rcvbuf.text[strlen(rcvbuf.text) - 1] = 0;
			//printf("%d----\n", __LINE__);

			sendto(sfd, &rcvbuf, sizeof(rcvbuf), 0, (struct sockaddr*)&sin, sizeof(sin));	
			//printf("%d----\n", __LINE__);

		}
	}
	else 
	{
		ERR_MSG("fork");
		return -1;
	}

	//关闭所有文件描述符
	close(sfd);
	return 0;
}

客户端

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

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

#define PORT 6667               //1024~49151
#define IP  "192.168.31.115"    //IP地址,本机IP ifconfig 

struct cli_msg  //协议
{
    char type;      //L C Q
    char name[20];
    char text[128];
};


int main(int argc, const char *argv[])
{
	//创建报式套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}
	printf("socket create success  sfd=%d __%d__\n", sfd, __LINE__);

	//绑定--->非必须绑定
	//如果不绑定,操作系统会自动绑定本机IP,从49152~65535范围内随机一个未被使用的端口号
	
	//填充服务器的地址信息结构体
	//供给下面的sendto使用,
	struct sockaddr_in sin;
	sin.sin_family 		= AF_INET; 	//必须填AF_INET;
	sin.sin_port 		= htons(PORT); 	//服务器绑定的端口,网络字节序
	sin.sin_addr.s_addr = inet_addr(IP); 	//服务器绑定的IP,本机IP ifconfig 

	printf("请输入名字>>>");
	char name[20] = "";
	scanf("%s", name);
	getchar();

	//登录时的协议
	struct cli_msg sndbuf;
	sndbuf.type = 'L';
	strcpy(sndbuf.name, name);
	strcpy(sndbuf.text, "加入群聊");
	
	//发送登录请求
	if(sendto(sfd, &sndbuf, sizeof(sndbuf), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
		ERR_MSG("sendto");
		return -1;
	}
	//printf("发送登录请求成功\n");

	char buf[128] = "";
	ssize_t res = 0;

	struct sockaddr_in rcvAddr; 	//存储获取到的数据包是从谁那里来的
	socklen_t addrlen = sizeof(rcvAddr);
	struct cli_msg rcvbuf;  //存储接收和发送的数据协议(结构体)

	pid_t cpid = fork();  //创建子进程

	if(cpid > 0)  //父进程
	{
		//发送数据,发送聊天信息
		while(1)
		{
			bzero(buf, sizeof(buf));
			fgets(buf, sizeof(buf), stdin);
			buf[strlen(buf)-1] = 0;

			sndbuf.type = 'C';
			strcpy(sndbuf.text, buf);

			if(strcmp(sndbuf.text, "quit") == 0)
			{
				sndbuf.type = 'Q';
				strcpy(sndbuf.text, "退出群聊");
			}

			sendto(sfd, &sndbuf, sizeof(sndbuf), 0, (struct sockaddr*)&sin, sizeof(sin));

			if(strcmp(sndbuf.text, "退出群聊") == 0)
			{
				break;
			}
			//printf("聊天信息发送成功\n");
			//printf("客户端发送数据:[%s]:%s\n", sndbuf.name, sndbuf.text);
		}
		//父进程退出,此时子进程需不需要在接收数据,也需要退出子进程
		kill(cpid, SIGKILL);
		wait(NULL);//等待回收子进程资源
	}
	else if(0 == cpid)  //子进程
	{
		//接收数据 (已经可以成功接收登录数据)
		while(1)
		{
			//memset(&rcvbuf, 0, sizeof(rcvbuf));
			//recvfrom(sfd, &rcvbuf, sizeof(rcvbuf), 0, (struct sockaddr*)&rcvAddr, &addrlen);
			printf("\n");
			//fflush(stdout);
			recvfrom(sfd, &rcvbuf, sizeof(rcvbuf), 0, NULL, NULL);
			printf("[%s] : %s", rcvbuf.name, rcvbuf.text);
		}
	}
	else
	{
		ERR_MSG("fork");
		return -1;
	}
	//关闭文件描述符
	close(sfd);

	return 0;
}

结果示意

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值