Linux 基本语句_16_Udp网络聊天室

news2024/11/23 12:25:36

代码:

服务端代码:

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

#define N 128
#define L 1
#define C 2
#define Q 3

typedef struct{
	int type;
	char name[N];
	char text[N];
}MSG; // 存信息 

typedef struct node{
	struct sockaddr_in addr; // 存ip 和 端口号 
	struct node *next; // 链表 
}linklist_t;

linklist_t *linklist_create(); // 创建链表函数 

void do_login(MSG msg, linklist_t *h, int sockfd, struct sockaddr_in clientaddr); // 某客端上线,将数据发送给其他在线客户端 
void do_chat(MSG msg, linklist_t *h, int sockfd, struct sockaddr_in clientaddr); // 将用户想要发送的数据广播给其他用户 
void do_quit(MSG msg, linklist_t *h, int sockfd, struct sockaddr_in clientaddr); // 在链表中删除自己的记录,并将自己退出的信息发送給其他客户端 

int main(int argc, const char *argv[]){
	int sockfd;
	struct sockaddr_in serveraddr, clientaddr; // 储存信息 
	socklen_t addrlen = sizeof(serveraddr);
	
	if(argc < 3){
		printf("argc number error\n");
		return -1;
	}
	
	/* 创建套接字 */ 
	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){ // IPV4、UDP协议、协议标志 
		printf("socket error\n");
		return -1;
	}
	
	/* 填充服务器网络信息 */ 
	serveraddr.sin_family = AF_INET; 
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));
	 
	if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) < 0){ // 套接字与服务器网络信息绑定、(套接字是中转站,bind将ip和端口信息存入中转站)
		printf("bind error\n");
		return -1;
	}
	
	MSG msg;
	
	pid_t pid;
	
	if((pid = fork()) < 0){
		printf("fork error\n");
		return -1;
	}
	else if(pid == 0){ // 子进程 
		msg.type = C;
		strcpy(msg.name, "server");
		
		while(1){
			fgets(msg.text, N, stdin); // 等待控制台输入
			msg.text[strlen(msg.text) - 1] = '\0';
			
			sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr*)&serveraddr, addrlen); // 子进程和父进程绑定在同一个ip地址 和 端口号 子进程能向父进程发送给对方 
		} 
	}
	else{ // 父进程负责接收数据并处理 
	    linklist_t *h = linklist_create();
		
		while(1){
			recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &addrlen); // 从中转站接收数据直到有数据为止 
			printf("%d -- %s -- %s\n", msg.type, msg.name, msg.text); // 打印接收的数据 
			
			switch(msg.type){ // 根据数据的类型做不同操作 
				case L:
					do_login(msg, h, sockfd, clientaddr); // 登录广播提醒 
					break;
				case C:
					do_chat(msg, h, sockfd, clientaddr); // 广播聊天 
					break;
				case Q:
					do_quit(msg, h, sockfd, clientaddr); // 广播退出 
					break;
			}
		}   
	}
	return 0; 
}

linklist_t *linklist_create(){ // 创建链表 
	linklist_t *h = (linklist_t *)malloc(sizeof(linklist_t)); // 创建一个链表节点,h为链表头部 
	h->next = NULL; // 整个链表只有一个节点 
	
	return h; 
}

void do_login(MSG msg, linklist_t *h, int sockfd, struct sockaddr_in clientaddr){
	linklist_t *p = h;
	
	/* 用户登录信息发送给其他客户 */ 
	sprintf(msg.text, "-------- %s login -------------", msg.name);
	while(p->next != NULL){
		sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&p->next->addr, sizeof(struct sockaddr_in)); // 发送那个给链表中所有对象 
		p = p->next;
	}
	
	linklist_t *temp = (linklist_t *) malloc(sizeof(linklist_t)); // 创建一个新结点 
	
	temp->addr = clientaddr; // 客户端信息存入结点 
	temp->next = h->next;
	h->next = temp; // 将temp存入链表末尾 
	
	return;
}

void do_chat(MSG msg, linklist_t *h, int sockfd, struct sockaddr_in clientaddr){
	
	char buf[N] = {};
	linklist_t *p = h;
	
	/* 将用户信息发送给其他在线的用户 */
	sprintf(buf, "%s : %s", msg.name, msg.text);
	strcpy(msg.text, buf); // 将数据存入msg 
	
	while(p->next != NULL){ // 发送数据 
		if(memcmp(&clientaddr, &p->next->addr, sizeof(clientaddr)) == 0){ // 数据是自己的就不传输了 
			p = p->next;
		} 
	    else{ // 其他人就发送 
		    sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&p->next->addr, sizeof(struct sockaddr_in)); // 发送的数据、长度、发送的位置 
		    p = p->next;
	    }
    }
	return; 
}

