嵌入式学习第二十五天!(网络的概念、UDP编程)

网络:

可以用来:数据传输数据共享

1. 网络协议模型:

1. OSI协议模型:

|-------|-------------------------|
| 应用层 | 实际收发的数据 |
| 表示层 | 发送的数据是否加密 |
| 会话层 | 是否建立会话连接 |
| 传输层 | 数据传输的方式(数据包,流式) |
| 网络层 | 数据的路由(如何从一个局域网到达另一个局域网) |
| 数据链路层 | 局域网下如何通信 |
| 物理层 | 物理介质的连接 |

2. TCP/IP协议模型:

|-------|-------------------|
| 应用层 | 传输的数据 |
| 传输层 | 传输的方式 |
| 网络层 | 数据如何从一个台主机到达另一台主机 |
| 网络接口层 | 物理介质的连接 |

1. 应用层:

例如有:HTTP 超文本传输协议

HTTPS

FTP 文件传输协议

TFTP 简单文本传输协议

SMTP 邮件传输协议

MQTT

TELNET

...

2. 传输层:

**UDP:**用户数据报协议

**特点:**1. 实现机制简单

  1. 资源开销小

  2. 不安全不可靠

**TCP:**传输控制协议

**特点:**1. 实现机制复杂

  1. 资源开销大

  2. 安全可靠

3. 网络层:

IPv4

**IP地址:**唯一网络中一台主机的标号

**IP地址:**网络位 + 主机位

子网掩码:用来标识IP地址的网络位和主机位

子网掩码是1的部分表示IP地址的网络位

子网掩码是0的部分表示IP地址的主机位

**网段号:**网络位不变,主机位全位0,表示网段号

**广播地址:**网络位不变,主机位全为1,表示广播地址

IP地址类型:

A类:

1.0.0.0 - 126.255.255.255

子网掩码:255.0.0.0

管理超大规模网络

私有IP地址:10.0.0.0 - 10.255.255.255

B类:

128.0.0.0 - 191.255.255.255

子网掩码:255.255.0.0

管理大中规模型网络

私有IP地址:172.16.0.0 - 172.31.255.255

C类:

192.0.0.0 - 223.255.255.255

子网掩码:255.255.255.0

管理中小规模型网络

私有IP地址:192.168.0.0 - 192.168.255.255

D类:

224.0.0.0 - 239.0.0.0

用于组播

E类:

240.0.0.0 - 255.255.255.255

用于实验

4. UDP编程:

socket套接字(全双工)编程:

**发端:**socket -> sendto -> close

**收端:**socket -> bind -> recvfrom -> close

1. 发端
1. socket:
cpp 复制代码
int socket(int domain, int type, int protocol);

**功能:**创建一个用来通信的文件描述符

参数:

**domain:**使用的协议族 AF_INET(IPv4协议族)

**type:**套接字类型

**SOCK_STREAM:**流式套接字

**SOCK_DGRAM:**数据报套接字

**SOCK_RAW:**原始套接字

**protocol:**协议

默认为0;

返回值:

成功返回文件描述符

失败返回-1

2. sendto:
cpp 复制代码
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

**功能:**利用套接字向指定地址发送数据信息

参数:

**sockfd:**套接字文件描述符

**buf:**发送数据空间首地址

**len:**发送数据的长度

**flags:**属性默认为0

**dest_addr:**目的地址信息存放的空间首地址

**addrlen:**目的地址的长度

cpp 复制代码
struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};

返回值:

成功返回实际发送字节数

失败返回-1

3. inet_addr:
cpp 复制代码
in_addr_t inet_addr(const char *cp);

**功能:**将字符串IP地址转换为内存中的IP地址

4. htons:
cpp 复制代码
uint16_t htons(uint16_t hostshort);

**功能:**将本地字节序转换为网络的大端字节序

练习:
  1. 编写程序实现从终端接收字符串发送给windows软件调试助手,并接收软件助手的回复,显示在终端屏幕上
cpp 复制代码
#include "head.h"

int main(void)
{
	int sockfd = 0;
	ssize_t nsize = 0;
	char tmpbuff[1024] = {0};
	struct sockaddr_in recvaddr;

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd == -1)
	{
		perror("fail to socket");
		return -1;
	}
	
	gets(tmpbuff);


	recvaddr.sin_family = AF_INET;
	recvaddr.sin_port = htons(50000);
	recvaddr.sin_addr.s_addr = inet_addr("192.168.1.162");
	
	bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(&recvaddr));

	nsize = sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
	if(nsize == -1)
	{
		perror("fail to sendto");
		return -1;
	}

	printf("成功发送 %ld 字节!\n", nsize);

	
	memset(tmpbuff, 0, sizeof(tmpbuff));
	nsize = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, (struct sockaddr *)&recvaddr, (socklen_t *)sizeof(&recvaddr));
	printf("%s\n",tmpbuff);

	close(sockfd);

	return 0;
}
2. 收端
1. recvfrom:
cpp 复制代码
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

**功能:**从套接字中接收数据

参数:

**sockfd:**套接字文件描述符

**buf:**存放数据空间首地址

**flags:**属性,默认为0

**src_addr:**存放IP地址信息的空间首地址

**addlen:**存放接收到IP地址大小空间的首地址

返回值:

成功返回实际接收字节数

失败返回-1

2. 修改虚拟机到桥接模式:

点击"虚拟机"

