Linux 基本语句_16_Udp网络聊天室

news/2024/5/18 15:13:55 标签: 网络, linux, 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的可靠性保证机制。


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

相关文章

机器学习算法---异常检测

类别内容导航机器学习机器学习算法应用场景与评价指标机器学习算法—分类机器学习算法—回归机器学习算法—聚类机器学习算法—异常检测机器学习算法—时间序列数据可视化数据可视化—折线图数据可视化—箱线图数据可视化—柱状图数据可视化—饼图、环形图、雷达图统计学检验箱…

电脑上怎么录屏?教你3种方法,轻松搞定!

在数字时代&#xff0c;电脑录屏已经成为各行各业的普遍需求&#xff0c;无论是制作教学视频、记录操作步骤&#xff0c;还是制作工作报告。可是很多人不知道电脑上怎么录屏&#xff0c;本文将详细介绍电脑上三种常见的录屏方法&#xff0c;通过这些工具&#xff0c;您可以轻松…

使用libaom处理av1编码教程

使用libaom处理av1编码教程 文章目录 使用libaom处理av1编码教程一. av1 是什么二. av1 用处三. libaom 是什么四. libaom 安装五. libaom 安装完成六. 解码av1 一. av1 是什么 AV1&#xff08;AOMedia Video 1&#xff09;是一种 开源视频编码格式 。它由开放媒体联盟 (AOM) …

恐怖题材黑马大作,艾尔莎B760M-E D5和你玩转《心灵杀手2》

说起恐怖题材的游戏&#xff0c;相信不少朋友都会第一时间想到《生化危机》、《寂静岭》、《死亡空间》等经典系列与作品。而在最近这几年&#xff0c;恐怖题材游戏也有不少黑马出现&#xff0c;比如最近推出的《心灵杀手2》就是2010年《心灵杀手》的续作&#xff0c;它是由开发…

Crow:Middlewares 庖丁解牛2 产生序列

include\crow\utility.h内有这样一段比较晦涩难懂的代码: template<class T> using Invoke = typename T::type;template<unsigned...> struct seq {using type = seq; };template<class S1, class S2> struct concat;template<unsigned... I1, unsigne…

B01、JVM与Java体系结构-01

字节码与多语言混合编程 字节码概述&#xff1a; 我们平时说的java字节码&#xff0c;指的是用java语言编译成的字节码。准确的说任何能在jvm平台上执行的字节码格式都是一样的。所以应该统称为&#xff1a;jvm字节码。不同的编译器&#xff0c;可以编译出相同的字节码文件&…

ukui-kwin-x11占用cpu 100%

1.问题描述 在某些机器上偶尔看到进程ukui-kwin-x11占用cpu使用率100%的情况。 2.期望 在任何机器上不允许看到进程ukui-kwin-x11占用cpu使用率100%的情况。 &#xff13;.问题分析定位 Ukui-kwin最大的一个特点就是支持窗口模糊等等特效&#xff0c;而也就是这个模糊特效…

【电路笔记】-电容器特性

电容器特性 文章目录 电容器特性1、概述2、标称电容 (C)3、工作电压&#xff08;WV&#xff09;4、公差&#xff08;%&#xff09;5、漏电流6、工作温度&#xff08;T&#xff09;7、温度系数&#xff08;TC&#xff09;8、极化9、等效串联电阻 (ESR) 电容器的特性决定了其温度…