void do_quit(MSG msg, linklist_t *h, int sockfd, struct sockaddr_in clientaddr){
	linklist_t *p = h;
	linklist_t *temp;
	
	/* 将用户退出的信息发送给其他用户,并将其信息从链表中删除 */
	
	sprintf(msg.text, "-------- %s offline --------", msg.name);
	
	while(p->next != NULL){
		if(memcmp(&clientaddr, &p->next->addr, sizeof(clientaddr)) == 0){ // 自己的就不发送 
			temp = p->next;
			p->next = temp->next;
			free(temp); // 释放本结点 
			temp = NULL; // 指针至为空指针 
		}
		else{
			sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&p->next->addr, sizeof(struct sockaddr_in)); // 不是自己就发送 
			p = p->next;
		}
	}
	return; 
}

客户端代码:

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

#define N 128
#define L 1
#define C 2
#define Q 3

typedef struct{
	int type;
	char name[N];
	char text[N];
}MSG; // 存信息 



int main(int argc, const char *argv[]){
	int sockfd;
	struct sockaddr_in serveraddr, clientaddr; // 储存信息 
	socklen_t addrlen = sizeof(serveraddr);
	
	if(argc < 3){
		printf("argc number error\n");
		return -1;
	}
	
	/* 创建套接字 */ 
	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){ // IPV4、UDP协议、协议标志 
		printf("socket error\n");
		return -1;
	}
	
	/* 填充服务器网络信息 */ 
	serveraddr.sin_family = AF_INET; 
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));
	
	MSG msg;
	
	msg.type = L;
	
	printf("please enter your name: ");
	fgets(msg.name, N, stdin); // 将控制台输入的信息传入name中
	msg.name[strlen(msg.name) - 1] = '\0'; 
	sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, addrlen); // 将数据发送给服务端 
	
	pid_t pid;
	
	if((pid = fork()) < 0){
		printf("fork error\n");
		return -1;
	}
	else if(pid == 0){ // 子进程,发送数据 
		
		while(1){
			fgets(msg.text, N, stdin); // 等待控制台输入
			msg.text[strlen(msg.text) - 1] = '\0';
			
			if(strncmp(msg.text, "quit", 4) == 0){ // 若要退出 
				msg.type = Q; // 退出广播 
			    sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr*)&serveraddr, addrlen); // 子进程和父进程绑定在同一个ip地址 和 端口号 子进程能向父进程发送给对方
				close(sockfd);
				kill(getppid(), SIGKILL); // 退出父进程 
				return 0; 	
			} 
			
			msg.type = C; // 聊天 
			sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr*)&serveraddr, addrlen); // 子进程和父进程绑定在同一个ip地址 和 端口号 子进程能向父进程发送给对方
		} 
	}
	else{ // 父进程负责接收数据

		
		while(1){
			recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, &addrlen); // 从中转站接收数据直到有数据为止 
			printf("%s\n", msg.text); // 打印接收的数据 
			
		}   
	}
	return 0; 
}

效果:

在这里插入图片描述
总体效果是客户上线状态、退出状态、发送的消息都能通过广播,将信息发送给所有在线客户端,服务端能接收并显示所有客户端发送的消息,且也具备广播能力

原理:

服务端:

服务端创建了一个链表,这个链表中的每个节点是专用于储存客户端的ip地址和端口号等一系列信息,目的是方便遍历实现广播功能

服务端的创建了父子进程,子进程专门接收控制台发送的数据,并转发给父进程,父进程将数据广播给所有客户端,朴素的讲子进程接收控制台数据,父进程接收客户端信息并广播

客户端:

客户端也是创建父子进程,父进程负责接收服务器转发的数据,并打印。子进程负责发送,从本控制台获取的信息并发送给服务端通过服务器广播给其他客户端

服务器本质就是中转站,负责接收客户端信息状态并广播

拓展:

套接字:

套接字可以理解为网络通信的中转站,将通信双方的ip地址和端口号等相关信息存入套接字,以便通信双方能通过ip地址和端口号找到对应接收端。

服务端和客户端都创建套接字的原因:

