在上篇文章中,我们了解到网络传输的基本流程,从局域网传输到跨网传输,也了解到在传输时需要封装和解包,接下来我们就要为下篇文章Socket编程作预备,我们需要通过理论加实战来输入了解,所以这篇文章会对Socket编程前铺垫一些知识概念
IP在网络中,用来标识主机的唯一性 注意:后面我们会讲IP的分类,会详细阐述IP的特点
但是这里要思考一个更深层次的问题:数据传输到主机真的是最终目的吗?
其实并不是。因为数据最终都是为人服务的。比如:聊天是人在聊天,下载是人在下载,浏览网页是人在浏览。数据只是载体,人才是主体
但是人是怎么看到聊天信息的呢?怎么执行下载任务呢?怎么浏览网页信息呢?人是如何与这些数据交互的呢?
通过启动的 qq,迅雷,浏览器。而启动的 qq,迅雷,浏览器都是进程。换句话说,进程是人在系统中的代表,只要把数据给进程,人就相当于是拿到了数据。 所以:数据传输到主机不是目的,而是手段。到达主机内部,在交给主机内的进程,才是目的。
但是系统中,同时会存在非常多的进程,当数据到达目标主机之后,怎么转发给目标进程?在复杂的网络环境中,如何确保数据不仅能到达目标主机,还能准确送达目标进程?
显然,仅靠IP地址(标识了主机)是远远不够的。这需要在两个层面建立标识体系:

端口号( port )是传输层协议的内容。
IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
就像是我们现实中的电话号码,例如那些110、119、120、10086等是固定特殊的号码,而我们普通人则是动态分配的随机号码
我们之前在学习系统编程的时候, 学习了 pid 表示唯一的一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这两者之间是怎样的关系?
一个比喻:10086客服热线
核心结论:目的和上下文不同
另外:
一个进程可以绑定多个端口号
就像不能有两个不同的热线号码都叫10086,否则客户就不知道该打哪个了。系统(内核)会保证一个端口在同一时间只能被一个进程监听。
但是一个端口号不能被多个进程绑定
就像一个超级客服可以同时负责接听10086、10085等多个热线。一个复杂的网络服务(如Nginx)可以同时监听80端口(HTTP)和443端口(HTTPS)。
进程 PID 属于系统概念,技术上也具有唯一性,确实可以用来标识唯一的一个进程,但是这样做,会让系统进程管理和网络强耦合,实际设计的时候,并没有选择这样做。
传输层协议( TCP 和 UDP )的数据段中有两个端口号,分别叫做源端口号和目的端口号.,就是在描述 “数据是谁发的, 要发给谁”。
目的端口号:回答了 “数据要交给对方主机的哪个进程?” 这是通信的目标。
源端口号:回答了 “数据是来自对方主机的哪个进程?” 并更重要的是,它提供了 “请将回复发送到我的哪个端口?” 的返回地址。
综上, IP 地址用来标识互联网中唯一的一台主机,port 用来标识该主机上唯一的一个网络进程
注意:我们之前学习了
System V IPC进程间通信,其实POSIX也有一套更标准的通信方式,它不仅可以进行进程通信,也能进行网络通信。而网络通信的方式就可以通过网络套接字Socket来实现,当然也有本地套接字Socket可以实现进程间通信,这里就不过多赘述,感兴趣可以自行了解
如果我们了解了系统,也了解了网络协议栈,我们就会清楚,传输层是属于内核的,那么我们要通过网络协议栈进行通信,必定调用的是传输层提供的系统调用,来进行的网络通信。

此处我们先对 TCP ( Transmission Control Protocol 传输控制协议)有一个直观的认识; 后面我们再详细讨论TCP的一些细节问题.
此处我们也是对 UDP ( User Datagram Protocol 用户数据报协议)有一个直观的认识; 后面再详细讨论.
因为我们暂时还没有深入了解 tcp 、 udp 协议,此处我们下面只需对概念做下了解即可
什么是有连接和无连接?
指:通信双方在交换数据之前是否需要先建立一个专门的通道。
可靠和不可靠又是指什么?
指:协议是否保证数据能够准确无误、完整地送达对方。从字面意思其实就不难看出来,但是这里要提一嘴,不可靠不一定就不好,它其实是一个中性词,存在即合理,既然不可靠还会留下来说明肯定有它的独到之处,就比如有时候我们看直播之类的,突然卡了(比如数据掉包),但是它卡了之后并不会继续刚刚的画面,有可能就是在那之后的画面了,但是这也并不会有什么影响,如果要保证可靠的话,实现肯定就要复杂很多,有时候我们不追求这种,就可以使用UDP
那什么又是面向字节流,什么又是面向数据报呢?
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分。磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分,那么如何定义网络数据流的地址呢?
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
为什么要有“网络字节序”?
不同主机可能有不同的字节序(大端机或小端机)。如果它们直接按照自己内存中的格式发送数据,就会导致严重的通信问题。
网络字节序的提出,就是为了给网络通信提供一个统一的、标准化的数据表示格式,从而消除不同平台字节序差异带来的歧义,实现异构系统之间的可靠通信
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

#include <arpa/inet.h>
// 主机字节序 -> 网络字节序 (Host to Network)
uint32_t htonl(uint32_t hostlong); // 转换32位长整数(常用于IP地址)
uint16_t htons(uint16_t hostshort); // 转换16位短整数(常用于端口号)
// 网络字节序 -> 主机字节序 (Network to Host)
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);host , n 表示 network , l 表示 32 位长整数, s 表示 16 位短整数。htonl 表示将 32 位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。📌 网络规定:所有发送到网络上的数据,都必须是大端的!
// 创建 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编程时会详细介绍这些接口。
仔细观察一下,我们可以发现大部分接口都有sockaddr结构体
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket.。然而,,各种网络协议的地址格式并不相同.

而上图中:
sockaddr_in 用于网络通信,使用IP地址和端口号作为寻址方式。
sockaddr_un 用于本地进程间通信,使用文件系统路径作为寻址方式。
socket会有很多的种类,来满足不同的应用场景,socket未来的接口,会有不同的通信接口规范,socket的设计者,只想提供一种通信接口!
可以自行区分,你到底是网络通信,还是本地通信。
if(address->sin_family ==AF_INET)
else if(address->sin_family == AF_UNIX)所以,说白了这不就是继承和多态嘛,
struct sockaddr是基类,而struct sockaddr_in,struct sockaddr_un以及struct sockaddr_in6(IPv6)等都是派生类
sockaddr 结构

sockaddr_in 结构

虽然socket api的接口是sockaddr,但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in,这个结构里主要有三部分信息:地址类型、端口号、IP地址。
in_addr结构 、

in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数
我们现在已经铺垫了一些知识,下一篇文章我们就来写代码,通过理论+代码能更好帮助我们理解网络通信,当然关于具体的协议,例如传输层的UDP和TCP协议,网络层的IP协议等,我们后面也会详细探讨