基于UDP的网络聊天室

news/2024/5/18 15:59:50 标签: 网络, udp, 网络协议

客户端

#include <myhead.h>
//定义存储信息结构体
typedef struct _MSG
{
	char code;  //操作码:'L'表示登录'C表示群聊'S'表示系统消息'S'表示退出
	char name[128];  
	char txt[256];

}msg_t;

//定义保存客户端网络信息的链表
typedef struct _ADDR
{
	struct sockaddr_in cin;
	struct _ADDR* next;
}addrlist_t;

//登录操作的函数
void do_login(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{
	//先遍历链表 将新用户加入群聊的消息发送给所有人
	addrlist_t* tmp = addr;  //记录链表头结点
	while(tmp->next != NULL){
		tmp = tmp->next;
		if(sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(tmp->cin),sizeof(tmp->cin)) == -1){
			perror("sendto error");
			return;
		}
	}
	//将新用户的网络信息结构体头插入链表
	addrlist_t* pnew = NULL;
	if(NULL == (pnew = (addrlist_t*)malloc(sizeof(addrlist_t)))){
		printf("malloc error\n");
		return;
	}
	pnew->cin = cin;
	pnew->next = addr->next;
	addr->next = pnew;
	printf("%s已上线\n",msg.name);
	return;
}

//群聊操作函数
void do_chat(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{
	//遍历链表,将群聊消息发给除自己以外其他人
	addrlist_t* ptmp = addr;
	while(ptmp->next != NULL){
		ptmp = ptmp->next;
		if(memcmp(&cin, &(ptmp->cin), sizeof(cin))){
			//说明不是自己就发送数据
			if(sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(ptmp->cin),sizeof(ptmp->cin)) == -1){
				perror("sendto error");
				return;
			}
		}
	}
	return;
}

//退出操作的函数
void do_quit(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{
	//遍历链表 是自己就将自己在链表中删除,不是自己就发送退出群聊的数据
	addrlist_t* ptmp = addr;
	addrlist_t* del = NULL;
	while(ptmp->next != NULL){
		if(memcmp(&(ptmp->next->cin), &cin, sizeof(cin))){
			//不是自己
			ptmp = ptmp->next;
			if((sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&(ptmp->cin),sizeof(ptmp->cin))) == -1){
				perror("sendto error");
				return;
			}
		}else{
			del = ptmp->next;
			ptmp->next = del->next;
			free(del);
			del=NULL;
		}
	}
	printf("%s已下线\n",msg.name);
	return;
}
int main(int argc, const char *argv[])
{
	if(argc != 3){   //输入ip地址及端口号,进行判断
		printf("input error\n");
		printf("usage: %s <IP> <PORT>\n",argv[0]);
		return -1;
	}

	//定义用于接收等待套接字
	int sfd;
	if((sfd = socket(AF_INET,SOCK_DGRAM,0)) == -1){
		perror("socket error");
		return -1;
	}
	printf("socket sfd success\n");

	//设置端口号快速重用
	int reuse = 1;
	if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1){
		perror("setsockopt error");
		return -1;
	}
	printf("设置端口号快速重用_%d_%s_%s_\n",__LINE__,__FILE__,__func__);
	//绑定(填充服务器信息结构体)
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port   = htons(atoi(argv[2]));
	sin.sin_addr.s_addr = inet_addr(argv[1]);
	socklen_t seraddr_len = sizeof(sin);

	if((bind(sfd, (struct sockaddr*)&sin, seraddr_len)) == -1){
		perror("bind error");
		return -1;
	}
	printf("bind success\n");
	//定义客户端网络信息结构体
	struct sockaddr_in cin;
	socklen_t cliaddr_len = sizeof(cin);
	msg_t msg;  //定义接收信息的变量msg
	
	pid_t pid;  //进程号
	pid = fork();  //创建多进程
	if(pid < 0){
		perror("fork error");
		return -1;
	}else if(pid == 0){   //子进程,用来收发数据
		//创建保存客户端信息的链表头结点
		addrlist_t* addr;
		if(NULL == (addr = (addrlist_t*)malloc(sizeof(addrlist_t)))){
			printf("malloc error\n");
			return -1;
		}
		memset(addr, 0, sizeof(addr));
		addr->next = NULL;

		while(1){  //循环收发数据
			memset(&msg,0,sizeof(msg));  //每次接收新用户数据清空
			memset(&cin,0,sizeof(cin)); 
			//接收客户端发送的消息,存放在msg中
			if((recvfrom(sfd, &msg,sizeof(msg), 0,(struct sockaddr*)&cin, &cliaddr_len)) == -1){
				perror("recvfrom error\n");
				return -1;
			}
			switch(msg.code){  //判断消息中的操作码,根据操作码执行对应操作
				case 'L':   //登录操作
					do_login(sfd,msg,addr,cin);
					break;
				case 'C':   //群聊操作
					do_chat(sfd,msg,addr,cin);
					break;
				case 'Q':   //退出操作
					do_quit(sfd,msg,addr,cin);
					break;
			}
		}
	}else{
		//父进程,用来发送系统消息
		//向子进程发送群聊消息
		strcpy(msg.name, "系统消息");
		msg.code = 'C';
		while(1){
			memset(msg.txt, 0,sizeof(msg.txt));
			fgets(msg.txt, 256,stdin);  //终端获取接收消息
			msg.txt[strlen(msg.txt)-1] = '\0';
			if((sendto(sfd,&msg,sizeof(msg),0 ,(struct sockaddr*)&sin,seraddr_len)) == -1){
				perror("sendto error");
				return -1;
			}
		}
	}
	close(sfd);

	return 0;
}

