1、目的
实现不同主机上进程间的通信。
2、问题
- 主机与主机之间在物理层面必须互联互通。
- 进程与进程在软件层面必须互联互通。
- IP地址:计算机的软件地址,用来标识计算机设备。
- MAC地址:计算机的硬件地址(固定)。
- 网络的端口号:标记同一主机上的不同网络进程。
3、网络协议
网络通信标准
OSI七层模型
开放系统互联模型,是不同设备间网络通信的通信标准,可概括为“物数网传会表应”。
- 应用层:要传输的数据信息,如文件传输、电子邮件等。
- 表示层:进行数据加密、解密操作,压缩、解压缩。
- 会话层:建立数据传输通道(会话)。
- 传输层:确定传输方式(UDP、TCP),涉及端口号。
- 网络层:实现数据路由、路径规划(路由器、IP),涉及传输方式(UDP、TCP)和端口号。
- 数据链路层:将各层数据封装成帧,实现点对点通信(局域网内通信),进行差错检测(交换机、ARP)。
- 物理层:定义物理设备标准、电器特性,涉及网线、光纤等传输介质。
TCP/IP模型
应用模型,有五层和四层两种划分方式。
五层划分
- 应用层:
- HTTP:超文本传输协议。
- HTTPS:超文本传输协议(SSL加密算法)。
- FTP:文件传输协议(TCP)。
- TFTP:简单文本传输协议(UDP)。
- MQTT:消息队列遥测传输(物联网协议)。
- DNS:域名解析服务(将域名转换为IP地址)。
- 传输层:
- TCP:传输控制协议。
- UDP:用户数据报协议。
- 网络层:
- IP协议:包括IPv4和IPv6。
- 数据链路层:
- ARP协议:地址解析协议。
- 物理层
四层划分
- 应用层
- 传输层
- 网络层
- 网络接口层
4、IP协议
基本概念
IP协议位于网络层。
- IPv4为32位,如192.168.1.128。
- IPv6为128位。
IP地址的用户表示形式为点分十进制,如192.168.1.140;
计算机存储形式为32位二进制,如11000000 10101000 00000000 01000011。
组成与相关概念
- IP地址组成:IP地址 = 网络位 + 主机位。
- 例如192.168.0.121/24,其中24表示网络位的位数。
- 网络位:标识该IP地址位于哪个网段(局域网)内。
- 主机位:标识在这个网段(局域网)内的第几台主机。
- 例如192.168.0.121/24,其中24表示网络位的位数。
- 子网掩码:用于区分IP地址的网络位和主机位,搭配IP地址使用。
- 子网掩码是1的部分对应IP地址的网络位,是0的部分对应IP地址的主机位。
- 如255.255.255.0,其二进制形式为11111111.11111111.11111111.00000000。
- 网段号:IP地址网络位不变,主机位全为0,则为该IP地址的网段号。
- 例如,IP地址192.168.1.3,子网掩码255.255.0.0,其网段号为192.168.0.0,位于192.168.1.0网段内(网段内的IP能直接通信)。
- 广播号:IP地址网络位不变,主机位全为1,则为该IP地址的广播号。
- 向广播号发送信息,所有局域网内IP都能收到此信息。例如,IP地址192.168.1.3,子网掩码255.255.255.0,其广播号为192.168.1.255(如feiQ、VNC使用)。
- 网关地址:如192.168.1.1。
IP地址划分
类别 | 范围 | 子网掩码 | 用途 | 私有IP地址范围 |
---|---|---|---|---|
A类地址 | 1.0.0.0 - 126.255.255.255 | 255.0.0.0 | 管理大规模网络 | 10.0.0.0 - 10.255.255.255 |
127.0.0.0 | 回环地址 | |||
B类地址 | 128.0.0.0 - 191.255.255.255 | 255.255.0.0 | 管理大中规模网络 | 172.16.0.0 - 172.31.255.255 |
C类地址 | 192.0.0.0 - 223.255.255.255 | 255.255.255.0 | 管理中小规模网络 | 192.168.0.0 - 192.168.255.255 |
D类地址 | 224.0.0.0 - 239.255.255.255 | 组播和广播使用 | ||
E类地址 | 240.0.0.0 - 255.255.255.254 | 用来进行实验 |
- 公有IP:由电信公司直接分配,需要付费,可以直接访问internet。
- 私有IP:不能直接访问internet的IP地址,目的是节省IP地址。
5、网络端口号
基本信息
端口号是16位的整形数据(unsigned short),范围为0~65535,功能是标记同一主机的不同网络进程。
分类
- 1-1023之间的端口号:任何TCP/IP实现所提供的服务使用。如http:80,FTP:20/21,TFPT:69,HTTPS:443。
- 1024-49151:被注册的端口号,被IANA指定为特殊服务使用。如MQTT:1883/8883。
- 49152-65535:动态或私有端口号。
6、网络配置
- ping命令:ping ip地址/域名,用于查看当前主机和IP/域名所对应的主机网络是否联通。如ping www.baidu.com。
- 查看IP地址:
- 在Linux中使用 ifconfig。
- 在Windows上使用 ipconfig。
- 网络配置步骤:
- 虚拟机--》设置--》网络适配器---》桥接模式。
- 编辑--》虚拟网络编辑器--》更改设置--》VMnet0---》桥接至--》当前PC正在上网的网卡上--》应用--》确定。
- 修改网络配置文件:sudo vim /etc/network/interfaces,文件内容如下:
auto lo iface lo inet loopback auto ens33 iface ens33 inet dhcp
- 重启网络服务:sudo /etc/init.d/networking restart
- 测试:ping www.baidu.com
7、网络协议--UDP
基本概念
UDP(User Datagram Protocol)即传输层用户数据报协议。
网络编程模型
- B/S模型(browser/server,浏览器/服务器):
- 客户端是通用的客户端(浏览器)。
- 一般只做服务器开发。
- 客户端要加载的数据均来自服务器。
- C/S模型(client/server,客户端/服务端):
- 客户端是一个专用的客户端。
- 服务器和客户端都需开发。
- 客户端可以保存资源,本地加载,无需所有数据都请求服务器。
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。
sockaddr_in结构体(man 7 ip)
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 */ };
字节序转换函数
字节序转换
网络字节序(大端顺序)和主机字节序(小端顺序)在数据存储方式上存在差异。
网络字节序中,高位字节存储在低地址;主机字节序中,低位字节存储在低地址。
例如,数值$0x1234$:
- 小端存储:低地址为$0x34$,高地址为$0x12$
- 大端存储:低地址为$0x12$,高地址为$0x34$
标准库函数提供了字节序转换功能,具体声明如下:
uint32_t htonl(uint32_t hostlong); // 主机转网络(32位)
uint16_t htons(uint16_t hostshort); // 主机转网络(16位)
uint32_t ntohl(uint32_t netlong); // 网络转主机(32位)
uint16_t ntohs(uint16_t netshort); // 网络转主机(16位)
in_addr_t inet_addr(const char *cp);
功能:将字符串IP地址转换成二进制IP地址形式char *inet_ntoa(struct in_addr in);
功能:将二进制ip转换成字符串
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:绑定自己的IP地址和端口号
参数:sockfd:套接字addr:需要绑定的地址addrlen:地址大小
返回值:成功:0失败:-1
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:从套接字上接收数据
参数:sockfd:套接字buf:存放接收数据的内存首地址len:希望接收的字节数flags:0 :按照默认方式接收(阻塞)src_addr:发送方的地址信息addrlen:发送发地址的指针
功能:成功:实际接收到的字节数失败:-1
8、代码训练
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include<string.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{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");// 创建子进程char buff[1024] = {0};while(1){fgets(buff, sizeof(buff), stdin);ssize_t cnt = sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&seraddr, sizeof(seraddr));if(cnt < 0){perror("sendto error");return -1;}printf("cnt = %ld\n", cnt);memset(buff, 0, sizeof(buff));}close(sockfd);return 0;
}
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{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.177");int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if(ret < 0){perror("bind error");ret -1;}char buff[1024] = {0};while(1){ssize_t cnt = recvfrom(sockfd, buff, sizeof(buff), 0, NULL, NULL);if(cnt < 0){perror("recvfrom error");return -1;}printf("cnt = %ld, buff = %s\n", cnt, buff);memset(buff, 0, sizeof(buff));}close(sockfd);return 0;
}