在一个典型的客户端-服务器模型中,服务器和客户端通过套接字建立通信。一般情况下,服务器会先创建一个套接字并绑定到一个特定的 IP 地址和端口上,然后等待客户端连接。
在客户端与服务器建立连接时,客户端会创建一个新的套接字,并尝试连接到服务器的套接字地址。如果连接成功,服务器会接受这个连接并为客户端创建一个新的套接字,该套接字将用于与这个特定客户端之间的通信。
这样,服务器会保持一个主套接字用于监听客户端的连接请求,并为每个连接创建一个新的套接字来处理与特定客户端之间的通信。这些连接的套接字通常是独立的,即服务器和每个客户端之间都有一个独立的套接字,用于他们之间的通信。

UDP连接方式:

在 UDP 协议中,客户端并不需要显式地调用 bind() 来绑定一个端口。通常情况下,在客户端发送数据时,系统会自动分配一个临时的端口号,并在发送数据时使用这个端口号。这个临时端口号通常在发送后被释放,因此客户端不需要显式地绑定一个端口。
客户端在发送数据时,使用 sendto() 或者 sendmsg() 等函数向目标服务器发送数据报。在发送时,指定目标服务器的 IP 地址和端口号即可,而不需要调用 bind() 来指定客户端的本地端口。UDP是无连接的,因此客户端不需要事先建立连接,只需要在发送数据时指定目标地址和端口即可。
相反,服务器通常会先调用 bind() 来绑定一个固定的端口号,以便监听客户端发送来的数据。服务器需要绑定一个固定端口号来等待客户端的连接请求或者接收数据报。
总之,在UDP中,客户端通常不需要显式地调用 bind() 来绑定端口,它可以自动分配一个临时的端口来发送数据。服务器端则需要绑定一个固定的端口号来等待客户端的连接或接收数据。

区别:

TCP是面向连接的协议,它在通信之前需要建立连接,并确保数据传输的可靠性。它提供数据的可靠性保证、流量控制和拥塞控制。
UDP是无连接的协议,不需要在发送数据之前建立连接。它不保证数据的可靠性,也不提供类似TCP的可靠性保证机制。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1317903.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32 DAC+串口

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、DAC是什么&#xff1f;二、STM32 DAC1.什么型号有DAC2. 简介3. 主要特点4. DAC框图5. DAC 电压范围和引脚 三、程序步骤1. 开启DAC时钟2. 配置引脚 PA4 PA5…

【数学建模】《实战数学建模:例题与讲解》第十二讲-因子分析、判别分析(含Matlab代码)

【数学建模】《实战数学建模&#xff1a;例题与讲解》第十二讲-因子分析、判别分析&#xff08;含Matlab代码&#xff09; 基本概念时间判别费歇判别贝叶斯判别 习题10.31. 题目要求2.解题过程3.程序4.结果 习题10.6&#xff08;1&#xff09;1. 题目要求2.解题过程——对应分析…

Stable-Diffusion|从图片反推prompt的工具:Tagger(五)

stable-diffusion-webui-wd14-tagger 前面几篇&#xff1a; Stable-Diffusion|window10安装GPU版本的 Stable-Diffusion-WebUI遇到的一些问题&#xff08;一&#xff09; 【Stable-Diffusion|入门怎么下载与使用civitai网站的模型&#xff08;二&#xff09;】 Stable-Diffusi…

PyQt6 QDial旋钮控件

锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计46条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话版…

Linux----文件权限命令

1. chmod命令的介绍 命令说明chmod修改文件权限 chmod修改文件权限有两种方式: 字母法数字法 2. chmod 字母法的使用 角色说明: 角色说明uuser, 表示该文件的所有者ggroup, 表示用户组oother, 表示其他用户aall, 表示所有用户 权限设置说明: 操作符说明增加权限-撤销权…

性能测试之Locust(完整版)

官方文档&#xff1a;Locust说明文档 一、Locust简介 1、定义 Locust是一款易于使用的分布式负载测试工具&#xff0c;完全基于事件&#xff0c;即一个locust节点也可以在一个进程中支持数千并发用户&#xff0c;不使用回调&#xff0c;通过gevent使用轻量级过程&#xff08…

什么软件可以压缩视频大小?超级简单

什么软件可以压缩视频大小&#xff1f;当我们想将视频上传到网上时&#xff0c;有时候会遇到视频因为体积太大而无法上传的问题&#xff0c;这种情况就需要将视频进行压缩了。那什么软件可以压缩视频大小呢&#xff1f;下面小编就来为大家介绍压缩视频的方法&#xff0c;支持批…

SpringBoot+FastJson 优雅的过滤 Response Body

Spring 源码系列 1、Spring 学习之扩展点总结之后置处理器&#xff08;一&#xff09; 2、Spring 学习之扩展点总结之后置处理器&#xff08;二&#xff09; 3、Spring 学习之扩展点总结之自定义事件&#xff08;三&#xff09; 4、Spring 学习之扩展点总结之内置事件&#xf…

