嵌入式第三十五课!!Linux下的网络编程

一、目的

网络编程的目的实际上也是进程通信的一种方式,不过它可以在不同的主机上进行通信;

二、需要解决的问题

  1. 主机与主机之间物理层面必须互联互通。
  • 指的是参与通信的计算机(主机)需要通过物理设备建立连接(光纤、网线),确保数据能够在物理介质上传输
  • 具体包括:网卡正常工作、网线或无线信号等传输介质连接正常、路由器 / 交换机等网络设备配置正确
  1. 进程与进程在软件层面必须互联互通。
  • 指的是在物理连接通畅的基础上,不同主机上的应用程序进程需要通过网络协议进行逻辑连接
  • 具体包括:使用正确的 IP 地址和端口号、采用相同的网络协议(如 TCP/UDP)、进程已正确绑定端口并处于监听状态

举个例子:

这是一个传输机制图解;每个有IP地址的方格代表一个主机;其中,交换机就是局域网内的主机通信的物理设备;但是如果要在广域网与其他网段的主机进行通信的话,就需要"路由器+ 链路" 来负责跨网段转发。简单说,左边局域网和右边局域网,靠路由器之间的物理链路打通,数据就能从左传到右、右传到左。

至于进程,两边进程得用 相同的协议(比如 TCP/UDP) 说话。比如左边进程用 TCP 发数据,右边进程也得用 TCP 收,否则 "语言不通" 没法交流。而每个进程要通信,得绑定 IP + 端口 。比如左边主机 192.168.0.140 上的QQ给另一台主机的QQ发消息,得告诉系统:"我在 192.168.0.140 这台机器,监听 QQ 端口,别人可以给我发数据";右边主机 192.168.1.140 上的进程同理。如果端口不同,就有可能接收不到信息;

所以,在这里总结几个概念:

  • IP地址:计算机的软件地址,用来标识计算机设备(由路由器动态分配)
  • MAC地址:计算机的硬件地址(固定)
  • 网络的端口号:标记同一主机上的不同网络进程

需要注意的是:

  • 在同一个局域网下,不能有2个一模一样的ip地址;
  • 路由器在进行远距离数据传输的时候,会对路径进行选择规划,力求最短/最快路径

图解如下:

三、网络协议

网络协议即是不同体系结构设备间,网络通信的标准

OSI七层模型

意味开放系统互联模型(open system interconnect)

它是一个理论模型:

|-------|-----------------------------------------|
| 应用层 | 要传输的数据信息,如文件传输,电子邮件等 |
| 表示层 | 数据加密,解密操作,压缩,解压缩(安全性) |
| 会话层 | 建立数据传输通道, (会话) |
| 传输层 | 传输的方式 UDP TCP 端口号 |
| 网络层 | 实现数据路由,路径规划 路由器 ip |
| 数据链路层 | 封装成帧(帧数据),点对点通信(局域网内通信),差错检测 交换机 ARP |
| 物理层 | 定义物理设备标准、电气特性,比如网线,光纤等传输介质(比特流 bit 0 1) |

TCP/IP模型:应用模型

它是一个应用模型,一共分为5层:

| 应用层 ||
| HTTP | 超文本传输协议 |
| HTTPS | 超文本传输协议(SSL加密算法) |
| FTP | 文件传输协议(TCP) |
| TFTP | 简单文件传输协议(UDP) |
| MQTT | 消息队列遥测传输(物联网协议) |

DNS 域名解析服务(www.baidu.com转为IP地址)

| 传输层 ||
| TCP | 传输控制协议 |

UDP 用户数据报协议
网络层
IP协议 IPV4 IPV6

| 数据链路层 ||

ARP 地址解析协议

物理层同上:

|-----|-----------------------------------------|
| 物理层 | 定义物理设备标准、电气特性,比如网线,光纤等传输介质(比特流 bit 0 1) |

在有一些定义中,也可能是四层:

  • 应用层:同上
  • 传输层:同上
  • 网络层:同上
  • 网络接口层:包含物理层与数据链路层

四、IP协议

网络层

|------|------|
| IPv4 | 32位 |
| IPv6 | 128位 |

例:

192.168.1.140 (用户表示形式) 点分十进制

11000000 10101000 00000000 01000011 (计算机存储形式) 32bits

IP地址

IP地址 = 网络位 + 主机位

例:192.168.0.121/24

24:网络位的位数

|------|----------------------------|
| 网络位 | 该IP地址位于哪个网段(局域网)内 |
| 主机位 | 这个网段(局域网)第几台主机 |
| 子网掩码 | 用来区分IP地址的网络位和主机位,搭配IP地址使用。 |

举个例子:

255.255.255.0

11111111.11111111.11111111.00000000

子网掩码是1的部分对应IP地址的网络位