点击"设置"

点击"网络适配器"

选择"桥接模式"

点击"确定"

3. 将网卡桥接到无线网卡:

点击"编辑"

点击"虚拟网络编辑器"

点击"更改设置"

4. 在Ubuntu中重启网络服务:
bash 复制代码
sudo /etc/init.d/networking restart 
5. 通过ifconfig查看虚拟机IP地址
6. bind:
cpp 复制代码
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

**功能:**在套接字上绑定一个IP地址和端口号

参数:

**sockfd:**套接字文件描述符

**addr:**绑定IP地址空间首地址

**addrlen:**绑定IP地址的长度

返回值:

成功返回0

失败返回-1

3. UDP需要注意的细节点:
  1. UDP是无连接,发端退出,收端没有任何影响

  2. UDP发送数据上限,最好不要超过1500个字节

  3. UDP是不安全不可靠的,连续且快速的传输数据容易产生数据丢失

4. wireshark

可以通过wireshark抓包工具来查看收发的数据

操作流程:

  1. 打开wireshark:
bash 复制代码
sudo wireshark
  1. 选择抓取数据包的网卡:any

  2. 执行通信的代码

  3. 停止通信

  4. 设定过滤条件

ip.addr == IP地址 :通过IP地址查找

udp :通过传输方式udp查找

tcp :通过传输方式tcp查找

udp.port == 端口号:通过端口号查找

5. UDP包头长度:8个字节

源端口号(2个字节)

目的端口号(2个字节)

长度(2个字节)

检验和(2个字节)

练习:

要求在不同主机中编写两个程序,实现全双工聊天功能

  1. 进入软件后接收当前用户的昵称

  2. 显示的格式为对方用户昵称 (对方IP:对方端口) > 接收到的内容

  3. 用户输入".quit"退出聊天

  4. 网络通信时收发结构体

cpp 复制代码
struct person 
{
    char name[32];
    char text[512];
};
cpp 复制代码
#include "head.h"

int sockfd = 0;
ssize_t nsize = 0;
struct sockaddr_in tmpaddr;
struct sockaddr_in sendaddr;
socklen_t addrlen = sizeof(tmpaddr);

struct person
{
	char name[32];
	char text[512];
};

pthread_t tid_recv;
pthread_t tid_send;

void *RecvInfo(void *arg)
{
	struct person user;
	while(1)
	{
		memset(&user, 0, sizeof(user));
		nsize = recvfrom(sockfd, &user, sizeof(user), 0, (struct sockaddr *)&tmpaddr, &addrlen);
		if(nsize == -1)
		{
			perror("fail to recvfrom");
			return NULL;
		}
		printf("%s %s : %d > %s\n", user.name, inet_ntoa(tmpaddr.sin_addr), ntohs(tmpaddr.sin_port), user.text);
		if(!strcmp(user.text, ".quit"))
		{
			break;
		}
	}
	pthread_cancel(tid_send);
	return NULL;
}

void *SendInfo(void *arg)
{
	struct person user;
	while(1)
	{
		memset(&user, 0, sizeof(user));
		scanf("%s", user.name);
		scanf("%s", user.text);
		nsize = sendto(sockfd, &user, sizeof(user), 0, (struct sockaddr *)&sendaddr, sizeof(sendaddr));
		if(nsize == -1)
		{
			perror("fail to sendto");
			return NULL;
		}
		printf("success send %ld byte\n", nsize);
		if(!strcmp(user.text, ".quit"))
		{
			break;
		}
	}
	pthread_cancel(tid_recv);
	
	return NULL;
}

int main(void)
{
	struct sockaddr_in recvaddr;

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd == -1)
	{
		perror("fail to socket");
		return -1;
	}
	
	recvaddr.sin_family = AF_INET;
	recvaddr.sin_port = htons(30000);
	recvaddr.sin_addr.s_addr = inet_addr("192.168.1.153");
	bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
	
	sendaddr.sin_family = AF_INET;
	sendaddr.sin_port = htons(30000);
	sendaddr.sin_addr.s_addr = inet_addr("192.168.1.152");
	
	pthread_create(&tid_recv, NULL, RecvInfo, NULL);
	pthread_create(&tid_send, NULL, SendInfo, NULL);

	pthread_join(tid_recv, NULL);
	pthread_join(tid_send, NULL);


	close(sockfd);

	return 0;

}
相关推荐
软件黑马王子17 分钟前
C#系统学习第八章——字符串
开发语言·学习·c#
strongwyy2 小时前
蓝牙墨水屏上位机学习(2)
学习
九皇叔叔2 小时前
(3)手摸手-学习 Vue3 之 变量声明【ref 和 reactive】区别
学习
The_cute_cat5 小时前
Ajax和Axios的初步学习
前端·学习·ajax
amazinging5 小时前
北京-4年功能测试2年空窗-报培训班学测开-第四十三天
python·学习
祁思妙想6 小时前
八股学习(五)---MySQL
学习
虾球xz6 小时前
CppCon 2018 学习:THE MOST VALUABLE VALUES
开发语言·c++·学习
丰锋ff7 小时前
计网学习笔记第2章 物理层(灰灰题库)
笔记·学习
Chef_Chen8 小时前
从0开始学习R语言--Day39--Spearman 秩相关
开发语言·学习·r语言
天水幼麟14 小时前
动手学深度学习-学习笔记(总)
笔记·深度学习·学习