优于别人,并不高贵,
真正的高贵应该是优于过去的自己。
--- 海明威 ---
1 网络通信流程
两台主机进行通信时,需要进行两个通信协议栈,分别进行封装信息和解包信息。网络层包装协议时,会加入当前IP地址和目标IP地址,然后再次封装到数据链路层进行通信。路由器得到数据包后进行一层的解包,通过目标IP地址进行判断:
- 发现不是给同一局域网的主机,直接通过MAC地址找到目标
- 发现是给不同局域网的主机,就推送数据包给路由器进行搜索对应IP地址,然后再次封装数据包进行通信,最后通过MAC地址找到对应主机。
图示:
- 网络层(就是IP层)向上(包括网络层)看到的所有的报文的都是一样的,至少是IP报文。
- IP可以屏蔽底层网络的差异
- 所以的网络都是IP网络,所以手机,电脑和平板都可以互相通信!
2 IP地址 VS MAC地址
我们讲个故事来理解这两个地址:
最近黑神话悟空非常热门,我们就以西游记进行举例: 唐僧师徒四人从东土大唐长安城出发,前往西天灵山取经。中间会经过若干城池:车迟国,女儿国,火焰山…
- 此时唐僧有两份地址 :长安到灵山 , 长安到车迟国。到达车迟国时,唐僧告诉国王:“我从东土大唐而来 ,前往女儿国,请问接下来应该前往何处?” 国王根据唐僧四人的目的地告诉:下一站是女儿国。这时唐僧的两份地址为: 长安到灵山 , 车迟国到女儿国!
- 到达女儿国,唐僧告诉国王:“我从东土大唐而来 ,前往西天灵山取经,请问接下来应该前往何处?” 女儿国国王告诉唐僧下一站是火焰山。这时唐僧的两份地址为:长安到灵山 , 女儿国到火焰山
- 最终到达火焰山。
这里反复出现的“我从东土大唐而来 ,前往西天灵山取经”就是IP地址 而每一次的起始站是MAC地址,每个国相当于路由器,通过唐僧的目标,选择最合适的下一站
所以:
- IP地址是最终目标
- MAC地址是下一个目标,受IP地址影响!
将西游记的落实到实际中就是这样的一张图!!!
3 网络socket
3.1 理解源 IP 地址和目的 IP 地址
MAC地址只能在局域网内保证唯一性,因为MAC地址不会超出局域网。IP地址才是用来保证网络中的主机的唯一性!
数据传输的目的不是仅仅是到达主机,而是让用户来进行使用使用数据!也就是要在进程中来使用数据! 我们看B站,从服务器主机传输来的视频数据,最终是要在我们手机上的B站进程中进行播放的!
数据传输到主机不是目的, 而是手段。 到达主机内部, 在交给主机内的进程,才是目的!
但是,数据包通过IP地址找到对应主机,之后是如何从大量进程中找到目标进程呢?通过端口号(PORT),IP地址中的端口号开放给进程使用,用来标识目标进程!
也就是数据包解包到应用层时通过端口号找到对应进程!
这里具有疑问了?为什么不直接使用进程的pid,这不也是唯一的吗? 因为如果使用pid进行网络标识,那么网络与系统就产生了强耦合!如果系统层出现问题导致PID变化,就会导致网络层也出现问题!这可不行,我们要做的是弱耦合!所以单独使用端口号来进行标识!
所以:socket = IP PORT == 网络中唯一的进程
。
这样也就得到了网络通信的本质:不同主机的进程间通信!!!这两个进程看到的公共资源就是网络!!!这种通信就叫socket
通信!
- 端口号是一个 2 字节 16 位的整数;
- 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
- IP 地址 端口号能够标识网络上的某一台主机的某一个进程;
- 一个端口号只能被一个进程占用
端口号分为两部分,一部分是系统内部只有,其余的才允许用户使用!
- 0 - 1023: 知名端口号, HTTP, FTP, SSH 等这些广为使用的应用层协议, 他们的端口号都是固定的
- 1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的.
传输层协议(TCP 和 UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号.就是在描述 “数据是谁发的, 要发给谁”;
3.2 传输层的典型代表
用户是在应用层进行操控,用户想要进行通信就需要使用传输层。传输层之下的层,用户不需要考虑,操作系统会帮我们完成!我们只需要理解使用传输层的系统调用即可!
传输层有UDP协议和TCP协议:
- UDP协议具有无连接,不可靠传输,面向数据报的特点!
- TCP协议具有有连接,可靠传输,面向字节流的特点!
注意可靠听起来比不可靠更好,但是可靠的协议底层实现就肯定更加复杂!所以可不可靠不是优缺点,而是一种特性!以后我们再来进行详细讲解!
3.3 网络字节序
在学习C语言时,了解过两种机器:大端机和小端机,这两种的储存方式是不一样的:
- 大端机的低地址储存数据的高位
- 小端机的低地址储存数据的低位
如果两台主机不是一样的!那么主机如何读取另一个数据发送过来的数据包呢?因为根本无法判断对方是大端机还是小端机,那么就不可能正常的读取数据!
所以:TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高字节.
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.这样按照大端字节序更加顺畅!传输数据时,如果是小端机就通过系统调用将数据转换成大端,否则就忽略,直接发送就可以!
3.4 socket编程基础
这里有个很有意思的地方,因为设计者认识到网络通信的本质是进程间通信,所以设计者就希望通过一套接口完成网络通信和系统通信!
通过不同的标志为可以选择要进行何种方式的通信!其中的系统通信非常像命名管道的通信方式!那么通过一套公共接口实现了两种不同的通信方式,这不就是多态吗!通过结构体的第一个字段判断如何读取下面的字段,判断出应该采用什么方式进行通信。
下面是socket API:
代码语言:javascript复制// 创建 socket 文件描述符 (TCP/UDP, 客户端 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
// 开始监听 socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
socket API 可以都用 struct sockaddr *
类型表示, 在使用的时候需要强制转化成sockaddr_in
或sockaddr_un
; 这样的好处是程序的通用性!
之后我们就来学习UDP套接字编程!开始网络编程的第一步!