广播组播、本地套接字通信、wireshark、以太网帧格式、三次握手四次挥手

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

广播(使用 UDP 套接字)

广播地址:主机号最大的地址。
广播:给所在局域网的所有主机发送数据报。(之前的数据报发送方式是单播。)

以下情况中使用广播: 局域网 搜索协议。
比如家中的智能产品, 使用手机可以搜索出附近的智能产品,这就是一个局域网搜索协议。

基于 setsockopt 实现广播

广播发送者(客户端):

1、创建一个数据报套接字;

int sockfd = sock(AF_INET, SOCK_DGRAM, 0); 

2、setsockopt(sockfd, 协议层, 选项名, 数据类型, 大小);

int opt = 1;	// 非0即可 
setsockopt(sockfd, **SOL_SOCKET**, SO_BROADCAST, &opt, sizeof(op)); 

在这里插入图片描述

3、填充结构体;

struct sockaddr_in addr; 
addr.sin_family = AF_INET; 
addr.sin_addr.s_addr = inet_addr(argv[1]); 
addr,sin_port = htons(atoi(argv[2])); 

4、发送数据报;

sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&addr, sizeof(addr));

广播接收者(服务器):

1、创建一个数据报套接字;

int sockfd = socket(AF_INET, SOCK_DGRAM, 0); 

2、绑定广播IP;

struct sockaddr_in saddr, caddr; 
saddr.sin_family = AF_INET; 
saddr.sin_port = htons(atoi(argv[2])); 
saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); 	// 广播地址 或 0.0.0.0 
socklen_t length = sizeof(caddr); 
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){...} 

3、等待接收数据;

recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&caddr, &length); 

缺点:

广播发送给所有主机,过多的的网络会发送大量的网络带宽,造成广播风暴。
广播风暴: 网络长时间被大量数据包占用,无法通信,网络会变得缓慢,甚至崩溃。

机制: 通过向广播地址发送UDP数据包,将数据包发送给网络中的所有主机。当一个主机发送广播消息时,该消息会被路由器转发到网络中的所有子网。然后,每个子网上的主机都会接收到该广播消息,并转发给它们的相邻主机。这个过程会一直持续下去,直到广播消息传播到整个网络中的所有主机,如果这个现象一直循环如此,则会造成广播风暴。

广播:给发送者设定相应的权限;
组播:接收者要加入到多播组;

组播(使用 UDP 套接字)

组播地址(D类IP):224.0.0.1 ~ 239.255.255.255

基于 setsockopt 实现组播

// 多播结构体
struct ip_mreq{
    struct  in_addr  imr_multiaddr;   // 指定多播组IP 
    struct  in_addr  imr_interface;   // 本地IP,通常指定为 INADDR_ANY--0.0.0.0
}

struct in_addr{
	_be32  s_addr;  					// IP地址(大端)
}

组播接收者(服务器):

1、创建数据报套接字;

int sockfd = sock(AF_INET, SOCK_DGRAM, 0); 

2、加入多播组(仅限于接收者);

// 核心代码 ------------------------------------
struct ip_mreq mreq;			// 定义组播的结构体变量
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(argv[1]); 			// 填充多播组IP
mreq.imr_interface.s_addr = inet_addr("0.0.0.0");  			// 自动获取本机IP

// 改变套接字属性
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));

在这里插入图片描述

3、填充结构体,绑定 组播IP 和 端口;

struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET; 
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); 			// 组IP
socklen_t length = sizeof(caddr);

if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){...}

4、等待接收数据;

recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&caddr, &length); 

组播发送者(客户端):

1、创建数据报套接字
2、指定接收方地址为 组播地址,设置端口信息
3、发送数据报

广播和组播的区别:

● 广播方式:将数据报发给所有的主机。过多的广播会占用大量网络带宽,造成广播风暴,影响通信。
● 组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。
● 组播方式既可以发给多个主机,又能避免像广播那样带来过多的负载。

本地套接字通信

· unix 网络编程:最开始都是一台主机内进程和进程之间的编程。(本地通信)
· socket:可以用于本地间进程通信,创建套接字时使用本地协议 AF_LOCALAF_UNIX

特点:

本地通信不需要IP和端口,无法进行两个主机通信;
分为流式套接字和数据报套接字; // 可以使用 流式套接字 或者 数据包套接字
和其他进程间通信相比,使用方便、效率更高,常用于前、后台进程通信;
unix 域套接字编程,实现本间进程的通信,依赖的是 s 类型的文件;

核心步骤:

#include <sys/socket.h>
#include <sys/un.h>

struct sockaddr_un {
    sa_family_t sun_family;               	/* 本地协议 AF_UNIX */
    char        sun_path[UNIX_PATH_MAX];  	/* 本地路径 s类型的套接字文件 */
};

