linux网络栈和tcp/ip
简单的来说,socket就是对tcp/ip的api接口。通过socket接口,两个主机建立连接后直接通过recv()和send()即可以完成两个主机之间的通信。
但是有没有想过,在TCP/IP中,在链路层,我们需要对数据包添加帧头(MAC地址信息等),IP层,我们又要为数据包添加IP头(ip地址),在传输层,我们又需要添加端口信息等。而且其中还有一些差错校验等工作我们都没有做。但是,事实上,在每一次包传输的过程中,linux网络协议栈都完成了对这些工作。而我们只需要调用内核暴露出来的系统调用(socket)就可以了
如下图所示,网络包从网卡传递到应用层可以分为如下几个步骤:
- 网卡接受到数据包后,将数据包存放到包接受队列中去,并且发出硬中断信号
- 硬中断处理程序将数据包,存放到sk_buff缓冲区中,并且发出软中断信息
- 内核收到软中断后,将sk_buff中取出传递给linux网络协议栈
- 经过网络协议栈的一些列的处理,将数据存放到了socket缓存中
- 应用程序通过调用socket接口从socket缓存中取出数据
小实验
# 一台服务端server,两个客户端c1, c2,os-centos7
# server
[root@master ~]# python
Python 2.7.5 (default, Apr 9 2019, 14:30:50)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> s = socket.socket()
>>> s.bind(("x.x.x.x", 20000)) # 绑定端口
>>> s.listen(10) # 监听该端口
# 在服务端另外打开一个端口,输入以下命令
# netstat -tnlp | grep 20000
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 x.x.x.x:20000 0.0.0.0:* LISTEN 172870/python
# 发现server端开始监听20000端口,从recv-q和send-q为0可以看出此时还没有客户发起连接请求
# 注:在listen状态下recv-q表示半连接状态的个数,即客户端发送了连接请求,但服务端还没有发ack报文
# 打开一个c1客户端
>>> import socket
>>> s = socket.socket()
>>> s.connect(("x.x.x.x", 20000))
# 在c1客户端中发送连接请求
# 回到server打开另一终端,输入如下命令
[root@master ~]# netstat -atnp | grep 20000
tcp 1 0 x.x.x.x:20000 0.0.0.0:* LISTEN 172870/python
tcp 0 0 x.x.x.x:20000 x.x.x.x:39332 ESTABLISHED -
# 再打开另外一个客户端c2
>>> import socket
>>> s = socket.socket()
>>> s.connect(('x.x.x.x', 20000))
# 回到server中,输入如下命令
# netstat -atnp | grep 20000
tcp 2 0 x.x.x.x:20000 0.0.0.0:* LISTEN 172870/python
tcp 0 0 x.x.x.x:20000 x.x.x.x:47890 ESTABLISHED -
tcp 0 0 x.x.x.x:20000 x.x.x.x:39332 ESTABLISHED -
# 此时发现recv-q中字段为2,表示半连接的个数,即表示客户端发送了连接请求,但还没得到服务端的ack报文。半连接状态在linux也有也显示,但看不到对应的进程(对比下面建立时的区别)
# 回到server中的python终端窗口
>>> import socket
>>> s = socket.socket()
>>> s.bind(("x.x.x.x", 20000))
>>> s.listen(10)
>>> c1, addr = s.accept() # 从半连接队中,选择一个半连接,发送确定帧,形成连接
>>> c2, addr2 = s.accept()
# 回到server中终端窗口
[root@master ~]# netstat -atnp | grep 20000
tcp 0 0 x.x.x.x:20000 0.0.0.0:* LISTEN 172870/python
tcp 0 0 x.x.x.x:20000 x.x.x.x:47890 ESTABLISHED 172870/python
tcp 0 0 x.x.x.x:20000 x.x.x.x:39332 ESTABLISHED 172870/python
# 发现监听状态的端口的recv-q字段变为0了,并且下方显示成功建立的连接。
# 去其中一个client发送一下数据
>>> import socket
>>> s = socket.socket()
>>> s.connect(("x.x.x.x", 20000))
>>> s.send('today is a good day')
19
# 回到master的termal
[root@master ~]# netstat -atnp | grep 20000
tcp 0 0 x.x.x.x:20000 0.0.0.0:* LISTEN 172870/python
tcp 0 0 x.x.x.x:20000 x.x.x.x:47890 ESTABLISHED 172870/python
tcp 19 0 x.x.x.x:20000 x.x.x.x:39332 ESTABLISHED 172870/python
# 注意,在establish状态时,recv-q表示的接收缓冲去中还有多少数据没有被取走,发现正好是上面输入的19个字符还没有被取走
# 回到master中python终端
>>> import socket
>>> s = socket.socket()
>>> s.bind(("x.x.x.x", 20000))
>>> s.listen(10)
>>> c1, addr = s.accept()
>>> c2, addr2 = s.accept()
>>> c1.recv(5) # 从接受队列中取走五个字符的数据
'today'
# 回到server的终端
[root@master ~]# netstat -atnp | grep 20000
tcp 0 0 x.x.x.x:20000 0.0.0.0:* LISTEN 172870/python
tcp 0 0 x.x.x.x:20000 x.x.x.x:47890 ESTABLISHED 172870/python
tcp 14 0 x.x.x.x:20000 x.x.x.x:39332 ESTABLISHED 172870/python
# 发现19变成14了,正好符合实验预期
#回到一个client终端,关闭一端的连接
>>> s = socket.socket()
>>> s.connect(("x.x.x.x", 20000))
>>> s.send('today is a good day')
19
>>> s.close()
# 回到server终端
[root@master ~]# netstat -atnp | grep 20000
tcp 0 0 x.x.x.x:20000 0.0.0.0:* LISTEN 172870/python
tcp 0 0 x.x.x.x:20000 x.x.x.x:47890 ESTABLISHED 172870/python
tcp 14 0 x.x.x.x:20000 x.x.x.x:39332 CLOSE_WAIT 172870/python
# 符合tcp的要求,关闭tcp连接时,需要双方都要关闭,此时仅客户端关闭了,也要到server端去关闭连接
# 回到server终端
>>> import socket
>>> s = socket.socket()
>>> s.bind(("x.x.x.x", 20000))
>>> s.listen(10)
>>> c1, addr = s.accept()
>>> c2, addr2 = s.accept()
>>> c1.recv(5)
'today'
>>> c1.close()
# 回到server终端
[root@master ~]# netstat -atnp | grep 20000
tcp 0 0 x.x.x.x:20000 0.0.0.0:* LISTEN 172870/python
tcp 0 0 x.x.x.x:20000 x.x.x.x:47890 ESTABLISHED 172870/python
# 连接完全关闭了
linux常见网络命令
- nslookup: 查看域名的ip地址
- /etc/resolv.conf: 设置DNS服务器
- time:查看一个命令的运行时间
- wrk和ab: http压力测试工具
- sar: 查看网络每秒的传输数据
- /etc/sysctl.conf: 配置linux网络协议栈参数(sysctl -p 是配置生效)
配置内容 | 配置描述 |
---|---|
net.ipv4.ip_forward | linux内核是否可以转发数据包 |
net.ipv4.tcp_max_sys_backlog | tcp半连接最大数目 |
net.ipv4.tcp_xx | 一些列和tcp相关的参数 |
net.ipv4.udp_xx | 一些列和udp相关的参数 |
net.core.rmem.max | 套接字接受缓冲区大小 |
net.core.wmem_max | 套接字发送缓冲区大小 |
- iptables: 数据包转发的控制
- ip: 查看ip地址情况
- ifconfig: 查看网卡及ip地址,还有网络接受/发送包的个数和字节数
小实验
# cat /etc/resolv.conf # 查看系统配置的DNS服务器
# Generated by NetworkManager
nameserver 202.115.32.36
nameserver 8.8.8.8
[root@master ~]# time nslookup www.baidu.com
Server: 202.115.32.36 # DNS服务器地址
Address: 202.115.32.36#53 # DNS服务器地址及其监听的端口
Non-authoritative answer:
www.baidu.com canonical name = www.a.shifen.com.
Name: www.a.shifen.com # 域名
Address: 182.61.200.6 # 地址
Name: www.a.shifen.com
Address: 182.61.200.7
real 0m0.011s # time命令所展示的时间,实际用了0.011秒
user 0m0.005s
sys 0m0.006s
# 换一个DNS服务器
[root@master ~]# echo "nameserver 8.8.8.8" > /etc/resolv.conf && cat /etc/resolv.conf
nameserver 8.8.8.8
[root@master ~]# time nslookup www.baidu.com
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
www.baidu.com canonical name = www.a.shifen.com.
www.a.shifen.com canonical name = www.wshifen.com.
Name: www.wshifen.com
Address: 103.235.46.39
real 0m0.290s # 刚刚是0.011秒,现在是0.29秒啊,慢了好多啊!还是换回来把
user 0m0.008s
sys 0m0.007s
# echo "nameserver 202.115.32.36" > /etc/resolv.conf
网友评论