子网掩码是0的部分对应IP地址的主机位

特殊的IP地址

| 网段号: | IP地址网络位不变,主机位全为0,则为该IP地址的网段号 |
| 广播号: | IP地址网络位不变,主机位全为1,则为该IP地址的广播号 |

网关地址: 192.168.1.1(局域网与广域网的网关)

举个例子:

IP地址:192.168.1.3

子网掩码:255.255.255.0

网段号:192.168.1.0(网段内的IP能直接通信)
IP地址:192.168.1.3

子网掩码:255.255.255.0

网段号:192.168.1.255(向广播号发送信息,所有局域网内IP都能收到此信息)

IP地址的划分

在此之前,我们先来区分两个概念:

为了节省ip地址,把ip分为两种:

公有IP:由电信公司直接分配,并需要付费的IP地址, 可以直接访问internet

私有IP:不能直接访问internet的ip地址,由路由器分配,进入广域网时自动转为公有IP

(1)A类地址:

用于管理大规模网络

|------------|---------------------------|
| 范围 | 1.0.0.0 - 126.255.255.255 |
| 子网掩码 | 255.0.0.0 |
| 可以分配的IP地址数 | 126*2^24 |
| 私有IP地址 | 10.0.0.0 - 10.255.255.255 |

(2)B类地址:

管理大中规模网络

|------------|-----------------------------|
| 范围 | 128.0.0.0 - 191.255.255.255 |
| 子网掩码 | 255.255.0.0 |
| 可以分配的IP地址数 | 2^16 |
| 私有IP地址 | 172.16.0.0 - 172.31.255.255 |

(3)C类地址:

管理中小规模网络

|------------|-------------------------------|
| 范围 | 192.0.0.0 - 223.255.255.255 |
| 子网掩码 | 255.255.255.0 |
| 可以分配的IP地址数 | 2^8 |
| 私有IP地址 | 192.168.0.0 - 192.168.255.255 |

(4)D类地址:

组播和广播使用

|----|-----------------------------|
| 范围 | 224.0.0.0 - 239.255.255.255 |

(5)E类地址:

用来进行实验

|----|-----------------------------|
| 范围 | 240.0.0.0 - 255.255.255.254 |

五、 网络端口号

端口号 :为16位的整形数据(unsigned short)(范围是:0-65535)

端口号功能:标记同一主机上的不同网络进程

分类

1)任何TCP/IP实现所提供的服务都用1-1023之间的端口号。

| http | 80 |
| FTP | 20/21 |
| TFPT | 69 |

HTTPS 443

2)端口号从1024-49151是被注册的端口号,被IANA指定为特殊服务使用。

MQTT:1883/8883

3)从49152-65535是动态或私有端口号。

六、网络配置

1. ping ip地址/域名

查看当前主机和IP/域名所对应的这台主机网络是否联通

2. ifconfig

在Linux查看当前主机的IP地址

ipconfig

在Windows上查看当前主机的IP地址

3. 网络配置

1)虚拟机--》设置--》网络适配器---》桥接模式

2)编辑--》虚拟网络编辑器--》更改设置--》VMnet0---》桥接至--》当前PC正在上网的网卡上--》应用--》确定

3)修改网络配置文件

在终端输入命令:sudo vim /etc/network/interfaces

cs 复制代码
auto lo
iface lo inet loopback

auto ens33 
iface ens33 inet dhcp

4)重启网络服务

sudo /etc/init.d/networking restart

5) 测试

ping www.baidu.com

七、网络协议------UDP

UDP:(传输层)用户数据报协议(User Datagram Protocol)

1)网络编程模型

B/S模型:browser/server(浏览器/服务器)
1. 客户端是通用的客户端(浏览器)
2. 一般只做服务器开发
3. 客户端要加载的数据均来自服务器

| C/S模型:client/server(客户端/服务端) |
| 1. 客户端是一个专用的客户端 |
| 2. 服务器和客户端都需开发 |

3. 客户端可保存资源,本地加载,无需所有数据都请求服务器

2)UDP编程流程

我们先来厘清一个概念:

套接字:文件描述符;网络通信时,应用层可操作的端口。

socket函数:

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

功能:创建通信的套接字

参数:

domain:网络层使用什么协议族

AF_INET:IPv4

AF_INET6:IPv6

type:规定传输层的协议

SOCK_DGRAM : UDP协议

SOCK_STREAM:TCP协议

SOCK_RAW :原始套接字

protocol :0 按照默认协议方式创建

返回值:

成功:套接字

失败:-1

sendto函数

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:接收方的地址信息(IP+端口号)

addrlen:接收方地址的大小

返回值:

成功:实际发送的字节数

失败:-1

使用man 7 ip 命令获取两个结构体

cs 复制代码
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 */
           };

其中:

port in network byte order代表基于网络字节序的端口号;