unix socket = socket(AF_UNIX, type, 0); 	// type 可以为流式套接字或数据包套接字
                                            // unix 写为 int 就可以

struct sockaddr_un myaddr;
myaddr.sun_family = AF_UNIX; 				// 填充 UNIX 域套接字
strcpy(saddr.sun_path,"./myunix"); 			// 创建套接字的路径

相关代码

receiver
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <poll.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    // 1.创建套接字
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("sock is err:");
        return -1;
    }

    system("rm ./myunix -f");
    unlink("./myunix");

    // 2. 填充结构体
    struct sockaddr_un saddr;
    saddr.sun_family = AF_UNIX;
    strcpy(saddr.sun_path, "./myunix");

    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){
        perror("bind is err:");    
        return -1;
    }

    if (listen(sockfd,5) < 0){
        perror("listen is err:");  
        return -1;
    }

    int acceptfd = accept(sockfd, NULL, NULL);
    if (acceptfd < 0){
        perror("acceptfd < 0");    
        return -1;
    }

    char buf[128] = "";
    int recvbyte;
    while(1){
        recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
        if (recvbyte < 0){
            perror("recv is err:");   
            return -1;
        }
        else if (recvbyte == 0){
            printf("exit\n");
            break;
        }
        else{
            printf("buf: %s\n", buf);
        }
    }
    
    close(sockfd);
    close(acceptfd);
    return 0;
}
sender
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <poll.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    // 1.创建套接字
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd < 0){
        perror("sock is err:");
        return -1;
    }

    // 2. 填充结构体
    struct sockaddr_un saddr;
    saddr.sun_family = AF_UNIX;
    strcpy(saddr.sun_path, "./myunix");

    if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){
        perror("connect is err:");
        return -1;
    }

    char buf[128] = "";
    int recvbyte;
    while(1){
       	fgets(buf,sizeof(buf),stdin);
    	if(buf[strlen(buf) -1] == '\n')
            buf[strlen(buf) -1] = '\0';

      	send(sockfd, buf, sizeof(buf), 0);
    }

    close(sockfd);
    return 0;
}

网络头协议分析

在这里插入图片描述

wireshark抓包工具

在这里插入图片描述

抓包工具的使用

虚拟机: sudo apt-get install wireshark
windows: 小飞机
(抓包的过程,就是抓取流经网卡的数据。如果不加 sudo 就找不到网卡,没有办法抓到数据。)
两台不同的主机通信 或 两台不同的操作系统(windows、linux)之间 才可以进行抓包。

步骤

1)运行 linux 下的服务器;
在这里插入图片描述

2)打开 windows 下的小飞机;
在这里插入图片描述
在这里插入图片描述

3)打开抓包工具;
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4)过滤无关的包;
在这里插入图片描述

5)小飞机模拟客户端, 与 linux 下的服务器通信;

实现:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

包头分析在这里插入图片描述

以太网的完整帧格式

在这里插入图片描述

网络层最大数据帧长度是1500字节。(MTU: 最大传输单元)
链路层最大数据长度是1518字节。(网络层 1500 + 以太网 14 + CRC检错 4)
在这里插入图片描述
在这里插入图片描述

TCP 粘包、拆包 与 UDP丢包