项目实战:自动驾驶之方向盘操纵

项目介绍 根据汽车前方摄像头捕捉的画面,控制汽车方向盘转动的方向和角度,这是自动驾驶要解决的核心问题。这个项目主要是通过使用深度神经网络解决一个回归问题。不同于分类、识别场景,回归问题中神经网络输出的是一个连续的值。 通过这个项目的学习,可以将神经网络用于通…

Flink系列之:大状态与 Checkpoint 调优

Flink系列之&#xff1a;大状态与 Checkpoint 调优 一、概述二、监控状态和 Checkpoints三、Checkpoint 调优四、RocksDB 调优五、增量 Checkpoint六、RocksDB 或 JVM 堆中的计时器七、RocksDB 内存调优八、容量规划九、压缩十、Task 本地恢复十一、主要&#xff08;分布式存储…

PADS9.5 : 原图绘图图纸尺寸下修改

原图绘图图纸尺寸下修改 图页边界线也要修改 如果二者选择不一致&#xff1a; 会出现下图所示情况&#xff1a;

Django和ECharts异步请求示例

前提条件 创建django项目&#xff0c;安装配置过程这里就不讲述了。 后端url http://127.0.0.1:8000/echarts/demo/ view视图函数 from django.http import HttpResponse import jsondef EchartsDemo(request):data {}categories ["衬衫","羊毛衫",&…

基于net6的zmq调试工具

0.前言 最近在做CS架构的上位机控制软件&#xff0c;服务端和客户端是通过zmq进行通讯的&#xff0c;网上现有的工具都是tcp、串口的调试工具&#xff0c;一直没有找到一个合适的zmq调试工具。就使用C#语言开发了这个简易的zmq调试工具&#xff0c;项目地址ZmqDebuggerTool。 …

这一篇就够了!全套SpringBoot教程02

SpringBoot运维实用篇 基础篇发布以后&#xff0c;看到了很多小伙伴在网上的留言&#xff0c;也帮助超过100位小伙伴解决了一些遇到的问题&#xff0c;并且已经发现了部分问题具有典型性&#xff0c;预计将有些问题在后面篇章的合适位置添加到本套课程中&#xff0c;作为解决方…

app分发平台哪个好点?手机app应用内测分发平台支持负载均衡的重要性

随着互联网的快速发展&#xff0c;内测分发平台扮演着越来越重要的角色。而在现代应用程序的开发和运营过程中&#xff0c;负载均衡技术是不可或缺的一部分。内测分发平台支持负载均衡对于提高系统的稳定性、可靠性和性能至关重要。那么什么是负载均衡又有哪些重要性。 图片来源…

FPGA设计时序约束十二、Set_Clock_Sense

目录 一、序言 二、Set Clock Sense 2.1 基本概念 2.2 设置界面 2.3 命令语法 2.4 命令示例 三、工程示例 3.1 工程代码 3.2 无set_clock_sense 3.3 设置set_clock_sense 四、参考资料 一、序言 本章将介绍Set_Clock_Sense约束&#xff0c;在介绍约束之前&#xff0…

【上海大学数字逻辑实验报告】七、中规模元件及综合设计

一、实验目的 掌握中规模时序元件的测试。学会在Quartus II上设计序列发生器。 二、实验原理 74LS161是四位可预置数二进制加计数器&#xff0c;采用16引脚双列直插式封装的中规模集成电路&#xff0c;其外形如下图所示&#xff1a; 其各引脚功能为&#xff1a; 异步复位输…

cmd命令bat脚本隐藏执行窗口

家里一直都有远程开3389的需要&#xff0c;一直使用的是frp. 最近发现总是经常掉线。也不清楚原因&#xff0c;后调查出来原来是由于 我命令行窗口一般启动的比较多&#xff0c;有时候就会去点一下&#xff08;选择复制内容&#xff09;&#xff0c;如下&#xff1a; 有时候…

解决:Invalid bound statement (not found): com.XXXXX.UserMapper.countUser

问题&#xff1a;Invalid bound statement (not found): com.XXXXX.UserMapper.countUser 原因&#xff1a;mapper.java和mapper.xml映射不上 解决方法&#xff1a; 1、在application.properties全局配置文件中没有加上映射mapper文件的配置,mapper为resources下的文件&…

可以给网站任意位置添加4个区块源码

在网站里添加html区块把html里的代码复制进去&#xff0c;然后把3、4、5行的CSS和JS修改成网站对应目录就行了