网络字节序:大端 (network)

主机字节序:小端 (host)

为了切换这两者,我们可以使用函数:

uint32_t htonl(uint32_t hostlong); 主机转网络

uint16_t htons(uint16_t hostshort); 主机转网络

uint32_t ntohl(uint32_t netlong); 网络转主机

uint16_t ntohs(uint16_t netshort); 网络转主机

我们通常传递的ip地址为一个字符串,为了使其切换成二进制IP地址的形式,也要再调用一次函数:

in_addr_t inet_addr(const char *cp);

功能:

将字符串IP地址转换成二进制IP地址形式

char *inet_ntoa(struct in_addr in);

功能:

将二进制ip转换成字符串

bind函数:

int bind(int sockfd, const struct sockaddr *addr,

socklen_t addrlen);

功能:绑定自己的IP地址和端口号

参数:

sockfd:套接字

addr:需要绑定的地址

addrlen:地址大小

返回值:

成功:0

失败:-1

recvfrom函数:

功能:从套接字上接收数据

参数:

sockfd:套接字

buf:存放接收数据的内存首地址

len:希望接收的字节数

flags:0 :按照默认方式接收(阻塞)

src_addr:发送方的地址信息

addrlen:发送发地址的指针

功能:

成功:实际接收到的字节数

失败:-1

客户端编程:

cs 复制代码
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/types.h>          /* See NOTES */
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	//socket()
	//sendto()
	//recvfrom()
	//close()

	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		perror("socket error");
		return -1;
	}
	
	//定义的接收方(服务端)的地址变量
	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(50000);    //接收方的端口号
	seraddr.sin_addr.s_addr = inet_addr("192.168.0.179");  //接收方所在主机的IP地址

	char buff[1024] = {0};
	while (1)
	{
		fgets(buff, sizeof(buff), stdin);
		ssize_t cnt = sendto(sockfd, buff, 0, 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
		if (cnt < 0)
		{
			perror("sendto error");
			return -1;
		}

	//	printf("cnt = %ld\n", cnt);

		memset(buff, 0, sizeof(buff));
		recvfrom(sockfd, buff, sizeof(buff), 0, NULL, NULL);
		printf("ser-->cli : %s\n", buff);
	}
	//
	close(sockfd);

	return 0;
}

服务端编程:

cs 复制代码
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/types.h>          /* See NOTES */
#include <arpa/inet.h>
#include <unistd.h>


int main(int argc, const char *argv[])
{
	//socket()
	//bind()
	//recvfrom()
	//sendto()
	//close()
	
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		perror("socket error");
		return -1;
	}
	
	struct sockaddr_in cliaddr;
	socklen_t clilen = sizeof(cliaddr);
	//服务端自己的地址信息变量
	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(50000);
	seraddr.sin_addr.s_addr = inet_addr("192.168.0.179");

	int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0)
	{
		perror("bind error");
		return -1;
	}
	
	char buff[1024] = {0};
	while (1)
	{
		memset(buff, 0, sizeof(buff));
		ssize_t cnt = recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&cliaddr, &clilen);
		if (cnt < 0)
		{
			perror("recvfrom error");
			return -1;
		}
		else if (0 == cnt)
		{
			printf("cnt = %ld\n", cnt);
			break;
		}
		printf("[%s : %d][%ld] %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port),cnt, buff);

		fgets(buff, sizeof(buff), stdin);
		//strcat(buff, "--->ok");
		sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&cliaddr, clilen);

	}
	close(sockfd);

	return 0;
}

以上就是今天和大家分享的内容!!!!感谢你的阅读!!!!!

相关推荐
yzzzzzzzzzzzzzzzzz21 分钟前
JavaScript 操作 DOM
开发语言·javascript·ecmascript
weixin_4707403625 分钟前
某算法的python执行汇编
汇编·python·算法
是乐谷1 小时前
燧原科技招大模型训练算法工程师
科技·算法
海绵宝宝汉堡包1 小时前
c# 项目 文件夹
开发语言·c#
YuTaoShao1 小时前
【LeetCode 热题 100】139. 单词拆分——(解法一)记忆化搜索
java·算法·leetcode·职场和发展
小白要加油努力2 小时前
C++设计模式--策略模式与观察者模式
开发语言·c++·设计模式
Dobby_052 小时前
【Ansible】变量与敏感数据管理:Vault加密与Facts采集详解
linux·运维·云原生·ansible
记忆不曾留3 小时前
unbuntu 20.04 docker 部署wordpress
运维·docker·容器·wordpress·独立站建站
小马学嵌入式~3 小时前
数据结构:队列 二叉树
c语言·开发语言·数据结构·算法
l_tian_tian_3 小时前
SpringClound——网关、服务保护和分布式事务
linux·服务器·前端