Day7(网络编程)

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

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

服务器代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

struct Cli_Msg{
	char type; //定义一个类型,发送不同的类型给服务器,由服务器分别接收
	char name[20]; 	//用户名
	char context[128]; 	//传输的信息
};

//定义链表存储多个客户端网络信息
typedef struct node{
	//数据域
	union{
		struct sockaddr_in cin; 	//普通节点存放信息
		int len; 	//头结点长度
	};
	//指针域
	struct node *next;
}*LinkList, Node;

#define PORT 45678
#define IP "127.0.0.1"

LinkList Creat_Head();
LinkList Creat_Nod();
int do_login(struct sockaddr_in cin, LinkList L, int sfd, struct Cli_Msg SndBuf);
int do_chat(struct sockaddr_in cin, LinkList L, int sfd, struct Cli_Msg SndBuf);
int do_quit(struct sockaddr_in cin, LinkList L, int sfd, struct Cli_Msg SndBuf);

int main(int argc, const char *argv[])
{
	//创建套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sfd < 0)
	{
		perror("socket");
	}
	printf("创建sfd=%d\n", sfd);
	//填充结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);
	sin.sin_addr.s_addr = inet_addr(IP);
	//绑定信息
	if(bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
	{
		perror("bind");
		return -1;
	}

	//创建结构体保存客户端网络信息
	struct sockaddr_in cin;
	socklen_t addrlen = sizeof(cin);
	//创建变量
	ssize_t res = -1;
	//接收结构体
	struct Cli_Msg SndBuf;

	//分进程
	pid_t cpid = fork();
	if(cpid < 0)
	{
		perror("fork");
		return -1;
	}
	else if(0 == cpid) 	//子进程接收消息
	{
		//创建l用来保存客户端网络信息结构体的链表
		LinkList L = Creat_Head();
		if(NULL == L)
		{
			printf("创建头结点失败\n");
			return -1;
		}
		while(1)
		{
			//接收数据
			res = recvfrom(sfd, &SndBuf, sizeof(SndBuf), 0, (struct sockaddr*)&cin, &addrlen);
			if(res < 0)
			{
				perror("recvfrom");
			}
			printf("[%s:%d] 内容:[%s]\n", inet_ntoa(cin.sin_addr), \
					ntohs(cin.sin_port), SndBuf.context);
			//根据包的不同进行分类
			switch(SndBuf.type)
			{
				//登录包
			case 'L':
				do_login(cin, L, sfd, SndBuf);
				break;

				//群聊包
			case 'C':
				do_chat(cin, L, sfd, SndBuf);
				break;

				//下线包
			case 'Q':
				do_quit(cin, L, sfd, SndBuf);
					break;

					//	default:

				}
			}
	}
	else 	//主进程发送消息
	{
		strcpy(SndBuf.name, "系统广播");
		SndBuf.type = 'C';
		while(1)
		{
			fgets(SndBuf.context,  sizeof(SndBuf.context), stdin);
			SndBuf.context[strlen(SndBuf.context)-1] = '\0';

			if(sendto(sfd, &SndBuf, sizeof(SndBuf), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
			{
				perror("sendto");
				return -1;
			}
		}

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

//创建头结点
LinkList Creat_Head()
{
	LinkList L = (LinkList)malloc(sizeof(struct node));
	if(L == NULL)
		return NULL;

	L->len = 0;
	L->next = NULL;
	return L;
}

//创建普通节点
LinkList Creat_Node()
{
	LinkList P  = (LinkList)malloc(sizeof(struct node));
	if(P == NULL)
		return NULL;

	P->next = NULL;
	return P;
}

//收到登录包后的处理函数
int do_login(struct sockaddr_in cin, LinkList L, int sfd, struct Cli_Msg SndBuf)
{
	//先遍历链表,将登录消息发给所有人
	LinkList Ltemp = L;
	while(Ltemp->next != NULL)
	{
		Ltemp = Ltemp->next;
		if(-1 == sendto(sfd, &SndBuf, sizeof(SndBuf), 0,\
					(struct sockaddr *)&(Ltemp->cin), sizeof(Ltemp->cin)))
		{
			perror("sendto");
			return -1;
		}
		printf("登录消息发送客户端成功\n");
	}
	//将新登录的用户插入到链表中
	LinkList p = Creat_Node();
	p->cin = cin;
	p->next = L->next;
	L->next = p;

	return 0;	
}

//群聊包
int do_chat(struct sockaddr_in cin, LinkList L, int sfd, struct Cli_Msg SndBuf)
{
	printf("群聊包解析成功\n");
	//将聊天信息发送给除了自己以外的所有人
	LinkList Ltemp = L;
	while(Ltemp->next != NULL)
	{
		Ltemp = Ltemp->next;
		if((&Ltemp->cin.sin_port - &cin.sin_port) != 0)
		{
			if(-1 == sendto(sfd, &SndBuf, sizeof(SndBuf), 0, (struct sockaddr*)&(Ltemp->cin), sizeof(Ltemp->cin)))
			{
				perror("sendto");
				return -1;
			}
			printf("群聊信息发送成功 __%d__\n", __LINE__);
		}
	}
	return 0;
}

//退出信息发送给全部人
int do_quit(struct sockaddr_in cin, LinkList L, int sfd, struct Cli_Msg SndBuf)
{
	//发送退出消息,在链表中删除发送退出消息的
	LinkList Ltemp = L;
	while(Ltemp->next != NULL)
	{
		//判断是否是发送退出消息的客户端
		if(&Ltemp->next->cin.sin_port, &cin.sin_port != 0)
		{//如果不是发送消息
			Ltemp = Ltemp->next;
			if(-1 == sendto(sfd, &SndBuf, sizeof(SndBuf), 0, (struct sockaddr*)&(Ltemp->cin), sizeof(Ltemp->cin)))
			{
				perror("sendto");
				return -1;
			}
			printf("群聊信息发送成功 __%d__\n", __LINE__);
		}
		//如果是发送退出消息的客户端
		else
		{
			//从链表中删除信息
			LinkList p = Ltemp->next;
			Ltemp->next = p->next;
			free(p);
			p = NULL;
		}
	}
	return 0;
}

客户端代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

struct Cli_Msg{
	char type; //操作码 L登录 C群聊 Q退出
	char name[20]; 	//用户名
	char context[128]; 	//传输的信息
};

#define PORT 45678
#define IP "127.0.0.1"

int main(int argc, const char *argv[])
{
	//创建套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sfd < 0)
	{
		perror("socket");
	}
	//填充结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);
	sin.sin_addr.s_addr = inet_addr(IP);

	//发送名字
	struct Cli_Msg SndBuf;
	printf("请输入用户名>>>");
	scanf("%s", SndBuf.name);
	getchar();

	//发送登录包
	SndBuf.type = 'L';
	strcpy(SndBuf.context, "加入群聊");
	if(sendto(sfd, &SndBuf, sizeof(SndBuf), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
		perror("sendto");
		return -1;
	}
	//fork创建子进程
	pid_t cpid = fork();
	if(cpid < 0)
	{
		perror("fork");
		return -1;
	}
	//子进程接收消息
	else if(0 == cpid)
	{
		while(1)
		{
			if(recvfrom(sfd, &SndBuf, sizeof(SndBuf), 0, NULL, NULL) < 0)
			{
				perror("recvfrom");
				return -1;
			}
			printf("[%s] 发送消息:[%s]\n", SndBuf.name, SndBuf.context);

		}
	}
	//主进程发送消息
	else
	{
		while(1)
		{
			SndBuf.type = 'C'; 
		//	printf("用户[%s] 发送消息:[%s]\n", SndBuf.name, SndBuf.context);
			fgets(SndBuf.context, sizeof(SndBuf.context), stdin);
			SndBuf.context[strlen(SndBuf.context)-1] = '\0';

			//判断数据是否退出
			if(!strcmp(SndBuf.context, "quit"))
			{
				SndBuf.type = 'Q';
				strcpy(SndBuf.context, "退出群聊");
				if(sendto(sfd, &SndBuf, sizeof(SndBuf), 0, (struct sockaddr *)&sin, sizeof(sin)) < 0)
				{
					perror("sendto");
					return -1;
				}

				break;
			}

			if(sendto(sfd, &SndBuf, sizeof(SndBuf), 0, (struct sockaddr *)&sin, sizeof(sin)) < 0)
			{
				perror("sendto");
				return -1;
			}

			if(!strcmp(SndBuf.context, "退出群聊"))
			{
				break;
			}
		}
		//先让子进程自杀
		kill(cpid, SIGKILL);

		wait(NULL);
		close(sfd);
	}

	return 0;
}

输出结果:

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值