● 发生 TCP 粘包或拆包 有很多原因,常见的有以下几点:
1、待发送数据大于 TCP 发送缓冲区剩余空间大小,将会发生拆包。
2、待发送数据大于 MSS(最大报文长度),TCP 在传输前将进行拆包。
3、待发送数据小于 TCP 发送缓冲区的大小,TCP 将多次写入缓冲区的数据一次发送出去,将会发生粘包。
4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
● TCP 粘包的 解决办法:
1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2、发送端将每个数据包封装为固定长度(不够的可以通过补 \0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
在这里插入图片描述

以太网头

以太网中封装了 目的mac地址 以及 源mac地址、IP 类型,以太网头又称为 mac头。
切换网络时,IP地址会改变,Mac地址不会改变。
在这里插入图片描述

type 类型
0x0800 ——> 只接收发往 本机MAC 的 IP类型的数据帧;
0x0806 ——> 只接收发往 本机 ARP类型 的数据帧;
0x8035 ——> 只接收发往 本机 RARP类型 的数据帧;
0x0003 ——> 接收发往 本机MAC 的所有类型:IP, ARP, RARP 数据帧,接收从本机发出去的数据帧,
当混杂模式打开的情况下,会接收到非发往本地的 MAC 数据帧。
ARP:ARP协议用于将 IP地址 解析为 MAC地址。当一台计算机向另一台计算机发送数据时,它需要知道目标计算机的 MAC地址,而不是 IP地址。
RARP:RARP协议则是与 ARP 相反的过程,它用于将 MAC地址 解析为 IP地址。

IP头

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

UDP头

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

TCP头

在这里插入图片描述
在这里插入图片描述

三次握手

服务器必须准备好接受外来的连接。这通过调用 socket、 bind 和 listen 函数来完成,称为被动打开(passive open)。
第一次握手:客户通过调用 connect 进行主动打开(active open)。客户端发送一个SYN(表示同步)
分节(SYN=J),它告诉服务器客户将在连接中发送到数据的初始序列号。并进入 SYN_SEND 状态,
等待服务器的确认。
第二次握手:服务器必须确认客户的 SYN,同时自己也得发送一个 SYN 分节,它含有服务器将在同一连接
中发送的数据的初始序列号。服务器以单个字节向客户发送 SYN 和 对客户 SYN 的 ACK(表示确认)
此时服务器进入 SYN_RECV 状态。
第三次握手:客户收到服务器的 SYN+ACK。向服务器发送确认分节,此分节发送完毕,客户服务器进入
ESTABLISHED(确认)状态,完成三次握手。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四次挥手

第一次挥手:主动关闭方发送一个 FIN 给被动方,进入 FIN_WAIT 状态;
第二次挥手:被动方接收到 FIN 包,给主动方发送一个 ACK 包;并进入 CLOSE_WAIT 状态,主动方接收
到 ACK 包后,如果有数据没有发送完毕,则继续发送,一直到发送完毕;
第三次挥手:被动方发送一个 FIN 包,进入 LAST_ACK 状态。
第四次挥手:主动关闭方收到 FIN 包,回复一个 ACK包。被动关闭方收到主动关闭方的 ACK后关闭连接。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


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

相关文章

Clickhouse设置多磁盘存储策略

设置多磁盘存储 clickhouse安装完成以后&#xff0c;配置了一个默认的存储空间&#xff0c; 这个只能配置一个目录&#xff0c;如果要使用多个磁盘目录&#xff0c;则需要配置磁盘组策略 查看当前的存储策略 select name, path, formatReadableSize(free_space) as free, fo…

Go使用开源库go-excelize操作Excel文件

以下是一个示例代码&#xff0c;读取一个 Excel 文件并打印其中的所有单元格值&#xff1a; package mainimport ("fmt""github.com/30x/go-excelize" )func main() {// 打开 Excel 文件f, err : excelize.OpenFile("yourfile.xlsx")if err ! n…

【libGDX】使用Mesh绘制矩形

1 前言 使用Mesh绘制三角形 中介绍了绘制三角形的方法&#xff0c;本文将介绍绘制正方形的方法。 libGDX 以点、线段、三角形为图元&#xff0c;没有提供绘制矩形内部的接口。要绘制矩形内部&#xff0c;必须通过三角形拼接而成&#xff0c;如下图&#xff0c;是通过GL_TRIANGL…

为UE和Unity开发者准备的Godot指南

为UE和Unity开发者准备的Godot指南 ——两位大哥打架&#xff0c;请带上我 这两天游戏行业又开始热闹了&#xff0c;昨天两条信息直接刷爆朋友圈&#xff0c;最大的两家游戏引擎公司怼起来了。 《为Unity开发者准备的虚幻引擎指南》&#xff1a; 为Unity开发者准备的虚幻引擎指…

RabbitMQ快速入门(简单收发消息)

文章目录 前言一、数据隔离1.用户管理2.virtual host 二、控制台收发1.交换机2.队列3.绑定 三、编程式收发1.依赖和配置2.收发信息 总结 前言 1.了解数据隔离 2.RabbitMQ控制台收发信息 3.SpringBoot整合RabbitMQ收发信息 一、数据隔离 1.用户管理 点击Admin选项卡&#xff0…

Hadoop -hdfs的读写请求

1、HDFS写数据&#xff08;宏观&#xff09;&#xff1a; 1、首先&#xff0c;客户端发送一个写数据的请求&#xff0c;通过rpc与NN建立连接&#xff0c;NN会做一些简单的校验&#xff0c;文件是否存在&#xff0c;是否有空间存储数据等。 2、NN就会将校验的结果发送给客户端…

音视频项目—基于FFmpeg和SDL的音视频播放器解析(十八)

介绍 在本系列&#xff0c;我打算花大篇幅讲解我的 gitee 项目音视频播放器&#xff0c;在这个项目&#xff0c;您可以学到音视频解封装&#xff0c;解码&#xff0c;SDL渲染相关的知识。您对源代码感兴趣的话&#xff0c;请查看基于FFmpeg和SDL的音视频播放器 如果您不理解本…

leetcode 32最长有效括号 34在排序数组中查找元素的第一个和最后一个位置

32. 最长有效括号 给你一个只包含 ( 和 ) 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号子串的长度。 示例 1&#xff1a; 输入&#xff1a;s "(()" 输出&#xff1a;2 解释&#xff1a;最长有效括号子串是 "()" 示例 2&a…