服务器

#include <myhead.h>
typedef struct _MSG
{
	char code; //操作码:'L'表示登录'C'表示群聊'S'表示系统内存出错'S'表示系统消息
	char name[128];
	char txt[256];
}msg_t;   //定义消息结构体类型

int main(int argc, const char *argv[])
{
	if(3 != argc){  //考虑用命令行传参方式输入ip地址及端口号,先进行判断
		printf("input error!\n");
		printf("usage:%s <IP> <PORT>\n", argv[0]);
		return -1;
	}

	//定义通信的套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(-1 == sfd){
		perror("sockfd error");
		return -1;
	}
	
	//定义服务器地址信息结构体
	struct sockaddr_in sin;
	memset(&sin, 0,sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port   = htons(atoi(argv[2]));
	sin.sin_addr.s_addr = inet_addr(argv[1]);
	socklen_t seraddr_len = sizeof(sin);

	msg_t msg;
	memset(&msg,0,sizeof(msg));
	//输入用户名
	printf("请输入用户名:");
	fgets(msg.name,45,stdin);
	msg.name[strlen(msg.name)-1] = '\0';
	msg.code = 'L';
	strcpy(msg.txt,"加入群聊");
	//给服务器发送登录信息
	if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,seraddr_len) == -1){
		perror("sendto error");
		return -1;
	}

	//定义父子进程并创建
	pid_t pid = 0;
	pid = fork();
	if(pid < 0){
		printf("fork error\n");
		return -1;
	}else if(pid == 0){  //子进程,循环接收并打印接收的数据
		while(1){
			if(recvfrom(sfd,&msg,sizeof(msg),0,NULL,NULL) == -1){
				perror("sendto error");
				return -1;
			}
			//打印收到的数据
			printf("[%s]:%s\n",msg.name, msg.txt);
		}
	}else{   //父进程循环接收终端数据并发送给客户端
		while(1){
			memset(msg.txt,0,sizeof(msg.txt));
			fgets(msg.txt,128,stdin);  //终端获取聊天消息
			msg.txt[strlen(msg.txt)-1] = '\0';
			if(strcmp(msg.txt, "quit") == 0){
				msg.code = 'Q';
				strcpy(msg.txt, "退出群聊");
			}else{
				msg.code = 'C';
			}
			if(sendto(sfd,&msg,sizeof(msg), 0,(struct sockaddr*)&sin,seraddr_len) == -1){
				perror("sendto error");
				return -1;
			}
			if(strcmp(msg.txt, "退出群聊") == 0){
				break;
			}
		}
		//杀死子进程
		kill(pid,SIGKILL);
		wait(NULL);  //等待回收子进程资源
	}
	close(sfd);
	return 0;
}


