Linux 下互联网络编程的基础知识
2019-10-07
关键字:Linux 网络编程基础
TCP/IP 协议里有两种不同的协议:
1、TCP协议
用于检测网络传输中的差错。
2、IP协议
用于对不同网络进行互联。
简单说就是 TCP 负责纠错,IP 负责传输。
网络体系结构:
网络体系结构就是将复杂的网络通信过程按照一定的规则进行分层,从而能使整个的网络通信过程更加清晰。
这一分层的核心思想有二:
1、每一层实现不同的功能,并对其上层做透明传输。
2、每一层都会使用到其下一层所提供的服务,并对其上一层提供服务。
早期的网络体系结构是 OSI 七层模型:
1、物理层
2、数据链路层
3、网络层
4、传输层
5、会话层
6、表示层
7、应用层。
但 OSI 模型在现在用的已经比较少了。通常这个 OSI 模型会和交换机比较相关。
现在用的较多的是 Internet 事实上的四层模型:
1、网络接口和物理层
该层的主要作用是为了屏蔽硬件差异。无论你是通过什么类型的硬件进行网络通信的,在 Linux 内核中都会将它描述成是一个 net_device 结构体。
这一层有一个很重要的概念:MAC地址。
与这一层相关的比较常用的网络协议有以下几种:
1、ARP/RARP 协议
ARP 的作用是将根据IP地址找到对应的MAC地址,而 RARP 则相反。
2、PPP 协议
这就是我们常说的拨号协议。
2、网络层
负责端到端的传输。简单理解就是不同机器之间的数据传输,网络层就负责将数据从一个机器准确抵达另一个机器上。
这一层也有一个很重要的概念:IP地址。
与这一层相关的比较常用的网络协议有以下几种:
1、IP协议
2、ICMP
Internet 控制管理协议。如 ping 命令。
3、IGMP
Internet 分组管理协议。如广播、组播等。
3、传输层
负责分发数据到确定的任务去处理。
与这一层相关的比较常用的网络协议有以下几种:
1、TCP
面向连接的一对一的可靠数据传输协议。
何谓可靠传输呢?即数据无误、数据无丢失、数据无错序以及数据无重复地到达的传输。
TCP 是如何实现可靠传输的呢?
1、它会将所有数据进行编号,每一个字节编一个号;
2、它传送数据是以一个 window 来进行的,并且有接收方的确认机制。
2、UDP
无连接的一对一的不可靠数据传输协议。
3、SCTP
流控制传输协议。它是一种可靠传输协议,它是 TCP 的增加版,它的可靠性要比 TCP 更好一些。
它能实现多主机、多链路的通信。
4、应用层
具体的应用程序对数据的消费。
与这一层相关的比较常用的网络协议有以下几种:
1、HTTP/HTTPS
网页访问协议。
2、POP3/SMTP/IMAP
邮件发送接收协议。 POP3用于发送,SMTP用于接收。
3、Telnet/SSH
远程登录协议。 Telnet 是明文传输的通信协议,SSH 是加密数据传输的。
4、NTP
网络时钟协议。
5、SNMP
简单网络管理协议,主要用于实现对网络设备的集中式管理。
6、RTP/RTSP
主要用于传输音视频的协议。
在 Linux 网络系统中,无论是 OSI 七层模型还是四层模型,传输层及其以下的层级都是 Linux 内核的部分。其上的部分才是属于应用空间的内容。
网络通信过程中的封包与拆包:
懒得转述,一图胜千言。
Socket :
首先 Socket 是一种编程接口,它同时也是一种特殊的文件描述符。
Socket 的类型有三种:
1、流式套接字:SOCK_STREAM --> TCP
2、数据报套接字:SOCK_DGRAM --> UDP
3、原始套接字:SOCK_RAW
这一协议跨过了传输层,直接对网络层进行 IP,ICMP 访问。
IP地址:
分为 IPV4 与 IPV6。IPV4 使用 4 个字节共 32 位的整型数表示。IPV6 使用 16 个字节共 128 位的整型数表示。
IPV4:
通常 IPV4 地址会以适合阅读的点分形式来表示,如:192.168.77.254。但在网络通信里,为了节省流量,通常会将点分形式的 IPV4 地址转成 32 位的整型数值来表示。
Linux 系统提供了多种将点分形式IP地址转换成整数形式的接口:
1、in_addr_t inet_addr(const char *cp);
参数 cp 就是点分形式的 IPV4 地址。这个函数内部已经包含了字节序转换操作的了。
这个函数只能转换 IPV4 的地址,并且这个函数不能转换 255.255.255.255 地址。
2、int inet_pton(int af, const char *src, void *dst);
这个函数可以转换任意 IPV4 或 IPV6 地址。
返回值为 1 时表示转换成功。
参数 af 指明地址协议族,可填 AF_INET 或 AF_INET6,分别对应于 IPV4 和 IPV6。
参数 src 是字符串形式的 IP 地址。
参数 dest 是转换的结果。
3、const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
将整数形式的 IP 地址转换成适宜阅读的字符串形式。
执行成功时返回一个非空的 dst 的地址,执行失败时返回 NULL 并设置相应的 errno。
参数 af 代表 IP 协议,可以填写 AF_INET 或 AF_INET6。
参数 src 就是要转换的整数形式的 IP 地址的地址。
参数 dst 通常就是 char * 地址。
参数 size 是 src 所代表的 sockaddr 结构体的大小。
特殊的 IPV4 地址:
局域网 IP:192.168.xxx.xxx , 172.16( ~ 31).xxx.xxx , 10.xxx.xxx.xxx
广播 IP:xxx.xxx.xxx.255 , 255.255.255.255(全网广播)
组播 IP:224.xxx.xxx.xxx ~ 239.xxx.xxx.xxx (所有以 255 结尾的 IPV4 地址都是广播地址,不能实现组播功能。)
端口号:
端口号是一个 16 位的数字,范围为 1 ~ 65535。
它的作用就是用于区分同一台机器中需要进行网络通信的不同的任务。
TCP 端口与 UDP 端口是相互独立的。即同一个端口号可以分别以 TCP 的形式和 UDP 的形式在系统中共存使用。
保留端口:
1 ~ 1023。这些端口号普通应用程序通常是无法使用的。
另外还有一组端口号是不建议普通应用程序使用但是却是可以直接使用的: 1024 ~ 5000。
特殊端口号:
FTP : 21
SSH : 22
HTTP : 80
HTTPS : 469
字节序:
字节序的概念主要应用于多字节整数的情况。
字节序主要分两种:
1、小端序;
2、大端序。
数据本身有低端数据与高端数据之分,如 0x123456789 ,数据 0x12 是高端数据,数据 0x89 是低端数据。
内存也有高低端之分。
当内存的低端存储着数据的低端时,就称为“小端序”。
当内存的低端存储着数据的高端时,就称为“大端序”。
通常 x86/arm 架构的机器都采用小端模式。而 powerpc/mips 或者说当 ARM 作为路由器时就采用大端模式。
当 CPU 访问字符串时不存在大小端问题,字符串都是单字节构成的,不存在字节排序问题。
字节序在实际应用中又分为:1、本地字节序(小端序);2、网络字节序(大端序)。两种概念。Linux 系统也提供了这两种字节序相互转换的函数接口:
本地字节序 ---> 网络字节序
u_long htonl(u_long hostlong);
u_short htons(u_short short);
网络字节序 ---> 本地字节序
u_long ntohl(u_long hostlong);
u_short ntohs(u_short short);
常用的网络信息检索函数:
1、gethostname();
获取主机名。
2、getpeername();
获得与套接口相连的远程协议地址。
3、getsockname();
获得本地套接口协议地址。
4、gethostbyname();
根据主机名取得主机信息。
将域名形式的地址转换成 IP 地址形式。
函数执行成功时返回一个结构体变量 struct hostent,它的原型如下:
struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
};
由于调用这个函数时系统会自动创建一个 hostent 结构体,这个结构体内部有多项指针。为了避免内存泄露,在结束地址信息使用时,应手动调用一下 endhostent() 函数。
5、gethostbyaddr();
根据主机地址取得主机信息。
6、getprotobyname();
根据协议名取得主机协议信息。
7、getprotobynumber();
根据协议号取得主机协议信息。
8、getservbyname();
根据服务名取得相关服务信息。
9、getservbyport();
根据端口号取得相关服务信息。
常用的网络函数:
1、设置 socket 参数;
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
根据这两个函数的名字也能猜到它们是与配置 socket 有关的函数了。
参数 level 用于指定控制套接字的层次,可选的值有三个:
1、SOL_SOCKET; 通用套接字选项。属于应用层
2、IPPROTO_IP; IP选项。属于网络层
3、IPPROTO_TCP; TCP选项。属于传输层
参数 optname 表示选项。这个的可选值比较多,具体的可以查询 man 手册。
参数 optval 表示选项的值。与前一个参数息息相关,需要注意的是,不同的选项类型所对应的值的类型也不一样,因此这里会要求传入一个无类型指针。具体的值类型需要参阅 man 手册。
参数 optlen 就是前一个参数选项的值的长度了。通常使用 sizeof 就可以了。
广播:
所谓广播就是指同一个网络包允许被多台主机接收到的网络通信模式。广播必须使用 UDP 协议。
每一个网段中最后一个 IP 地址就是广播专属地址,例如 192.168.1.x 网段,它的广播地址就为 192.168.1.255,而 10.x.x.x 网段的广播地址则为 10.255.255.255。另外,有一个特殊广播地址:255.255.255.255,它表示全网广播地址。
所谓广播地址就是指当你向某个广播地址发送数据包时,那么该网段上的所有主机都可以接收到这个网络包。但是对于 255.255.255.255 这个全网广播地址,绝大多数的路由器都是禁用了这一广播地址功能的。
广播通信编程模型中也分发送方与接收方两种角色,本质上就是 C/S 模型。
对于广播发送方,它需要按以下步骤实现:
1、创建UDP套接字;
2、默认创建出来的套接字不允许广播数据,它需要调用 setsockopt 设置一下套接字属性;
int b_br = 1;
setsockopt(sockFd, SOL_SOCKET, SO_BROADCAST, &b_br, sizeof(int));
3、将接收方的地址指定为对应的广播地址;
4、指定端口信息;
5、发送数据包。
对于广播接收方,它需要按以下步骤实现:
1、创建UDP套接字;
2、绑定本机IP地址和指定的广播通信端口;
3、等待接收数据。
组播:
组播与广播都属于一对多的网络通信模式。但与广播不同,组播是在特定的“组”里实现的一对多网络通信。换句话说就是组播是一个人发送,所有加入到特定组里的人才能接收到数据的通信模式。可以理解成是一种加了一些限定条件的广播通信。
同样的组播的通信也必须使用 UDP 协议。
组播的地址范围在 IP 地址分类中属于 D 类地址,它的范围为:224.0.0.1 ~ 239.255.255.254,这个地址范围中要注意所有以 255 结尾的地址都是广播地址,不属于组播的范畴。
组播的发送方通常需要按以下步骤实现:
1、创建UDP套接字;
2、将接收方地址设定为组播地址;
3、指定端口信息;
4、发送数据包。
组播的接收方通常需要按以下步骤实现:
1、创建 UDP 套接字;
2、加入特定的组播组;
struct ip_mreq mreq;
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr("233.5.6.100");
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(socdFd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
3、绑定本机IP地址和指定的端口号;
4、等待接收数据。