在【网络基础】中我们提到了IP地址,接下来了解一下网络通信中其他方面的知识
- 端口号是一个2字节16位的整数;
- 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
- 一个端口号只能被一个进程占用
(公网)IP唯一标识一台主机,这样两台主机就可以发送接收数据,但是还需要区分数据发给那个软件
各自主机上的进程由端口号(port)唯一标识
IP+端口号:表示该主机对应的服务进程在全网中是唯一的进程
软件之间的通信转换成进程,网络通信的本质就是进程间通信,客户端进程和服务端进程通过网络资源进行通信
这里仅仅是提一下,后面会结合实际、代码详细分析
TCP协议(传输控制协议)
UDP协议(用户数据报协议)
不可靠传输:如发送数据时出现了丢包的情况、或者数据被重复传递了(传递了多份)、或者网络出现了问题等等造成的后果就叫做不可靠。所以传输层就是用来解决可靠性的一个协议。
可靠是需要成本的,往往在维护和编码上都比较复杂;而不可靠没有成本,使用起来也简单。所以要分场景使用。
内存中的多字节数据相对于内存地址有大端和小端之分
如果一个大端机用大端的方式发送数据到一个小端机,网络需要识别发送方式,于是
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
- 发送主机通常将发送缓冲区中的数据
按内存地址从低到高的顺序
发出;(这里不管低地址处放的是高位还是低位)- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址
从低到高的顺序保存
;- 网络数据流的地址规定:
先发出的数据是低地址,后发出的数据是高地址
网络中接收和发送都是先低地址再高地址,解释数据的时候以大端存储来解释
网络字节序和主机字节序的转换可以直接调用库函数
#include
// 主机序列转网络序列
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
// 网络序列转主机序列
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h表示host,n表示network,l表示32位长整数,s表示16位短整数。
例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
IP地址+端口号(port)能够标识该主机上的唯一进程: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);
常见的套接字有三种:网络套接字,原始套接字,unix域间套接字
网络套接字主要运用于跨主机之间的通信,也能支持本地通信;域间套接字只能在本地通信;原始套接字可以跨过传输层(TCP/IP协议)访问底层的数据,为了方便使用,设计者只用了一套接口(小伙伴看到这里应该会想到一直常见的实现方式:多态!!!)
上图可以看到sockaddr_in和sockaddr_un是两个不同的通信场景。区分它们就用前2个字节:16位地址类型协议家族的标识符(代表是本地通信还是网络通信),但是我们选择用sockaddr这个结构体
比如要进行网络通信,虽然参数是const struct sockaddr *addr,但实际传递进去的却是sockaddr_in结构体(类型不一样,注定要进行强制类型转换)。
在函数读取sockaddr前两个字节判断是什么通信类型然后再强转回去。
综上:可以把sockaddr看成基类,把sockaddr_in和sockaddr_un看成派生类,构成了多态
本篇文章为接下来网络程序模拟实现做铺垫,接口的详细认识及sockaddr会结合代码细细讲解,关注我,为你带来更多知识