http://www.niftyadmin.cn/n/5227285.html

相关文章

【刷题笔记】长度最小的子数组||二分查找||边界||数组

长度最小的子数组 1 题目描述 https://leetcode.cn/problems/minimum-size-subarray-sum/ 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, …, numsr-1, numsr] &#xff0c;并返回…

怎么检测电脑电源?电脑电源检测系统软件如何助力?

电源是电脑的重要组成部分&#xff0c;为电脑提供稳定电源&#xff0c;保证电脑正常工作。但是在电脑实际使用过程中总会遇到各种各样的问题和故障&#xff0c;比如无法开机&#xff0c;因此电脑电源检测是非常重要的测试内容。 如何测试电脑电源? 1. 用万用表检测 a. 将万用表…

面向对象设计模式入门知识

设计模式 面向对象设计原则 依赖倒置原则&#xff08;DIP&#xff09; 高层模板(稳定)不应该依赖于低层模板(变化)&#xff0c; 二者都应该依赖抽象(稳定)抽象(稳定)不应该依赖于实现细节(变化)&#xff0c;实现细节应该依赖抽象(稳定) 开放封闭原则(OCP) 对扩展开放&…

hyper-V操作虚拟机ubuntu 22.03

安装hyper-V 点击卸载程序 都勾选上即可 新建虚拟机&#xff0c;选择镜像文件 选择第一代即可 设置内存 配置网络 双击 启动安装虚拟机 输入用户名 zenglg 密码&#xff1a;LuoShuwen123456 按照enter键选中openssh安装 安装中 安装完成 选择重启 输入用户名、密码

思维导图软件MindNode 5 mac使用场景

MindNode 5 for Mac是一款思维导图软件产品&#xff0c;为用户在灵感启发、思绪整理、记忆协助、项目规划、授课讲演等诸多场景下提升学习和工作效率。通过导图社区和云文件无缝链接用户设备&#xff0c;方便用户随时随地收集灵感和展示文档。 MindNode 5 for Mac应用场景 助力…

2023-简单点-picamera2中的取消auto focus,进行手动焦距设定

Auto Focus 什么是auto focus&#xff1f;简介picamera2支持的 Auto Focus stateAutoManual手动模式下的重要参数 lens position未完待续。。Af Wondows参数 什么是auto focus&#xff1f;简介 一个像素有两个 前置并列透镜阵列&#xff0c;左右可以纠正相位差 参考 picamera…

【洛谷 B2017】打印 ASCII 码 题解(顺序结构+输入输出)

打印 ASCII 码 题目描述 输入一个除空格以外的可见字符&#xff0c;输出其 ASCII 码。 输入格式 一个除空格以外的可见字符。 输出格式 一个十进制整数&#xff0c;即该字符的 ASCII 码。 样例 #1 样例输入 #1 A样例输出 #1 65思路 使用了C的iostream和cstdio库&…

轻型载重汽车转向前桥总成系统毕业设计机械设计

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;前桥 获取完整说明报告工程源文件 绪论 1.1 轻型载重汽车转向桥的设计意义 汽车是现代交通工具中用得最多&#xff0c;最普遍&#xff0c;也是最方便的交通运输工具。汽车转向系是汽车上的一个重要系统,它是汽车转向运动…