Linux 基本语句_16_Udp网络聊天室

代码:

服务端代码:

#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的可靠性保证机制。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

信号量机制理论详解专题

一文学懂信号量机制的各种大题&#xff0c;详细操作见下文~ 1965年&#xff0c;荷兰学者Dijkstra提出的信号量&#xff08;Semaphores&#xff09;机制是一种卓有成效的进程同步工具。在长期且广泛的应用中&#xff0c;信号量机制又得到了很大的发展&#xff0c;它从整型信号量…

LRU 缓存机制_题解(一道经典的数据结构算法题)

LRU 缓存机制_题解&#xff08;一道经典的数据结构算法题&#xff09; 146. LRU 缓存 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int k…

spark介绍及简单使用

简介 Spark是由加州大学伯克利分校AMPLab&#xff08;AMP实验室&#xff09;开发的开源大数据处理框架。起初&#xff0c;Hadoop MapReduce是大数据处理的主流框架&#xff0c;但其存在一些限制&#xff0c;如不适合迭代算法、高延迟等。为了解决这些问题&#xff0c;Spark在20…

jmeter如何循环运行到csv文件最后一行后停止

1、首先在线程组中设置’循环次数‘–勾选永远 2、csv数据文件设置中设置&#xff1a; 遇到文件结束符再次循环?——改为&#xff1a;False 遇到文件结束符停止线程?——改为&#xff1a;True 3、再次运行就会根据文档的行数运行数据 &#xff08;如果需要在循环控制器中&…

Git----学习Git第一步基于 Windows 10 系统和 CentOS7 系统安装 Git

查看原文 文章目录 基于 Windows 10 系统安装 Git 客户端基于 CentOS7 系统安装部署 Git 基于 Windows 10 系统安装 Git 客户端 &#xff08;1&#xff09;打开 git官网 &#xff0c;点击【windows】 &#xff08;2&#xff09;根据自己的电脑选择安装&#xff0c;目前一般w…

【Unity自动寻路】使用Navigation系统实现物体自动寻路绕开障碍物

知识点流程图 自动导航Navigation系统 我们在游戏场景中经常会有一些障碍物、墙壁、树木等等&#xff0c;如果我想要让角色或者怪物去墙的另一边&#xff0c;我直接在墙另一边点击左键&#xff0c;我希望角色自动跑过去&#xff0c;但是他不能直接穿透墙&#xff0c;他需要“智…

java8新特性之-LocalDateTime

java8新特性之-LocalDateTime 首先&#xff0c;我们必须明确&#xff0c;JAVA为什么在有Date这个类的情况下&#xff0c;又引入了LocalDateTime 大体上来说。 java8引入为了解决旧的java.util.Date和java.util.Calendar等类在处理日期和时间时存在的一些问题&#xff0c;并提供…

[python]用python获取EXCEL文件内容并保存到DBC

目录 关键词平台说明背景所需库实现过程方法1.1.安装相关库2.代码实现 关键词 python、excel、DBC、openpyxl 平台说明 项目Valuepython版本3.6 背景 在搭建自动化测试平台的时候经常会提取DBC文件中的信息并保存为excel或者其他文件格式&#xff0c;用于自动化测试。本文…

一篇文章了解Flutter Json系列化和反序列化

目录 一. 使用dart:convert实现JSON格式编解码1. 生成数据模型类2. 将JSON数据转化成数据模型类3. 数据模型类转化成JSON字符串 二、借助json_serializable实现Json编解码1.添加json_annotation、build_runner、json_serializable依赖2. 创建一个数据模型类3. 使用命令行生成JS…

http状态码(一)400报错

一 400报错汇总 ① 综述 一、4xx状态码报错说明&#xff1a; 客户端行为导致的报错二、通用的4xxHTTP报错1) 4002) 4013) 4034) 4045) 405 --> 不允许方法&#xff0c;可能跨域或者nginx限制请求方法6) 4087) 4138) 419三、ngin自身定义的4xx报错495、496、497、498、4…

