常用知识点整合
文章目录
一:为什么网络IO会被阻塞
I/O 就是指内存与外部设备之间的交互(数据拷贝)
1:服务端的通信流程
1.1:创建socket
首先服务端需要先创建一个 socket。在 Linux 中一切都是文件,那么创建的 socket 也是文件,每个文件都有一个整型的文件描述符(fd)来指代这个文件
// domain -> 这个参数用于选择通信的协议族,比如选择 IPv4 通信,还是 IPv6 通信等等
// type -> 选择套接字类型,可选字节流套接字、数据报套接字等等
// protocol -> 指定使用的协议 -> 通常可以设为 0 ,因为由前面两个参数可以推断出所要使用的协议
int socket(int domain, int type, int protocol);
比如socket(AF_INET, SOCK_STREAM, 0);
表明使用 IPv4 ,且使用字节流套接字,可以判断使用的协议为 TCP 协议
这个方法的返回值为 int ,其实就是创建的 socket 的 fd
1.2:bind
现在我们已经创建了一个 socket,但现在还没有地址指向这个 socket
众所周知,服务器应用需要指明 IP 和端口,这样客户端才好找上门来要服务
所以此时我们需要指定一个地址和端口来与这个 socket 绑定一下(让客户端知道到哪访问我)
// 参数里的 sockfd 就是我们创建的 socket 的文件描述符
// 执行了 bind 参数之后我们的 socket 距离可以被访问又更近了一步
int bind(int socketfd, const struct sockaddr *addr, socklen_t addrlen);
1.3:listen
执行了 socket、bind 之后,此时的 socket 还处于 closed 的状态,也就是不对外监听的
然后我们需要调用 listen 方法,让 socket 进入被动监听状态
这样的 socket 才能够监听到客户端的连接请求(有人访问我我才知道)
int listen(int sockfd, int backlog);
传入创建的 socket 的 fd,并且指明一下 backlog 的大小
这个 backlog 我查阅资料的时候,看到了三种解释:
- socket 有一个队列,同时存放已完成的连接和半连接,backlog为这个队列的大小
- socket 有两个队列,分别为已完成的连接队列和半连接队列,backlog为这个两个队列的大小之和
- socket 有两个队列,分别为已完成的连接队列和半连接队列,backlog仅为已完成的连接队列大小
何为半连接?
我们都知道 TCP 建立连接需要三次握手
- 当接收方收到请求方的建连请求后会返回 ack,此时这个连接在接收方就处于半连接状态
- 当接收方再收到请求方的 ack 时,这个连接就处于已完成状态
所以上面讨论的就是这两种状态的连接的存放问题。
基于 BSD 派生的系统的实现是使用的一个队列来同时存放这两种状态的连接,backlog 参数即为这个队列的大小。
而 Linux 则使用两个队列分别存储已完成连接和半连接,且 backlog 仅为已完成连接的队列大小
1.4:accept
现在我们已经初始化好监听套接字了,此时会有客户端连上来,然后我们需要处理这些已经完成建连的连接
三次握手完成后的连接会被加入到已完成连接队列中去:
这时候,我们就需要从已完成连接队列中拿到连接进行处理,这个拿取动作就由 accpet 来完成
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
这个方法返回的 int 值就是拿到的已完成连接的 socket 的文件描述符,之后操作这个 socket 就可以进行通信了。
如果已完成连接队列没有连接可以取,那么调用 accept 的线程会阻塞等待
2:客户端的通信流程
2.1:connect
客户端也需要创建一个 socket,也就是调用 socket()
,这里就不赘述了,我们直接开始建连操作。
客户端需要与服务端建立连接,在 TCP 协议下开始经典的三次握手操作,再看一下上面画的图:
客户端创建完 socket 并调用 connect
之后,连接就处于 SYN_SEND
状态,当收到服务端的 SYN+ACK
之后,连接就变为 ESTABLISHED
状态,此时就代表三次握手完毕。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
调用connect
需要指定远程的地址和端口进行建连,三次握手完毕之后就可以开始通信了。
客户端这边不需要调用 bind 操作,默认会选择源 IP 和随机端口。
用一幅图来小结一下建连的操作:
可以看到这里的两个阻塞点:
- connect:需要阻塞等待三次握手的完成。
- accept:需要等待可用的已完成的连接,如果已完成连接队列为空,则被阻塞
2.2:read, write
连接建立成功之后,就能开始发送和接收消息了,我们来看一下:
read 为读数据,从服务端来看就是等待客户端的请求,如果客户端不发请求,那么调用 read 会处于阻塞等待状态,没有数据可以读,这个应该很好理解。
write 为写数据,一般而言服务端接受客户端的请求之后,会进行一些逻辑处理,然后再把结果返回给客户端,这个写入也可能会被阻塞
read 读不到数据阻塞等待可以理解,write 为什么还要阻塞,有数据不就直接发了吗?
因为我们用的是 TCP 协议,TCP 协议需要保证数据可靠地、有序地传输,并且给予端与端之间的流量控制。
所以说发送不是直接发出去,它有个发送缓冲区,我们需要把数据先拷贝到 TCP 的发送缓冲区,由 TCP 自行控制发送的时间和逻辑,有可能还有重传什么的。
如果我们发的过快,导致接收方处理不过来,那么接收方就会通过 TCP 协议告知:别发了!忙不过来了。发送缓存区是有大小限制的,由于无法发送,还不断调用 write 那么缓存区就满了,满了就不然你 write 了,所以 write 也会发生阻塞
二:网络利器工具
1:nc
瑞士军刀,用来快速构建网络链接。常用来调试客户端程序
参数 | 描述 |
---|---|
-i | 设置数据包传送的时间间隔 |
-l | 以服务器的方式运行,默认为客户端运行 |
-k | 重复接受并处理某一个端口的所有的链接 |
-p | 以客户端运行时强调其使用指定端口 |
-C | 将CR和LF两个字符作为结束符 |
-u | 使用UDP协议 |
-X | nc客户端和代理服务器通信的时候默认为socket5协议 |
-z | 扫描目标机器某一个范围的服务是不是开启的 |
执行任务 | 执行命令 |
---|---|
扫描机器A端口在30-40的服务 | nc -z A 30-40 |
连接服务器A端口号为5000 | nc -C A 5000 |
传送文件 | MachineA:nc -v -n ip portE:\a.exe |
2:ping
用来实现对网路连通性探测。我们知道网络上机器有唯一确定的IP地址
给地方发送数据包,根据返回的信息初步判断目标机器是否存在或者目标机器操作系统是啥。
另外经常使用的Ping,底层原理是什么,是就TCP/UDP
在具体实现中其实使用了ICMP协议
,它是一种基于IP协议的控制协议,网际控制协议:
- 类型:表示ICMP的类型,如果为0表示请求类型,为8表示应答
- 代码:用来查找产生错误的原因
- 校验和:检查错误的数据
- 标识符:使用标识符确认到底是谁发送的控制协议
- 序列号:唯一确定的一个报文
ping命令组装成上述的IP报文进行发送,报文目的地为ping目的地址,原地址为发送ping主机地址,然后按照ICMP的规则填写数据
随后IP报文通过ARP协议
,请求广播到局域网络上的所有主机,并接收返回消息,以此确定目标的物理地址
常用参数 | 说明 |
---|---|
-l | 定义所发送的数据包的大小,默认是32字节 |
-n | 定义所发数据包的次数,默认是3次 |
-t | 表示不间断向目标IP发送数据包 |
可以看到ping之后,有一个返回结果是ttl,那么什么是TTL呢?
TTL 是 IP 协议包中的一个值,它告诉网络路由器包在网络中的时间是否太长而应被丢弃
- TTL设置时间越长,那么缓存时间也就越长,更新也就越不容易生效。
- 增大TTL值,可以节约域名解析时间从而加快网站的访问
- 减小TTL值,减少更换空间时的不可访问时间
如果出现超时,可能出现的情况:
- 对方已经关机或者根本没有这个地址
- 可能不在同一个网段,即使通过路由也无法找到对方从而出现超时
- 对方存在但是设置了防火墙过滤(destination host unreachable)
- 与对方不在同一个网段且没有设置默认路由
- 网线出问题了(bad ip address)
3:ifconfig/ipaddr
查看服务器网卡,IP等信息
IP地址按照小数点分割为四部分,每部分占8字节,所以IP地址为32位 -> 这个就是IPV4地址
当时觉得32位很够用了,还将其分为5类,如下图所示:
类别 | IP地址范围 | 最大主机数 | 私有IP地址范围 |
---|---|---|---|
A | 0.0.0.0 ~ 127.255.255.255 | 16777214 | 10.0.0.0 ~ 10.255.255.255 |
B | 128.0.0.0 ~ 191.255.255.255 | 65534 | 172.16.0.0 ~ 172.31.255.255 |
C | 192.0.0.0 ~ 223.255.255.255 | 254 | 192.168.0.0 ~ 192.168.255.255 |
上图中可知道c类地址太少了吧,但是B类地址又太多,怎么中和一下嘞
无类型域间选路(超网)
CIDR 地址中包含标准的32位IP地址和有关网络前缀位数的信息。
比如10.172.100.3/24,IP地址斜杠后面数字24,代表24位是网络号,后面八位为主机号
如何获取网络号?
使用IP地址 & 子网掩码 -> 网络地址
4:tcpdump
和它类似的工具在windows中是wireshark,其采用底层库winpcap/libpcap实现。
采用了bpf过滤机制。
执行任务 | 执行命令 |
---|---|
捕获特定网口数据包 | tcpdump -i eth0 |
捕获特定个数(1000)的包 | tcpdump -c 1000 -i eth0 |
将捕获的包保存到文件 | tcpdump -w a.pcap -i eth0 |
读取pcap格式的包 | tcpdump -r a.pcap |
增加捕获包的时间戳 | tcpdump -n -ttt -i eth0 |
指定捕获包的协议类型 | tcpdump -i eth0 arp |
捕获指定端口 | tcpdump -i eth0 post 22 |
捕获特定目标ip+port的包 | tcpdump -i eth0 dst address and port 22 |
捕获DNS请求和响应 | tcpdump -i eth0 -s0 port 53 |
匹配Http请求头 | tcpdump -s 0 -v -n -l | egrep -i “POST /|GET /|Host:” |
捕获特定网口数据包 | tcpdump -i eth0 |
捕获特定个数(1000)的包 | tcpdump -c 1000 -i eth0 |
将捕获的包保存到文件 | tcpdump -w a.pcap -i eth0 |
读取pcap格式的包 | tcpdump -r a.pcap |
增加捕获包的时间戳 | tcpdump -n -ttt -i et |