嵌入式第三十五课!!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;
}

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

相关推荐
To_OC7 小时前
LC 51 N 皇后:我以为难的是回溯,结果栽在了对角线下标
javascript·算法·leetcode
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
2401_841495648 小时前
【操作系统】进程同步与互斥实验报告
c++·算法·操作系统·进程·并发·同步·互斥
Yana.nice8 小时前
Linux 只保留 30 天内日志(find命令删除日志文件)
linux·运维·chrome
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
andxe10 小时前
安科士 AndXe 技术博客:400G QSFP112 SR4 光模块|AI 算力与超算短距互联最优方案
网络·人工智能·光模块·光通信