centOS7 安装tailscale并启用子网路由

1、在centOS7上安装Tailscale客户端 #安装命令所在官网位置&#xff1a;https://tailscale.com/download/linux #具体命令为&#xff1a; curl -fsSL https://tailscale.com/install.sh | sh #命令执行后如下图所示2、设置允许IP转发和IP伪装。 安装后&#xff0c;您可以启动…

Python框架批量数据抓取的高级教程

一、背景介绍 批量数据抓取是一种常见的数据获取方式&#xff0c;能够帮助我们快速、高效地获取网络上的大量信息。本文将介绍如何使用Python框架进行大规模抽象数据&#xff0c;以及如何处理这个过程中可能遇到的问题。 二、项目需求 我们将爬取大量知乎文章&#xff0c;讨…

机器学习 | 机器学习基础知识

一、机器学习是什么 计算机从数据中学习规律并改善自身进行预测的过程。 二、数据集 1、最常用的公开数据集 2、结构化数据与非结构化数据 三、任务地图 1、分类任务 Classification 已知样本特征判断样本类别二分类、多分类、多标签分类 二分类&#xff1a;垃圾邮件分类、图像…

为什么选择计算机?大数据时代学习计算机的价值探讨

还记得当初自己为什么选择计算机? 计算机是在90年代兴起的专业,那时候的年轻人有驾照、懂外语、懂计算机是很时髦的事情! 当初你问我为什么选择计算机,我笑着回答:“因为我梦想成为神奇的码农!我想像编织魔法一样编写程序,创造出炫酷的虚拟世界!”谁知道,我刚入门的…

Leetcode刷题笔记题解(C++):224. 基本计算器

思路&#xff1a; step 1&#xff1a;使用栈辅助处理优先级&#xff0c;默认符号为加号。 step 2&#xff1a;遍历字符串&#xff0c;遇到数字&#xff0c;则将连续的数字字符部分转化为int型数字。 step 3&#xff1a;遇到左括号&#xff0c;则将括号后的部分送入递归&#x…

java --- 异常

目录 一、异常体系介绍 二、异常的作用 三、异常处理方式 3.1 捕获异常 2.1 灵魂一问&#xff1a; 如果try中没有遇到问题&#xff0c;如何执行&#xff1f; 2.2 灵魂二问&#xff1a;如果try中可能会遇到多个问题&#xff0c;怎么执行&#xff1f; 2.3 灵魂三问&#x…

动态规划优化技巧

一、斐波那契系列 1、滚动数组优化空间复杂度 2、dp数组初始化/处理边界优化 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台

NtripShare Mos监测平台边缘计算终端与自动优化平差算法

忙忙乎乎23年又要过去了&#xff0c;回头看今年做的事&#xff0c;只有两件事值得一提&#xff1a; 1、自动化监测边缘计算终端&#xff1b; 2、自动优化平差算法。 自动化监测边缘计算终端 终端采用全国产硬件方案终端支持全站仪供电控制终端支持远程控制终端支持数据缓存技…

WebLangChain_ChatGLM:结合 WebLangChain 和 ChatGLM3 的中文 RAG 系统

WebLangChain_ChatGLM 介绍 本文将详细介绍基于网络检索信息的检索增强生成系统&#xff0c;即 WebLangChain。通过整合 LangChain&#xff0c;成功将大型语言模型与最受欢迎的外部知识库之一——互联网紧密结合。鉴于中文社区中大型语言模型的蓬勃发展&#xff0c;有许多可供利…

arcgis更改服务注册数据库账号及密码

最近服务器数据库密码换了&#xff0c;gis服务也得换下数据库连接密码。传统官方的更改方式&#xff08;上传连接配置文件&#xff09;&#xff1a; ArcGIS Server数据库注册篇(I) — 更新数据库密码_arcgis server sde换密码-CSDN博客 方式太麻烦了&#xff0c;需要安装ArcG…