0%

Net namespace实验

在 Linux 中,网络名字空间可以被认为是隔离的拥有单独网络栈(网卡、路由转发表、iptables)的环境。网络名字空间经常用来隔离网络设备和服务,只有拥有同样网络名字空间的设备,才能看到彼此。 network namespace 是实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,它们有独自的网络栈信息。不管是虚拟机还是容器,运行的时候仿佛自己就在独立的网络中

常用命令

comm 命令
ip netns add net1 添加namespace net1
ip netns help 获取帮助
ip netns del n1 删除namespace n1
ip netns ls 列出当前已有namespace
与net namespace相关的指令是ip netns后面跟具体指令
使用ip netns exec name子命令后面可以加上任何命令,表示在相应的namespace中执行相关命令,如:
1
2
3
root@mininet-vm:/home/mininet# ip netns exec n2 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
可以执行ip netns exec n2 bash,之后所有指令都在指定namespace中执行而不需要加上ip netns exec name
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
root@mininet-vm:/home/mininet# ip netns exec n2 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
root@mininet-vm:/home/mininet# ip netns exec n2 bash
root@mininet-vm:/home/mininet# ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
root@mininet-vm:/home/mininet# exit
exit
root@mininet-vm:/home/mininet# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast
.................
使用ip netns exec n2 bash --rcfile <(echo "PS1=\"namespace ns1>\"")可以修改命令行的前缀。
1
2
root@mininet-vm:/home/mininet# ip netns exec n2 bash --rcfile <(echo "PS1=\"namespace n2>\"")
namespace n2>
## namespace通信
### 使用 veth pair 进行通信
1. 创建一对veth pair
使用命令ip link add type veth创建一对veth pair,其默认名是veth0和veth1,使用ip link可查看链接
1
2
3
4
5
6
7
8
9
10
11
   root@mininet-vm:/home/mininet# ip link
.....
3: ovs-system: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN
root@mininet-vm:/home/mininet # ip link add type veth
root@mininet-vm:/home/mininet # ip link
....
3: ovs-system: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN
9: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 12:e8:a5:43:c0:43 brd ff:ff:ff:ff:ff:ff
10: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 9e:f8:c3:b9:af:ec brd ff:ff:ff:ff:ff:ff
2. 将veth pair的两端分别放到两个namespace
使用命令ip link set veth0 netns n1ip link set veth1 netns n2分别将veth0和veth1放到不同namespace
1
2
3
4
5
6
7
8
9
10
11
12
oot@mininet-vm:/home/mininet# ip link set veth0 netns n1
root@mininet-vm:/home/mininet# ip link set veth1 netns n2
root@mininet-vm:/home/mininet# ip netns exec n1 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
9: veth0@if10: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 12:e8:a5:43:c0:43 brd ff:ff:ff:ff:ff:ff
root@mininet-vm:/home/mininet# ip netns exec n2 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
10: veth1@if9: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 9e:f8:c3:b9:af:ec brd ff:ff:ff:ff:ff:ff
3. 为veth pair的两端分别配置ip
使用命令ip link set vethX upip addr add 10.0.0.10/24 dev vethX为veth pair配置ip,结果如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# namespace 1
namespace ns1> ip link set veth0 up
namespace ns1> ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
9: veth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 12:e8:a5:43:c0:43 brd ff:ff:ff:ff:ff:ff
namespace ns1> ip addr add 10.0.10.1/24 dev veth0
namespace ns1> ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
9: veth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 12:e8:a5:43:c0:43 brd ff:ff:ff:ff:ff:ff
inet 10.0.10.x1/24 scope global veth0
valid_lft forever preferred_lft forever

# namespace 2
namespace n2>ip link set veth1 up
namespace n2>ip addr add 10.0.10/24 dev veth1
namespace n2>ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
10: veth1@if9: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state LOWERLAYERDOWN group default qlen 1000
link/ether 9e:f8:c3:b9:af:ec brd ff:ff:ff:ff:ff:ff
inet 10.0.10.0/24 scope global veth1
valid_lft forever preferred_lft forever
4. 测试两个namespace之间的网络联通状态
分别在n1和n2中尝试ping
1
2
3
4
5
6
namespace ns1> ping 10.0.10.0 -c 1
PING 10.0.10.0 (10.0.10.0) 56(84) bytes of data.
64 bytes from 10.0.10.0: icmp_seq=1 ttl=64 time=0.035 ms
namespace n2>ping 10.0.10.1 -c 1
PING 10.0.10.1 (10.0.10.1) 56(84) bytes of data.
64 bytes from 10.0.10.1: icmp_seq=1 ttl=64 time=0.040 ms
5. 其拓扑结构如下
20190725185608.png
veth pair可以用于两个namespace之间的通信,但不适合用在多个namespace之间的通行
### 利用bridge通信
1. 在以上实验基础上,重新创建两个namespace:n3、n4
1
2
3
4
5
6
7
root@mininet-vm:/home/mininet# ip netns add n3
root@mininet-vm:/home/mininet# ip netns add n4
root@mininet-vm:/home/mininet# ip netns ls
n4
n3
n1
n2
2. 创建bridge
1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@mininet-vm:/home/mininet# ip link add br0 type bridge
root@mininet-vm:/home/mininet# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:1e:27:79 brd ff:ff:ff:ff:ff:ff
inet 192.168.117.128/24 brd 192.168.117.255 scope global eth0
valid_lft forever preferred_lft forever
3: ovs-system: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default
link/ether 72:f5:e5:5d:4d:ed brd ff:ff:ff:ff:ff:ff
11: br0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default
link/ether 76:d8:06:1a:b9:84 brd ff:ff:ff:ff:ff:ff
3. 利用veth pair将bridge与n3、n4、n1连通
创建3对veth pair
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
root@mininet-vm:/home/mininet# ip link add type veth
root@mininet-vm:/home/mininet# ip link add type veth
root@mininet-vm:/home/mininet# ip link add type veth
root@mininet-vm:/home/mininet# ip a
...
11: br0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default
link/ether 76:d8:06:1a:b9:84 brd ff:ff:ff:ff:ff:ff
12: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether ea:98:b6:3c:46:60 brd ff:ff:ff:ff:ff:ff
13: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether f2:f6:d8:6b:31:1f brd ff:ff:ff:ff:ff:ff
14: veth2@veth3: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 4a:7d:af:18:67:14 brd ff:ff:ff:ff:ff:ff
15: veth3@veth2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether ca:b6:e4:eb:b7:15 brd ff:ff:ff:ff:ff:ff
16: veth4@veth5: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether f2:b3:5f:0e:3d:09 brd ff:ff:ff:ff:ff:ff
17: veth5@veth4: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 76:0c:87:b1:16:80 brd ff:ff:ff:ff:ff:ff
将br0和n1,n3,n4连接
这时候如果把veth0加入n1的话会报错,因为n1里面已经有了一个veth0,可以换成其他名称的veth。
将veth pair放如br0的指令为ip link set dev veth3 master br0
4. 测试n1-n4之间的连通状态
发现不同namespace之间无法ping通 。
这个问题折腾了很久,后来多配置了几次有可以了。应该是之前漏掉了几个步骤,完整的步骤应该包括以下几步:
1
2
3
4
5
6
7
8
9
10
11
12
 # 启动网桥(网桥只需要启动一次就行)
ip link set br0 up
# 创建vethpair
ip link add br-1 type veth peer name 1-br
#将vethpair分配给网桥和namespace
ip link set br-1 master br0
ip link set 1-br netns n1
#启动veth
ip link set br-1 up
ip netns exec n1 ip link set 1-br up
# 为namespace中的veth设置ip
ip netns exec n1 ip addr add 10.0.10.2/24 dev 1-br
重新测试,发现三个namespace可以相互ping通。
上述网桥对应的veth的ip其实可以省略
  1. 其拓扑结构如下

namespace内部与namespace外部通信

默认情况下,namespace网络是隔离的,namespace内无法ping通namespace外的网络,可以通过veth pair打通网络状态。
当veth pair一端在namespace内部,一端在namespace外部时,namespace可以ping通位于外部的veth pair但无法ping同其他网络。

参考资料

https://cizixs.com/2017/02/10/network-virtualization-network-namespace/

mininet:vxlan实验

需改虚拟机的网络适配器,将其改为host-only

尝试ping宿主机ip地址,此时能够ping同与虚拟机相连的虚拟网卡ip地址,无法ping同其他网卡ip地址

在虚拟机和宿主机中创建网络topo

在虚拟机运行以下指令创建topo

1
sudo  mn

此时,在虚拟机中含以下网络节点

1
2
3
4
hosts:  h1 h2 
switches: s1
links: (h1, s1) (h2, s1)
controller:c0

其中h1和h2的ip分别为10.0.0.110.0.0.2

在宿主机中创建topo网络

新建topo-2sw-2host.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from mininet.topo import Topo

class MyTopo( Topo ):
"Simple topology example."

def build( self ):
"Create custom topo."

# Add hosts and switches
h3 = self.addHost( 'h3',ip='10.0.0.3' )
h4 = self.addHost( 'h4',ip='10.0.0.4' )
leftSwitch = self.addSwitch( 's3' )
# Add links
self.addLink( h3, leftSwitch )
self.addLink( leftSwitch,h4)
topos = { 'mytopo': ( lambda: MyTopo() ) }

通过命令sudo mn --custom topo-2sw-2host.py --topo mytopo创建网络topo
其中h1和h2的ip分别为10.0.0.310.0.0.4

关闭控制器

控制器能够帮助switch建立流表,如果要手动建立nodes链接关系,需要先关闭控制器。

注意:也可以在mn命令的最后添加 --controller remote关闭controller,以省略以下步骤
在关闭控制器前测试h1与h2之间互ping,因为controller的存在,彼此能够ping通。
在宿主机和虚拟机中分别输入sudo killall controller关闭控制器,这时候再尝试ping,发现无法ping通。因为switch中流表为空。
执行sh ovs-ofctl dump-flows s1,可以看到流表为空,其信息如下

1
2
mininet> sh ovs-ofctl dump-flows s1
NXST_FLOW reply (xid=0x4):

创建vxlan

运行sh ovs-vsctl add-port s1 vxlan添加vxlan端口。
在虚拟机中,端口可以成功成功创建,但在宿主机中创建失败,提示错误信息:

1
2
3
mininet> sh ovs-vsctl add-port s13vxlan
ovs-vsctl: Error detected while setting up 'vxlan': could not open network device vxlan (No such device). See ovs-vswitchd log for details.
ovs-vsctl: The default log directory is "/var/log/openvswitch".

解决方案如下

1
2
The port‘s name should be a exist interface use ifconfig to see, such as eth0. If you just want to use a virtual port name to make a test you should 
specify the port's type like ovs-vsctl add-port br0 port0 -- set Interface port0 type=internal or ovs-vsctl set Interface port0 type=internal

将指令改为

1
sh ovs-vsctl add-port s3 vxlan   -- set Interface vxlan type=internal

在虚拟机中输入以下指令

1
sh ovs-vsctl set interface vxlan type=vxlan option:remote_ip=10.0.0.7 option:key=100 ofport_request=10

其中 ip地址是宿主机的ip,key是vxlan的标签,opport_request是端口名称
在宿主机中需要将以上命令的ip改为虚拟机的ip地址。

总结

以上建立的网络形式如下
20190725110710.png
其中R为路由器,s1和s2为交换机,h1-h4为主机

同一交换机下的主机通信

20190725113623.png

  1. h1尝试ping h2时,拥有h2的ip地址但没有h2的mac地址。因此h1首先尝试通过arp获取h2 mac地址。
  2. h1 发送 arp给 s1,s1收到arp后进行广播,同时记录h1的mac地址和端口的映射。h2收到广播后发现与自己的ip一致,根据广播给定的ip地址向s1发送响应,s1将响应发送给h1,同时s1记录h2的mac地址和端口的映射。
  3. h1获取h2的mac地址后,在包里面加入h2的ip地址和mac地址并发送给s1.
  4. s1收到包后,检查mac地址,找出对应的h2端口映射,将包发送到h2对应的端口
  5. h2收到包后,匹配包的ip地址,ip地址一致则对包做进一步处理

地址解析协议(ARP)

地址解析协议(ARP)是指网络地址和MAC地址之间的转换
当一台主机需要向另一台主机发送数据时,需要知道目的主机的ip地址外还需要知道目的主机的mac地址。源主机首先会在自己的ARP表中寻找目的ip对应的mac地址,无法找到则发出一个arp广播包,目的主机收到广播包后发送一个响应报文告知源主机自己的mac地址。

交换机对包的处理:

当交换机收到包时,会查找交换机表,其可能遇到三种情况:

  1. 未找到相应记录,广播该包,将该包发送给除源端口外的所有端口
  2. 找到相应记录,且对应端口与源端口不同,将该包转发给相应端口
  3. 找到相应端口,但相应端口与源端口相同,将该包丢弃

    路由器对ARP的处理

    路由器会阻断包括ARP在内的广播包,也就是说主机无法通过以上的方式获取目的主机的mac地址。
    当两台主机位于不同子网或网段时,主机之间无法通过交换机通信,这个时候必须借助网关或者路由器。当主机发现访问的主机位于不同网段时,主机会将包发送给路由器,在通过路由器转发该包。
    20190725162055.png
    上图中,H1主机发送数据包给H2,在传输过程中,目的ip始终为h2的ip,但目的mac地址是下一跳的mac地址。
    需要注意的是,图中没有注明每个路由器的ip地址,而在实际网络中,路由器内含有一个交换表注明目的ip与下一跳ip的映射,路由器底层将下一跳的ip进行转化以获取下一跳的mac地址。

neutron的作用是实现虚拟机之间的网络通信及其管理功能,其中,虚拟机可是位于多台服务器,也可以在一台服务器上,虚拟机之间可以划分LAN,同一LAN下的虚拟机可以相互通信,不同LAN的虚拟机之间是不可见的,同一LAN的虚拟机可以位于不同的服务器。Neutron 实现了基于物理 VLAN 交换机的跨物理服务器二层虚拟网络。在同一服务器下的相同LAN虚拟机通过虚拟网桥或交换机进行通信,不同服务器之间的虚拟机通过物理交换机通信。

NAT

NAT(Network Address Transorm)网络地址转换协议,用于内网和外网之间的地址转换。

SNAT(Source Network Address Transform)源网络地址转化。当内部地址需要访问外部服务时,内部地址会主动发起链接,由路由器或者网关对内部地址做源地址转化

DNAT(Destination Network Address Transform)目的地址转化。当外部网络需要访问内部服务时,由路由器或者网关接受外部链接,然后将外部链接转化为内部链接

网络拓扑结构

在典型的网络生产环境中,一般将网络分为一下4种:

  1. 公共网络,即外网网络
  2. 管理网络,即OpenStack自身组件使用的网络
  3. 服务网络,即虚拟机所使用的内网网络
  4. 存储网络,存储专用网络,一般和管理网络同网段。

Neutron支持一下5种网络拓扑结构

  1. 单一平面网络是指所有租户共享一个相同的服务网络和公共网络,意味着一个租户下的虚拟机能够访问其他租户下的虚拟机,隔离性不好。
  2. 多平面网络也是服务网络和公共网络相同,但是可以有多个共享网络,当两个虚拟机需要通信时,虚拟机内部可以通过多网卡同时共享一个网段,这些共享网段对所有租户共享。
  3. 混合平面私有网络是指每个租户拥有自己的服务网络同时共享相同的公共网络。
  4. 通过私有网络实现运营商路由功能,是指每个租户拥有自己的服务网络,并且虚拟机可以直接通过提供商路由所提供的SNAT功能访问外部网络命名空间
  5. 通过私有网络实现每个租户创建自己专属的网络专区,支持命名空间,每个网络可以选择拥有自己独立的提供商路由——一般来说是虚拟路由

VLAN

VLAN 表示 Virutal LAN。一个带有 VLAN 功能的switch 能够同时处于多个 LAN 中。最简单地说,VLAN 是一种将一个交换机分成多个交换机的一种方法。比方说,你有两组机器,group A 和 B,你想配置成组 A 中的机器可以相互访问,B 中的机器也可以相互访问,但是A组中的机器不能访问B组中的机器。你可以使用两个交换机,两个组分别接到一个交换机。如果你只有一个交换机,你可以使用 VLAN 达到同样的效果。你在交换机上分配配置连接组A和B的机器的端口为 VLAN access ports。这个交换机就会只在同一个 VLAN 的端口之间转发包。

以太网端口有三种链路类型:Access、Hybrid和Trunk。

  • Access类型的端口只能属于1个VLAN,一般用于连接计算机的端口。这种类型的端口允许接收没有打标签的帧,再发出去时将会被打上标签。
  • Trunk类型的端口可以属于多个VLAN,可以接收和发送多个VLAN的报文,一般用于交换机之间连接的端口
  • Hybrid类型的端口可以属于多个VLAN,可以接收和发送多个VLAN的报文,可以用于交换机之间连接,也可以用于连接用户的计算机。Hybrid端口和Trunk端口的不同之处在于Hybrid端口可以允许多个VLAN的报文发送时不打标签,而Trunk端口只允许缺省VLAN的报文发送时不打标签。

标签是为了方便交换机识别传输的数据属于哪个LAN,对于计算机来说,其只接受其所在的LAN的数据帧,而筛选帧的过程是由交换机完成的。由于帧所属LAN的判断是由交换机完成而非计算机,因此,用于与计算机相连的Acess中的帧不含标签,而用于交换机与交换机之间的Trunk必须包含的标签。

概念

valn 虚拟lan,同一个物理lan下用标签实现隔离
vxlan 一套利用UDP协议作为底层传输协议的Overlay实现。一般认为作为VLan技术的延申或替代者。
gre 一种通过封装来实现隧道的方式。在openstack中一般是基于L3的gre
TAP设备 比如vnet0,是虚拟化技术如KVM和Xen用来实现虚拟网卡的。当一个以太网帧发送给TAP设备时,该帧会被虚拟机操作系统所接收。
VETH pair veth配对设备或OVS配对端口(patch port),是一对一直相连的虚拟网络接口。在虚拟网络技术中,经常使用veth pair作为虚拟配对线缆来连接两个虚拟网桥

Unix网络编程卷1学习笔记

第3章 套接字编程简介

数据类型

数据类型 说明
sa_family_t 套接字地址结构的地址族
socklen_t 套接字地址结构的长度,一般为uint32_t
in_addr_t IPv4地址,一般为uint32_t
in_port_t TCP或UDP端口,一般为uint16_t

字节排序函数

大端和小端的区别

网络字节序:大端

函数:

1
2
3
4
uint16_t htons(uint16_t)
uint32_t htonl(uint32_t)
uint16_t ntohs(uint16_t)
uint32_t ntohs(uint32_t)

字节操纵函数

1
2
3
4
5
6
bzero(void*, size_t)   //归零
bcopy(const void*src, void *dest,size_t nbytes) //复制
bcmp(const void*, const void*, size_t) //比较
memsetvoid*, int,size_t) //归零
memcpy(void*,const void*,size_t) //复制
memcmp(const void*,const void*,size_t) //比较

地址转换函数

函数 返回 说明
int inet_aton(const char*, struct in_addr*) 1:有效 将c字符串转换成一个32位的网络字节序二进制
in_addr_t inet_addr(const char*) 有效:32未二进制网络字节序的IPv4地址
无效:INADDR_NO
==已废弃==。 与 inet_aton 作用相同,不能处理有限广播地址255.255.255.255
char* inet_ntoa(sturct in_addr) 指向一个点分十进制数串的指针 将32位的网络字节序二进制IPv4转换为相应的点分十进制数串
int inet_pton(int,const char*, void*) 成功:1 输入不是有效格式:0 出错:-1 字符串转换,字符串->二进制
const char* inet_ntop(int,const void*, char*, size)t 成功:指向结果的指针 出错:NULL 字符串转换,二进制->字符串

p:表达(presentation)|字符串 n:数值(numeric)|二进制

errno取值:EINTR

说明 系统调用被一个捕获的信号中断

第4章 基本套接字编程

基本套接字函数

函数 说明
socket(int family, int type, int protocol) 指定期望的通信协议类型,成功则返回套接字描述符(sockfd)
connect(int sockfd, const struct sockaddr* servaddr, socklen_t addrlen) 与服务器建立连接,connect失败则该套接字不再可用,需要close后再socket
bind(int sockfd, const struct sockaddr* myaddr,socklen_t addrlen) 把一个本地协议地址赋予一个套接字。未调用bind时,使用connect或listen,内核将为相应套接字分配临时端口
listen(int sockfd,int backlog) 由服务器调用,调用时使得套接字由CLOSED转为LISTEN状态,同时规定套接字排队的最大连接个数
accept(int sockfd, struct sockaddr* cliaddr, socklen_t *addr_len) 由服务器调用,用于从已完成连接队列队头返回下一个已完成连接,若已完成连接队列为空,则进程被投入睡眠
int close(int sockfd) 关闭套接字,成功返回0.失败返回-1
int getsockname(int sockfd, struct sockaddr* localaddr,socklen_t* addrlen) 获取与套接字关联的本地协议地址,成功返回0,失败返回-1
int getpeername(int sockfd,struct sockaddr*peeraddr, socklen_t* addrlen) 获取与套接字关联的外地协议地址,成功返回0,失败返回-1
#### connect

  • 硬错误:指定端口上没有进程在等待与之连接
  • 软错误:目的地不可达

accept

accept函数调用成功时,返回由内核生成的全新描述符,代表与所返回客户的连接。

监听套接字描述符:一个服务器通常只创建一个监听套接字,由socket创建

已连接套接字描述符:内核为每个由服务器进程接受的客户连接创建一个已连接套接字,由accept创建

进程相关函数

函数 说明
pid_t fork() 创建新进程,父进程返回子进程进程id,子进程返回0
exec execl, execv, execle, execve, execlp, execvp

总结

了解网络编程基本的套接字函数,知道常用函数的作用及调用顺序,

这章内容主要集中在前面几个基本的套接字函数上,对后面的并发服务器的基本内容大概了解,并发服务器主要和进程创建有关,这部分内容之前了解过一些。

  • 什么是套接字和套接字描述符
  • 常用的socket、connect、bind、listen、accept函数的用途?

第5章 TCP客户/服务器示例

  • 端口查看命令
1
netstat -a | grep 9877

信号处理

  • SIGCHLD – 子进程终止时,发送给父进程

总结

  • fork子进程时,必须捕获SIGCHLD信号
  • 捕获信号时,必须处理被中断的系统调用
  • SIGCHLD的信号处理函数必须正确编写,应使用waitpid函数以免留下僵死进程​

第六章 I/O复用

总结

  • 第一遍看不懂,大概讲I/O复用的内容用于解决内核堵塞与某个输入输出的问题

实验

test1 时间获取服务器

实现简单的时间获取服务器和客户端

问题1 客户端例程无法读取服务器返回数据,返回读取错误(test1)

  • 原因分析:

    1. 服务器没有正确开启
    2. 客户端配置错误
  • 解决方案:

    1. 对照源代码,确定配置正确
    2. 检测服务器是否开启
  • 过程:

    1. 对比发现客户端少调用了connect函数,未与服务器建立连接
    2. 查看端口信息,使用netstat -anp未发现程序打开的端口
    3. 仔细对照源代码,发现端口的字节序汉书调用错误,调用了htonl,实际应为htons
  • 结论

    1. 客户端需要调用socket、connect函数
    2. 服务器需要调用socket、bind、listen、accpet函数
    3. 注意字节排序函数的调用,l和s所获取到的结果不同

test2 回射客户/服务器

q1 read堵塞

  • 使用read/write读取网络字节时,需要注意堵塞情况的发生。

    第十三章 守护进程和inetd超级服务器

    ###守护进程转化步骤
  1. 创建子进程
  2. 调用setid函数,为子进程创建新会话,从此不再有控制终端
  3. 在子进程中再次调用fork函数创建子进程,(确保新进程不会成为终端设备的控制终端)
  4. 改变工作目录,将工作目录改为根目录,活根据需要改为其他目录。(守护进程运行时,该工作目录所在的文件系统无法正常拆卸)
  5. 关闭所有打开的文件描述符。父进程中可能打开了守护进程不需要的文件描述符,因此需要关闭多余的描述符。
  6. stdin,stderr,stdout重定向到dev/null。(当守护进程打开与客户关联的套接字时,该套接字可能会打开这些描述符,导致将非预期数据发送给客户)

    总结

  • 守护进程是指进程运行于后台,与控制终端无关
  • inetd进程用于简化守护进程工作方式,该进程监听了所有守护进程所需套接字,当某个套接字受到客户请求时,inetd进程将创建子进程由于专门处理此请求,在子进程中调用预先设定的程序。
  • 对UDP链接,在子进程接管该套接字后,inetd进程应屏蔽对该套接字的检测,否则可能会产生多个子进程。

线程

线程

线程创建

1
2
3
4
5
6
7
8
9
10
11
12
#include<pthread.h>
int pthread_creat( //创建线程,成功返回0,失败返回errcode
pthread_t *thread, //线程号
pthread_attr_t *attr, //
void *(*func)(void*), //线程需要运行的函数
void *arg //传递给func的参数
)

int pthread_join( //等待线程终止,成功返回0,失败返回errcode
pthread_t pthread, //所等待的线程号
void* retval //指向线程返回值
)

在linux中使用pthread.h头文件需要连接lpthread库

线程池

组成成分

  1. 线程池管理器
  2. 工作线程
  3. 任务接口
  4. 任务队列

工作流程

1
2
3
4
5
6
7
1. 初始化线程池
2. 堵塞任务线程
2. 将任务添加进人物队列
3. 判断是否有空闲线程
4. 唤醒线程
5. 完成任务
6. 重新堵塞线程

常见线程池

  • 单线程池

    每次只有一个线程工作

  • 固定线程池

    线程池中线程的数量固定,当达到线程池最大数量时,后续任务进入等待队列

  • 可缓存线程池

    当任务超出线程数量时,添加线程,当线程池空闲线程数量过多时,回收部分空间

  • 无限制线程池

    线程池大小无限制,支持定时和周期性执行

优点

1
2
3
1. 控制线程产生数量,控制线程对象的内存消耗
2. 降低系统开销和资源消耗
3. 提高系统响应速度

通信

线程

进程

  • 命名通道 1. 通道是一个队列而不是常规文件 2. 其读和写是原子操作
  • 共享内存 1. 共享内存段不依赖于进程存在 2. 共享内存段的名字叫关键字 3. 关键字是一个整型数 4. 共享内存段有自己的拥有者己权限位 5. 进程可以连接共享内存段,并获得指针
  • 文件锁
  • 信号量
  • 相关函数 1. select、poll 2. mkfio 3. shmget、shmat、shmctl、shmdt(共享内存) 4.semget、semctl、semop

同步

互斥量

1
2
3
4
5
6
7
8
#include <pthread.h>
int pthread_mutex_lock( //等待互斥锁解开后锁住互斥量,成功返回0
pthread_mutex_t *mutex //指向互斥锁
)

int pthread_mutex_unlock( //解锁互斥量,成功返回0
pthread_mutex_t *mutex, //指向互斥量
)

条件变量

1
2
3
4
5
6
7
8
#include <pthread.h>
int pthread_cond_wait( //挂起线程,等待信号量,成功返回0
pthread_cond_t *cond, //
pthread_mutex_t *mutex, //指向互斥锁对象
)

int pthread_cond_signal( //唤醒等待线程,成功返回0
pthread_cond_t *cond) //指向条件变量

pthread_cond函数总是和互斥锁在一起使用。此函数先自动释放指定的锁,然后等待条件变量的变化。如果在调用此函数之前,互斥量mutex并没有被锁住,函数执行的结果是不确定的。在返回原调用函数之前,此函数自动将指定的互斥量重新锁住。

信号量

1
2
3
4
5
6
#include <semaphore.h>
int sem_init(sem_t *sem, //sem地址
int pshared, //0表示本进程中多个线程间同步,非0表示可以跨进程的同步操作
unsigned int value); //信号量初值(计数器的值)
P int sem_wait(sem_t *sem); // sem-1 如果小于0就阻塞
V int sem_post(sem_t *sem); // sem+1

读写锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//初始化和销毁
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
// 成功则返回0, 出错则返回错误编号.

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
// 成功则返回0, 出错则返回错误编号.

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
// 成功则返回0, 出错则返回错误编号.

安全

在多线程程序中,很有可能会将同一个文件描述符传递给两个不同的线程。即传递给它们的两个值指向同一个文件描述符。显然如果一个线程中的函数关闭了这个文件,此文件描述符对此进程中的任何线程来说都已经被关闭。

fork创建了一个新的进程,并把原调用进程的数据和代码复制给这个新的进程。如果线程中的某函数调用了 fork,只有调用fork的线程在新的进程中运行

总结

-[ ] select和epoll

基础篇

第二章 主机规划与磁盘分区

各硬件在linux中的文件名

设备 文件名
SCSI/SATA/USB硬盘机 /dev/sd[a-p]
USB闪存 /dev/sd[a-p]
virtl/IO界面 /dev/vd[0-7] (用于虚拟机内)
软盘机 /dev/fd[0-7]
打印机 /dev/lp[0-2] (15针) /dev/usb/lp[0-15] (USB)
鼠标 /dev/input/mouse[0-15] /dev/psaux /dev/mouse(当前鼠标)
CDROM/DVDROM /dev/scd[0-1] /dev/sr[0-1] /dev/cdrom
磁带机 /dev/ht0 /dev/st0 /dev/tape
IDE硬盘机 /dev/hd[a-d]

主分区和逻辑分区

主分区: 分区信息记录在分区表内,分区表所存储的分区信息有限

延伸分区:分区信息记录在分区表内,用于割分更多的分区,不可格式化

逻辑分区:分区信息记录在延伸分区表内,割分延伸分区表

GPT没有以上概念

开机流程

  1. BIOS:开机主动执行的固件,会认识第一个可开机的设备;
  2. MBR:第一个可开机设备的第一个扇区内的主要开机记录区块,内含开机管理程序;
  3. 开机管理程序(boot loader):一支可读取核心文件来执行的软件;
  4. 核心文件:开始操作系统的功能…

uefi与BIOS对比

目录树结构

目录树架构(directory tree)就是以根目录(/)为主,然后向下呈现分支状的目录结构的一种文件架构。

  • 挂载

利用一个目录当成进入点(挂载点),将磁盘分区的数据放置在该目录下; 也就是说,进入该目录就可以读取该分 区

基本操作

  • 指令格式

$ :一般用户 #: root用户

指令 说明
date 显示时间
cal 显示日历
bc 计算器
ctrl+c 终止指令
ctrl+d 键盘输入结束EOF、可取代exit
shift+pgdn(pgup) 翻页
sync 将数据同步写入硬盘
shutdown 关机
reboot 重启
chgrp 改变档案群组
chown 改变档案拥有者
chmod 改变档案权限
cat 读取文件内容
read 读取键盘输入
declare或typeset 声明变量名

[Tab] 接在一串指令的第一个字的后面,则为 “ 命令补全 ” ; [Tab] 接在一串指令的第二个字以后时,则为 “ 文件补齐 ” !

man

(man man) |编号 |代表内容| |1| 使用者在 shell 环境中可以操作的指令或可可执行文件| |2 |系统核心可调用的函数与工具等| |3| 一些常用的函数( function )与函数库( library ),大部分为 C 的函数库( libc )| |4| 设备文件的说明,通常在 /dev 下的文件| |5| 配置文件或者是某些文件的格式| |6| 游戏( games )| |7| 惯例与协定等,例如 Linux 文件系统、网络协定、 ASCII code 等等的说明| |8| 系统管理员可用的管理指令| |9| 跟 kernel 有关的文件|

笔记

可以通过man或info指令获取指令的详细信息

文件

文件属性

  • 第一个字符
    • d 目录
      • 档案
    • l 连结档
    • b 装置文件里的可供存储的接口设备
    • c 装置文件里的串行端口设备
  • 三个一组,分别为rwx,分别代表读、写、可执行。第一组为拥有者、第二组为同组群、第三为其他

文件类型

  • 正规文件【-】
    1. 纯文本文件
    2. 二进制文件
    3. 数据份格式文件
  • 目录【d】
  • 链接文件【l】
  • 设备与设备文件
    1. 区块设备文件【b】:存储数据的周边设备,硬盘等 . 字符设备文件【c】:一次性读取设备,鼠标、键盘等
  • 数据接口文件【s】
  • 输出传输档【p】

目录配置依据

|不变的|/usr(软件放置处)/opt(第三方协议)|/etc(配置文件) /boot(开机与核心文档)| |可变的|/var/mail(使用者邮件信箱) /var/spool/news(新闻群组)|/var/run(程序相关) /var/lock(程序相关)|

  • 目录定义 / ( root, 根目录):与开机系统有关; /usr ( unix software resource ):与软件安装 / 执行有关; /var ( variable ):与系统运行过程有关。

笔记

  • -x权限对目录十分重要,拥有x权限才可以将该目录设为工作目录,才能执行其他指令
  • -r权限可以读取到目录内的文件列表,但不能进入目录
  • -w权限可以修改目录内的文件,但不能修改文件内容,使用时需要拥有x权限 -linux文件是否可以执行与文件扩展名无关,其通过文件属性设置
  • 文件以.开头表示隐藏文件
  • Linux 的每个文件中,可分别给予使用者、群组与其他人三种身份个别的 rwx 权限;
  • 群组最有用的功能之一,就是当你在团队开发资源的时候,且每个帐号都可以有多个群组的支持;
  • 利用 ls -l 显示的文件属性中,第一个字段是文件的权限,共有十个位,第一个位是文件类型, 接下来三个为一组共三组,为使用者、群组、其他人的权限,权限有 r,w,x 三种;
  • 如果文件名之前多一个 “ . ” ,则代表这个文件为 “ 隐藏文件 ” ;
  • 若需要 root 的权限时,可以使用 su - 这个指令来切换身份。处理完毕则使用 exit 离开 su 的指令环境。
  • 更改文件的群组支持可用 chgrp ,修改文件的拥有者可用 chown ,修改文件的权限可用 chmod
  • chmod 修改权限的方法有两种,分别是符号法与数字法,数字法中 r,w,x 分数为 4,2,1 ; 对文件来讲,权限的性能为: r :可读取此一文件的实际内容,如读取文本文件的文字内容等; w :可以编辑、新增或者是修改该文件的内容(但不含删除该文件); x :该文件具有可以被系统执行的权限。
  • 对目录来说,权限的性能为: r ( read contents in directory ) w ( modify contents of directory ) x ( access directory )
  • 要开放目录给任何人浏览时,应该至少也要给予 r 及 x 的权限,但 w 权限不可随便给;
  • 能否读取到某个文件内容,跟该文件所在的目录权限也有关系 (目录至少需要有 x 的权限)。
  • Linux 文件名的限制为:单一文件或目录的最大容许文件名为 255 个英文字符或 128 个中文字符;
  • 根据 FHS 的官方文件指出, 他们的主要目的是希望让使用者可以了解到已安装软件通常放置于那个目录下
  • FHS 订定出来的四种目录特色为: shareable, unshareable, static, variable 等四类;
  • FHS 所定义的三层主目录为: /, /var, /usr 三层而已;
  • 绝对路径文件名为从根目录 / 开始写起,否则都是相对路径的文件名。

文件与目录管理

|. |代表此层目录| |..|代表上一层目录| |-|代表前一个工作目录| ||代表 “ 目前使用者身份 ” 所在的主文件夹| |account|代表 account 这个使用者的主文件夹( account 是个帐号名称)|

  • 常见目录指令 |cd|变更目录| |pwd|显示目前的目录| |mkdir|创建新目录| |rmdir|删除空目录|

vi和vim

第十章 BASH

变量

变量使用与设置

1
myname=yezh
  • 等号两边不能有空格
  • 使用单反引号[command]或$(command)可以执行额外的命令
1
version=$(uname -r)
  • 双引号内的特殊字符如$保持原有特性
  • 单引号内特殊字符与一般字符相同 可使用\将特殊字符转化为一般字符
1
2
3
4
5
myhome="$HOME"
#myhome = /home/yeah

myroom='$HOME'
#myroom=$HOME
  • 使用${name}or$"name"可扩展变量内容
1
2
3
myroom=$HOME  #myhome = /home/yeah
myroom=${myhome}:/home/bin
#myhome = /home/yeah:/home/bin
  • using variable on other program with export variablename
  • canncel variable with unset variablename
  • 父进程定义的变量无法在子进程中使用

环境变量功能

子进程只继承父进程的环境变量,不继承自定义变量

1
2
3
4
5
#查看环境变量
env
export
#查看所有变量(含环境变量和自定义变量)
set

变量内容的删除、替换

# 符合删除文字的最短字符串
## 符合删除文字的最长字符串
% 从后往前删除
%%
/old/new 替换第一个相符关键字
//old/new 替换全部相符关键字

第十一章 正则表达式

特殊符号 含义
[:alnum:] 大小写字母与数组,即09. az, A~Z
[:alpha:] 大小写字母,az, AZ
[:blank:] 空格键与[tab]键
[:cntrl:] 控制按键,CR、LF、Tab、Del等
[:digit:] 数字,0~9
[:graph:] 除空格键和[tab]以外所有按键
[:lower:] 小写字母a~z
[:print:] 可打印字符
[:punct:] 标点符号,“ ? . /等
[:upper:] 大写字母A~Z
[:space:] 任何会产生空白的字符,空格,TAB, CR等
[:xdigit:] 十六进制数字类型,包括0~ 9,af, AF
RE 字符 意义与范例
^word 意义:待搜寻的字串( word )在行首! 范例:搜寻行首为 # 开始的那一行,并列出行号 grep -n '^#' regular_express.txt
word$ 意义:待搜寻的字串( word )在行尾! 范例:将行尾为!的那一行打印出来,并列出行号 grep -n '!$' regular_express.txt
. 意义:代表 “ 一定有一个任意字符 ” 的字符! 范例:搜寻的字串可以是 (eve)(eae)(eee)(ee),但不能仅有(ee)!亦即e与e中间“一定”仅有一个字符,而空白字符也是字符! grep -n 'e.e' regular_express.txt
\ 意义:转义字符,将特殊符号的特殊意义去除! 范例:搜寻含有单引号 ‘ 的那一行! `grep -n ‘ regular_express.txt
* 意义:重复零个到无穷多个的前一个 RE 字符 范例:找出含有 ( es ) ( ess ) ( esss ) 等等的字串,注意,因为 * 可以是 0 个,所以 es 也是符合带搜寻字串。另外,因为 * 为重复 “ 前一个 RE 字符 ” 的符号, 因此,在 * 之前必须要紧接着一个 RE 字符喔!例如任意字符则为“.”! `grep -n ‘ess‘ regular_express.txt`
[list] 意义:字符集合的 RE 字符,里面列出想要撷取的字符! 范例:搜寻含有 ( gl ) 或 ( gd ) 的那一行,需要特别留意的是,在 [] 当中 “ 谨代表一个待搜寻的字符 ” , 例如 “a[afl]y ” 代表搜寻的字串可以是 aay, afy, aly 即 [afl] 代表 a 或 f 或 l 的意思! grep -n 'g[ld]' regular_express.txt
[n1-n2] 意义:字符集合的 RE 字符,里面列出想要撷取的字符范围! 范例:搜寻含有任意数字的那一行!需特别留意,在字符集合 [] 中的减号 - 是有特殊意义的,他代表两个字符之间的所有连续字符!但这个连续与否与 ASCII 编码有关,因此,你的编码需要设置正确(在 bash 当中,需要确定 LANG 与 LANGUAGE 的变量是否正确!) 例如所有大写字符则为 [A-Z] grep -n '[A-Z]' regular_express.txt
[^list] 意义:字符集合的 RE 字符,里面列出不要的字串或范围! 范例:搜寻的字串可以是 ( oog ) ( ood ) 但不能是 ( oot ) ,那个 ^ 在 [] 内时,代表的意义是 “ 反向选择 ” 的意思。 例如,我不要大写字符,则为 [^A-Z] 。但是,需要特别注意的是,如果以 grep -n [^A-Z] regular_express.txt 来搜寻,却发现该文件内的所有行都被列出,为什么?因为这个 [^A-Z] 是 “ 非大写字符 ” 的意思, 因为每一行均有非大写字符,例如第一行的”Open Source” 就有 p,e,n,o…. 等等的小写字grep -n 'oo[^t]' regular_express.txt
{n,m} 意义:连续 n 到 m 个的 “ 前一个 RE 字符 ” 意义:若为 {n} 则是连续 n 个的前一个 RE 字符, 意义:若是 {n,} 则是连续 n 个以上的前一个 RE 字符! 范例:在 g 与 g 之间有 2 个到 3 个的 o 存在的字串,亦即 ( goog )( gooog ) grep -n 'go\{2,3\}g' regular_express.txt

re

第十二章 shell脚本

1
2
3
4
5
6
7
8
9
#!/bin/bash
#program
# creat one file named by user's input and date command

read -p "please input file name: " fileuser
filename=${fileuser:-"backup"}
datestr=$(date +%y%m%d)
filename=${filename}-${datestr}
touch "${filename}"

对变量赋值时,等号两边不能有空格

执行方式

  1. source
    直接在父进程中运行脚本

  2. sh script
    在子进程中运行脚本

  3. ./script 需要将文件设置为可执行文件

    1
    chmod +x <filename>

命令行参数

bash命令行参数可以用$0,$1…获取

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
#program:test
echo "the program name is: $0"
echo "the frist arg is: $1"
echo "the second arg is: $2"

# bash
$ sh test 1 2 3
the program name is: test
the frist arg is: 1
the second arg is: 2

判断式

test命令

[ ]判断符号

第一章 引论

本质

  • 是计算机硬件的第一层软件
  • 是对硬件系统的首次扩充
  • 对内是资源管理者,对外是用户接口

目标

  • 方便性

    配置os后可使操作系统更容易使用

  • 有效性

    1. 提高系统资源利用率
    2. 提高系统的吞吐量
  • 可扩充性

    os必须具有很好的可扩充性,方能适应计算机硬件、体系结构以及应用发展的要求

  • 开放性

    为使来自不同厂家的计算机和设备能够通过网络加以集成化,并能正确、有效的协同工作,实现应用的可移植性和互操作性,要求操作系统必须提供统一的开放环境,进而要求os具有开放性。

作用

  1. 作为用户与计算机硬件系统之间的接口

    1563449315086

  2. 作为计算机系统资源的管理者

    四类计算机资源:处理器、存储器、I/O设备以及信息(数据和程序)

    os的主要功能是针对四类资源进行有效管理:

    • 处理机管理,用于分配和控制处理机
    • 存储器管理,主要负责内存的分配和回收
    • I/O设备管理,负责I/O设备的分配和操纵
    • 文件管理,负责文件的存取、共享和保护
  3. 实现了对计算机资源的抽象

发展过程

  1. 单道批处理系统

    系统对作用的处理是成批进行的,且在内存中始终只存在一道作业。

    特征:自动性、顺序性、单道性

  2. 多批道处理系统

    • 优缺点:资源利用率高、系统吞吐量大、平均周转时间长、无交互能力
  3. 分时系统

    特征:多路性、独立性、及时性、交互性

  4. 实时操作系统

  5. 微机操作系统

    单用户单任务操作系统

    单用户多任务操作系统

    多用户多任务操作系统

基本特性

  1. 并行和并发

    并行:两个或多个事件在同一刻发生;

    并发:两个或多个事件在同一时间间隔内发生

  2. 进程

    概念:在系统中能独立运行并作为资源分配的基本单位,由一组机器指令、数据和堆栈组成。是一个能独立运行的实体

    通常的程序是静态实体,在多道程序系统中不能独立运行

    引入进程的目的是为了使多个程序能并发运行

  3. 共享

    互斥共享方式

    同时访问方式

  4. 虚拟

    时分复用技术:虚拟处理机技术/虚拟设备技术

    空分复用技术

  5. 异步

主要功能

  1. 处理机管理功能

    • 进程控制
    • 进程同步
    • 进程通信
    • 调度:作业调度/进程调度
  2. 存储器管理功能

    内存分配:静态/动态分配方式

    内存保护

    地址映射

    内存扩充:请求调入功能/置换功能

  3. 设备管理功能

    • 缓存管理
    • 设备分配
    • 设备处理
  4. 文件管理功能

    • 文件存储空间管理
    • 目录管理
    • 文件的读/写管理保护
  5. 操作系统与用户之间的接口

    • 联机用户接口
    • 脱机用户接口
    • 图形用户接口
  6. 现代操作系统的新功能

    • 系统安全
    • 网络的功能和服务
    • 支持多媒体

结构设计

  1. 传统操作系统结构
    • 无结构操作系统(软件危机)
    • 模块化结构os(高内聚低耦合——横向关联)
    • 分层式结构os(隐马尔可夫链——纵向关联)
  2. C/S模式(客户/服务器)
  3. 面向对象的程序设计
  4. 微内核os结构(优缺点)
    • 足够小的内核
    • C/S模式
    • 应用“机制和策略分离”原理
    • 采用面向对象技术

第二章 进程

前趋图和程序执行

  1. 前趋图是一个有向无循环图,记为DAG,用于描述进程之间执行的前后关系

结点:程序段或进程,乃至一条语句

有向边:结点之间存在的前趋关系

初始结点:无前趋的结点

终止结点:无后继的结点

重量:该结点所含有的程序量或结点的执行时间

==前趋图中不存在循环==

  1. 程序顺序执行的特征
  • 顺序性:处理机的操作严格按照程序所规定的顺序执行
  • 封闭性:程序在封闭环境下执行,即运行时独占全机资源
  • 可再现性:只要执行环境和初始条件相同,不论如何执行,都获得相同的结果
  1. 程序并发运行

    特征:间断性、失去封闭性、不可再现性

进程描述

  1. 进程定义:是进程实体的运行过程,是系统进行资源调度的一个独立单位

    进程控制块(pcb)

    • 进程标识符:内部标识符(由操作系统创建)外部标识符(由创建者提供)
    • 处理机状态:主要由处理机的各种寄存器的内容组成(通用寄存器、指令计数器、程序状态字psw、用户栈指针)
    • 进程调度信息:进程状态、进程优先级、其他信息、事件(堵塞原因)
    • 进程控制信息
      • 程序和数据地址
      • 进程同步和通信机制
      • 资源清单
      • 链接指针

    进程实体:由程序段、相关数据段和pcb三部分构成

  2. 特征

    • 动态性:最基本特征。“由创建而产生,因调度而执行,由撤销而消亡”

    • 并发性:多个进程实体同存于内存中,且在一段时间内能独立运行

    • 独立性:在传统os中,独立性是指进程实体是一个能独立运行、独立分配资源和独立接收调度的基本单位

    • 异步性:进程实体按异步方式运行

      三种基本状态及其转换

进程控制

进程控制是进程管理中最基本的功能,用于创建一个新进程,终止一个已完成进程,或终止一个因出现某事件而使其无法运行下去的进程,还可负责进程运行中的状态转换。

  1. 操作系统内核

    1. 系统态(管态)、用户态(目态)
    2. 支撑功能:中断管理、时钟管理、原语操作
    3. 资源管理功能:进程、存储器、设备管理
  2. 进程创建

    进程图:描述一个进程家族关系的有向树

    有向树

    引起创建进程的事件

    • 用户登录
    • 作业调度
    • 提供服务
    • 应用请求

    引起进程终止的事件

    • 正常结束
    • 异常结束
    • 外界干预

    引起进程堵塞或唤醒事件

    • 请求系统服务
    • 等待操作完成
    • 新数据尚未到达
    • 等待新任务的到达

进程同步

  1. 基本概念
    1. 制约关系:间接相互制约关系、直接相互制约关系
    2. 临界资源
    3. 临界区
    4. 同步机制规则
      1. 空闲让进
      2. 忙则等待
      3. 有限等待
      4. 让权等待
  2. 硬件同步机制
  3. 信号量机制
    • 整型信号量
    • 记录型信号量

第三章 处理机

处理机调度的层次和调度算法的目标

层次

  1. 高级调度:又称长程调度或作业调度,对象是作业
  2. 低级调度:又称进程调度或短程调度,对象是进程
  3. 中级调度:又称内存调度

目标

  1. 处理机调度的共同目标
    • 资源利用率:CPU利用率=有效工作时间/总时间
    • 公平性:公平性是相对的
    • 平衡性:保持系统资源使用的平衡性
    • 策略强制执行
  2. 批处理系统的目标
    • 平均周转时间短
    • 系统吞吐量高
    • 处理机利用率高
  3. 分时系统目标
    • 响应时间快
    • 均衡性
  4. 实时系统目标
    • 截止时间的保证
    • 可预测性

作业和作业调度

批处理系统中的作业

作业运行的三个阶段和三种状态

  • 收容状态:建立jcb
  • 运行状态:建立pcb
  • 完成状态:回收pcb和jcb

作业调度的主要任务

作业调度算法

  1. 先来先服务(FCFS)

  2. 短作业优先调度

    FCFS算法有利于长作业,而不利于作业

    SJF:短作业优先调度算法

    SPF:短进程优先调度算法

    缺点:

    • 对长作业不利
    • 完全未考虑作业的紧迫程度
    • 不一定真正做到短作业优先调度
  3. 优先级调度算法(FPF)

    1. 优先权调度算法类型
      • 非抢占式优先
      • 抢占式优先
    2. 优先权类型:静态优先权、动态优先权
  4. 高响应比优先调度算法

进程调度

进程调度任务

  1. 保存处理机的现场信息
  2. 按某种算法选取进程
  3. 把处理器分配给进程

进程调度机制

  1. 排队器
  2. 分派器
  3. 上下文切换器

调度方式

  • 非抢占方式:突发事件、I/O请求、进程通信
  • 抢占方式:优先权原则、短进程优先原则、时间片原则

轮转调度算法

多机反馈队列算法

死锁概述

资源问题和死锁原因

  • 竞争资源:可重用性资源和消耗性资源、可抢占性资源和不可抢占性资源
  • 进程间推进顺序非法:请求和释放资源顺序不当
  1. 竞争不可抢占性资源引起资源死锁
  2. 竞争可消耗性资源引起死锁
  3. 进程推进顺序不当引起死锁

死锁的定义、必要条件和处理方法

  1. 定义
  2. 必要条件
    • 互斥条件
    • 请求和保持条件
    • 不剥夺条件
    • 环路等待条件
  3. 处理死锁方法
    • 预防死锁:通过设置某些限制条件,破坏产生死锁的四个必要条件中的一个或多个,来预防死锁
    • 避免死锁:在资源动态分配过程中,用某种方法去防止系统进入不安全状态
    • 检测死锁:通过系统所设置的检测机构及时检测出死锁的发生,并精确地确定与死锁有关的进程和资源,然后采取适当的措施,从系统中清除已发生的死锁
    • 解除死锁:与检测死锁向配套的一种措施

预防死锁

  1. 摒弃“请求和保持”条件:所有进程开始前都必须一次性申请其在整个运行过程中所需的全部资源
  2. 破坏“不可抢占”条件:进程逐个提出对资源的请求。当进程提出新的资源请求而得不到满足时,释放其已保持的所有资源,待以后需要时重新申请。
  3. 破坏“环路等待”条件:所有资源按线性排队,所有进程对资源的请求必须严格按照资源序号递增的次序提出

## 避免死锁

  1. 允许进程动态申请资源,但系统在进行资源分配前,先计算资源分配的安全性,如此次分配导致系统进入不安全状态,则令进程等待。
    • 安全状态:系统能够按某种进程顺序来为每个进程分配其所需资源,直至满足每个进程对资源的最大需求。使每个进程可以顺利完成。
    • 并非所有不安全状态都必然会转为死锁状态,但当系统进入不安全状态时,便有可能进入死锁状态。
  2. 银行家算法
  3. 安全性算法

##  死锁的检测和解除

  1. 死锁的检测
    • 保存有关资源的请求和分配信息
    • 提供一种算法,以利用这些信息来检测系统是否已经进入死锁状态
    • 死锁定理:当且仅当s状态的资源分配图是不可完全简化的(充分条件)
  2. 死锁的解除
    • 剥夺资源:从其他进程剥夺足够数量的资源给死锁进程,以解除死锁
    • 撤销进程

第四章 存储器

存储器的层次结构

计算机系统存储层次示意

程序的装入与链接

  • 编译:由编译程序将用户源代码编译成若干个目标模块
  • 链接:由链接程序将编译后形成的一组目标模块,以及它们所需的库函数链接在一起,形成一个完整的装入模块
  • 装入:由装入程序将装入模块装入内存
  1. 程序的装入方式
    • 绝对装入方式
    • 可重定位装入方式
    • 动态运行时装入方式
  2. 程序的链接
    • 静态链接:程序运行前
    • 装入时动态链接:装入内存时
    • 运行时动态链接:程序执行需要该模块时

连续分配存储管理方式

  1. 单一连续分配
  2. 固定分区分配
    • 分区方法:分区大小相等、分区大小不等
    • 内存分配
  3. 动态分区分配
  4. 动态可重定位分区分配

对换Swapping

将内存中暂时不能运行的进程或者暂时不用的程序和数据调出到外存上,以腾出足够的内存空间,再将已具备运行条件的进程或进程所需的程序和数据调入内存

分页存储管理方式

  • 分页存储管理:逻辑页大小=物理块大小
  • 分段存储管理:逻辑性强,物理性若
  • 段页式存储管理:段中有页
  1. 基本方法
    • 页面:将一个进程的逻辑地址空间分成若干大小相等的片
    • 物理块/页框:把内存空间分成与页面大小相等的若干个存储块
  2. 地址变换机构
    • 基本的地址变换机构
    • 具有块表的地址变换机构
  3. 两级和多级页表

分段存储管理方式

第五章虚拟存储器

概述

常规存储器管理方式特征

  1. 一次性
  2. 驻留性

局部性原理

  1. 程序执行时,除了少部分的转移和过程调用指令外,在大多数情况下仍是顺序执行的
  2. 过程调用将会使程序的执行轨迹由一部分区域转至另一部分区域,但过程调用的深度大多数情况下不超过5,即程序在一段时间内都局限在这些过程内执行
  3. 程序中存在许多循环结构
  4. 程序包括许多对数据结构的处理
    • 局部性还表现在时间局限性空间局限性

虚拟存储器的定义和特征

  • 虚拟存储器是指具有请求置换功能,能从逻辑上对内存容量加以扩充的一种存储系统
  • 其逻辑容量由内存容量和外存容量之和决定,运行速度接近于内存速度,而每位的成本接近于外存
  • 特征
    1. 多次性:指一个作业被分成多次调入内存
    2. 对换性:允许在作业的运行过程中进行换进、换出
    3. 虚拟性:从逻辑上扩充内容容量

请求分页管理方式

硬件支持

  1. 请求分页的页表机制

  2. 缺页中断机构

  3. 地址变换机构

    地址变换机构

实现请求分页的软件

  • 实现请求调页的软件和实现页面置换的软件
  • 最小物理块数:保证进程正常运行所需的最小物理块数,少于此值时,进程将无法运行
  1. 物理块分配策略
    • 固定分配局部置换
    • 可变分配全局置换
    • 可变分配局部置换
  2. 物理块分配算法
    • 平均分配算法
    • 按比例分配算法
    • 考虑优先权的分配算法
  3. 调页策略
    • 预调页策略
    • 请求调页策略

页面置换算法

  1. 最佳置换算法
  2. 先进先出置换算法
  3. 最近最久未使用算法(LRU)

“抖动”与工作集

请求分段存储管理方式

硬件支持

  1. 请求分段的段表机制

  2. 缺段中断机构

    请求分段系统中的中断处理过程

第六章 输入输出

I/O系统的功能、模型和接口

  • 管理的主要对象是I/O设备和相应的设备控制器
  • 最主要的任务是:
    • 完成用户提出的I/O请求
    • 提高I/O速率
    • 提高设备的利用率
    • 为更高层的进程方便地使用这些设备提供手段

基本功能

  1. 隐藏物理设备的细节
  2. 与设备无关性
  3. 提高处理机和I/O设备的利用率
  4. 对I/O设备进行控制
  5. 确保对设备的正常共享
  6. 错误处理

层次结构模型

  1. 层次结构

    层次结构

I/O设备和设备控制器

  1. I/O 设备的类型

    • 按使用特性:存储设备 输出输出设备
    • 按传输速率:低俗设备;中速设备;高速设备
    • 按信息交换单元:块设备;字符设备
    • 按共享属性:独占设备;共享设备;虚拟设备
  2. 设备与控制器之间的接口1545312780653

  3. 设备控制器的基本功能

    • 接收和识别命令
    • 数据交换
    • 标识和报告设备的状态
    • 地址识别
    • 数据缓存区
    • 差错处理
  4. 设备控制器的组成

    • 设备控制器与处理器的接口

    • 设备控制器与设备的接口

    • I/O逻辑

      1545313026687

  5. I/O通道

    • 字节多路通道
    • 数组选择通道
    • 数组多路通道

中断机构和中断处理程序

I/O控制方式

  1. 程序I/O方式
  2. 中断驱动I/O控制方式
  3. 直接存储器访问I/O控制方式(DMA)
  4. I/O通道控制方式

设备驱动程序

  • 设备驱动程序,又称设备处理程序,是I/O进程与设备控制器之间的通信程序
  • 主要任务是接收上层软件发来的抽象I/O请求,将其转化为具体要求后发给设备控制器

设备驱动程序的功能

  1. 接收由设备独立性软件发来的命令和参数,并将命令中的抽象要求转化为具体要求
  2. 检查用户I/O请求的合法性,了解I/O设备的状态,传递有关参数,设置设备的工作方式
  3. 发出I/O命令。如设备空闲,便立即启动I/O设备去完成指定的I/O操作;如设备忙碌,则将请求者的请求挂在设备队列上等待
  4. 及时响应由控制器或通道发来的中断请求,并根据器中断类型调用相应的中断处理程序进行处理
  5. 对于设置有通道的计算机系统,驱动程序还应根据用户的I/O请求,自动地构成通道程序

设备处理方式

  1. 为每一类设备设置一个进程,专门用于执行这类设备的I/O操作
  2. 在整个系统中设置一个I/O进程,专门用于执行系统中所有各类设备的I/O操作
  3. 不设置专门的设备处理进程,而只为各类设备设置相应的设备处理程序,供用户进程或系统进程调用

设备驱动程序的特点

  1. 设备驱动程序主要是指在请求I/O的进程与设备控制器之间的一个转换程序。
  2. 驱动程序与设备控制器和I/O设备的硬件特性紧密相关,因而对不同类型的设备应配置不同的驱动程序
  3. 驱动程序与I/O设备所采用的I/O控制方式紧密相关,
  4. 由于驱动程序与硬件紧密相关,因而其中的一部分必须用汇编语言书写
  5. 驱动程序应允许重入

设备驱动程序的处理过程

  1. 将抽象要求转化为具体要求
  2. 检查I/O 设备的合法性
  3. 读出和检查设备的状态
  4. 传送必要的参数
  5. 工作方式的设置
  6. 启动I/O设备

与设备无关的I/O软件

基本概念

  1. 以物理设备名使用设备
  2. 引入了逻辑设备名
  3. 逻辑设备名称到物理设备名称的转换

##用户层的I/O软件

Spooling(假脱机)

spooling的组成

  1. 输入井和输出井
  2. 输入缓冲区和输出缓冲区
  3. 输入进程和输出进程
  4. 井管理程序

spooling的特点:

  • 提高了I/O的速度
  • 将独占设备改造成共享设备
  • 实现了虚拟设备功能

缓存区管理

引入缓存区的原因

  • 缓和CPU和I/O设备间速度不匹配的矛盾
  • 减少对CPU的中断频率,放宽对CPU中断响应的限制
  • 解决数据粒度不匹配的问题
  • 提高CPU和I/O设备之间的并行性

缓存池

组成

  • 空白缓冲队列
  • 输入队列
  • 输出队列
  • 工作缓冲区:用于收容输入数据的工作缓冲区,用于提取输入数据的工作缓冲区、用于收容输出数据的工作缓冲区、用于提取输出数据的工作缓冲区

工作方式

  • 收容输入
  • 提取输入
  • 收容输出
  • 提取输出

磁盘存储器的性能和调度

磁盘访问时间

  1. 寻道时间
  2. 旋转延迟时间
  3. 传输时间

早期磁盘调度算法

  1. 先来先服务(FCFS)
  2. 最短寻道时间优先(SSTF)

基于扫描的磁盘调度算法

  1. 扫描算法(SCAN)
  2. 循环扫描算法(CSCAN)
  3. NStepSCAN算法
  4. FSCAN算法

第七章 文件管理

数据组成

  1. 数据项
    • 数据项是最低级的数据组织形式
    • 可分为:基本数据项和组合数据项
  2. 记录
    • 一组相关数据项的集合,用于描述对象在某些方面的属性
    • 关键字:唯一能标识一个记录的数据项
  3. 文件
    • 由创建者所定义的、具有文件名的一组相关元素的集合,可分为有结构文件和无结构文件
    • 文件是文件系统中一个最大的数据单位
    • 文件属性包括:文件类型、文件的物理位置、文件长度、文件的建立时间

文件类型

  1. 按用途分类
    • 系统文件
    • 用户文件
    • 库文件
  2. 按文件中数据的形式分类
    • 源文件
    • 目标文件
    • 可执行文件
  3. 按存取控制属性分类
    • 只执行文件
    • 只读文件
    • 读写文件
  4. 按组织形式和处理方式分类
    • 普通文件
    • 目录文件
    • 特殊文件

层次结构

文件系统模型

  1. 对象及其属性:文件、目录、磁盘
  2. 对对象操纵和管理的软件集合:
    • 对文件存储空间的管理
    • 对文件目录的管理
    • 用于将文件的逻辑地址转换为物理地址的机制
    • 对文件读和写的管理
    • 对文件的共享和保护
    • 将与文件系统相关的软件分为四个层次:I/O控制层、基本文件系统层、基本I/O管理程序、逻辑文件系统
  3. 文件系统接口
    • 命令接口
    • 程序接口

文件操作

  1. 最基本文件操作
    • 创建文件
    • 删除文件
    • 写文件
    • 设置文件的读写位置

文件的逻辑结构

文件的类型

  1. 按文件是否有结构分类
    • 有结构文件:定长记录、变长记录
    • 无结构文件
  2. 按文件的组织方式分类
    • 顺序文件
    • 索引文件
    • 索引顺序文件

文件目录

文件控制块(FCB)

  1. 基本信息:文件名,文件物理位置,文件逻辑结构,文件的物理结构
  2. 存储控制信息:文件主的存取权限,核准用户的存取权限,一般用户的存取权限
  3. 使用信息

简单文件目录

  1. 单级文件目录
  2. 两级文件目录

树形结构目录

  1. 树形目录
  2. 路径名和当前目录

文件共享

  1. 基于有向无循环图实现文件共享
  2. 利用符号链接来实现文件共享

文件保护

  1. 保护域
  2. 访问矩阵8

第八章 磁盘存储器的管理

go 学习笔记

Go使用package将源文件打包成包,main函数所在的文件必须打包成main,编译器才会将其编译为可执行文件,否则将其编译为包。

导入包时,使用import语句,GO语言要求导入的包必须是被使用的包,若导入包未被使用,编译器会直接报错,无法通过。

导入包时,程序会首先运行包里面的init函数,当我们只需要执行init函数,而暂时不引用包的变量或函数时,可以使用匿名导入的方式,防止编译器报错,匿名导入只需要在包名的前面加入_即可,如下所示:

1
import _ "fmt"

包的成员的外部可见性与成员的名称的大小写有关,当成员名称首字母为大写时,则该成员时导出的,引用该包的其他包可以访问这个成员;否则该成员未导出。

语句

Go语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号, 因此换行符添加的位置会影响Go代码的正确解析

  1. 函数的左括号必须位于函数名之后,且在行尾,不能独占一行,(其他相似情况下类似)
  2. 表达式x+y中,可以在+之后换行,不能在+之前换行

c类语言中i++为表达式,而go中,i++是语句而非表达式,且++i非法

1
*p++   //增加p指针指向的变量的值,而不改变p自身,与c/c++不同

if语句

go的条件语句结构如下所示

1
2
3
if 语句;布尔表达式{
语句
}

与其他类c语言不同的是,go中的if语句不需要()但必须带有{}.同时在布尔表达式之前可以插入语句用于声明局部变量或给变量赋值等。

循环语句

go中的循环语句相关的关键字只有for,与if语句相同,for语句不需要()但必须包含{},for语句一般结构如下所示

1
2
3
for 语句1;布尔表达式;语句2{
...
}

与c语言的for循环类似,同样包括三部分语句,且部分语句可以省略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
for i := 0; i < 10; i++ {
fmt.Println(i)
}
for i := 0; i < 10; {
fmt.Println(i)
i++
}
var j int
for ; j < 10; j++ {
fmt.Println(j)
}
j = 0
for {
if j == 10 {
fmt.Println("break frome for1")
break
}
j++
}
j = 0
for j < 10 {
fmt.Print(j)
j++
}

当for后面不接任何子语句时,该for语句为死循环。当for后面只含有布尔表达式时,可以将前后的分隔符省略。

for语句经常搭配range使用,range用于slice或map等类似于其他语言中的迭代器的时成分

1
2
3
4
5
6
7
8
s :="hello word"
var a []int = []int{1,2,3,4}
for i,j := range(s){
...
}
for k,v:= range(a){
...
}

复合类型

不同类型间不能进行直接赋值操作

数组

数组长度是数组类型的一部分,[3]int[4]int属于不同类型

1
2
p := [3]int{1,2,3}
p = [4]int{1,2,3,4} //error

与其他语言不同,go函数的数组形参包采用副本的形式传入,如果要对数组本身进行修改,需要使用指针形参,或者使用slice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func test(a [4]int)[4]int{
for i,j := range a{
a[i] = j+1
}
return a
}

func main() {
var a = [4]int{1,2,3,4}
fmt.Println(a) //1,2,3,4
var b = test(a)
fmt.Println(a) //1,2,3,4
fmt.Println(b) //2,3,4,5

}

slice

slice行为与数组类似,其底层包含一个数组,相较于数组,slice更加灵活,其长度可以动态改变,且作为函数参数时,其传递方式类似于引用,即被调函数内部的修改可以影响到主函数中的slice。

切片声明时不需要指定大小,系统自动生成

1
s := []int{1,2,3,4}       //与数组不同,不需要指定大小

值为nil的slice没有底层数组,与nil相等的slice长度为0,当长度为0的slice不一定是nil

1
2
3
4
var s []int // len(s) == 0, s == nil
s = nil // len(s) == 0, s == nil
s = []int(nil) // len(s) == 0, s == nil
s = []int{} // len(s) == 0, s != nil

内置的make函数创建一个指定元素类型、长度和容量的slice。容量部分可以省略,在这种情况下,容量将等于长度。

1
2
make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]

内置的append函数用于向slice追加元素:

1
2
3
4
5
var runes []rune
for _, r := range "Hello, 世界" {
runes = append(runes, r)
}
fmt.Printf("%q\n", runes) // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"

结构体

结构体一般格式

1
2
3
type name struct{
...
}

一个结构体可能同时含有导出成员和未导出成员

点操作符也可以和指向结构体的指针一起工作:

1
2
var employeeOfTheMonth *Employee = &dilbert
employeeOfTheMonth.Position += " (proactive team player)"

相当于下面语句

1
(*employeeOfTheMonth).Position += " (proactive team player)"

结构体比较

若结构的所有成员都是可比较的,则结构体是可比较的当结构体所有成员都相等时,结构体变量相等

嵌入和匿名

  • 结构体可以嵌入到另一个结构体中

  • 匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针

  • 对于匿名嵌入,可以直接访问叶子属性而不需要给出完整的路径

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    type Point struct {
    X, Y int
    } type Circle struct {
    Point
    Radius int
    }
    type Wheel struct {
    Circle
    Spokes int
    }
    var w Wheel
    w.X = 8 // equivalent to w.Circle.Point.X = 8
    w.Y = 8 // equivalent to w.Circle.Point.Y = 8
    w.Radius = 5 // equivalent to w.Circle.Radius = 5
    w.Spokes = 20

函数

声明

函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。

1
2
3
func name(parameter-list)(result-list){			    	
body
}

以下4中声明所代表的含义相同

1
2
3
4
5
6
7
8
func add(x int, y int) int {return x + y}
func sub(x, y int) (z int) { z = x - y; return}
func first(x int, _ int) int { return x }
func zero(int, int) int { return 0 }
fmt.Printf("%T\n", add) // "func(int, int) int"
fmt.Printf("%T\n", sub) // "func(int, int) int"
fmt.Printf("%T\n", first) // "func(int, int) int"
fmt.Printf("%T\n", zero) // "func(int, int) int"

返回值

go支持多个返回值

如果一个函数将所有的返回值都显示的变量名,那么该函数的return语句可以省略操作数。这
称之为bare return。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// CountWordsAndImages does an HTTP GET request for the HTML
// document url and returns the number of words and images in it.
func CountWordsAndImages(url string) (words, images int, err error) {
resp, err := http.Get(url)
if err != nil {
return
}
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
err = fmt.Errorf("parsing HTML: %s", err)
return
}
words, images = countWordsAndImages(doc)
return
}
func countWordsAndImages(n *html.Node) (words, images int) { /* ... */ }

函数值

在GO中,函数被看作第一类值:函数拥有类型,可以赋值给其他变量,传递给函数,从函数返回,但是函数值之间是不可比较的,也不能用函数值作为map的key。

匿名函数

拥有函数名的函数只能在包级语法块中被声明,通过函数字面量(function literal),我们可绕过这一限制,在任何表达式中表示一个函数值。函数字面量的语法和函数声明相似,区别 在于func关键字后没有函数名。函数值字面量是一种表达式,它的值被称为匿名函数(anonymous function)。通过这种方式定义的函数可以访问完整的词法环境(lexical environment), 这意味着在函数中定义的内部函数可以引用该函数的变量,如下例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// squares返回一个匿名函数。
// 该匿名函数每次被调用时都会返回下一个数的平方。
func squares() func() int {
var x int
return func() int {
x++
return x * x
}
}
func main() {
f := squares()
fmt.Println(f()) // "1"
fmt.Println(f()) // "4"
fmt.Println(f()) // "9"
fmt.Println(f()) // "16"
}

函数squares返回另一个类型为 func() int 的函数。对squares的一次调用会生成一个局部变量x并返回一个匿名函数。每次调用时匿名函数时,该函数都会先使x的值加1,再返回x的平方。第二次调用squares时,会生成第二个x变量,并返回一个新的匿名函数。新匿名函数操作的是第二个x变量。

注意捕获迭代变量

1
2
3
4
5
6
7
8
9
10
11
12
var rmdirs []func()
for _, d := range tempDirs() {
dir := d // NOTE: necessary!
os.MkdirAll(dir, 0755) // creates parent directories too
rmdirs = append(rmdirs, func() {
os.RemoveAll(dir)
})
}
// ...do some work…
for _, rmdir := range rmdirs {
rmdir() // clean up
}

在上面的程序中,for循环语句引入了新的词法块,循环 变量dir在这个词法块中被声明。在该循环中生成的所有函数值都共享相同的循环变量。需要注意,函数值中记录的是循环变量的内存地址,而不是循环变量某一时刻的值。以dir为例, 后续的迭代会不断更新dir的值,当删除操作执行时,for循环已完成,dir中存储的值等于最后一次迭代的值。这意味着,每次对os.RemoveAll的调用删除的都是相同的目录。这不是go或defer本身导致的,而是因为它们都会等待循环结束后,再执行函数值。
通常,为了解决这个问题,我们会引入一个与循环变量同名的局部变量,作为循环变量的副 本。比如下面的变量dir,虽然这看起来很奇怪,但却很有用。

1
2
3
4
5
for
_, dir := range tempDirs() {
dir := dir // declares inner dir, initialized to outer dir
// ...
}

可变参数

参数数量可变的函数称为为可变参数函数。典型的例子就是fmt.Printf和类似函数。Printf首先接收一个的必备参数,之后接收任意个数的后续参数。在声明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号“…”,这表示
该函数会接收任意数量的该类型参数。

1
2
3
4
5
6
7
func sum(vals...int) int {
total := 0
for _, val := range vals {
total += val
}
return total
}

sum函数返回任意个int型参数的和。在函数体中,vals被看作是类型为[] int的切片。sum可以接收任意数量的int型参数:

1
2
3
fmt.Println(sum()) // "0"
fmt.Println(sum(3)) // "3"
fmt.Println(sum(1, 2, 3, 4)) // "10"

在上面的代码中,调用者隐式的创建一个数组,并将原始参数复制到数组中,再把数组的一个切片作为参数传给被调函数。如果原始参数已经是切片类型,我们该如何传递给sum?只需在最后一个参数后加上省略符。下面的代码功能与上个例子中最后一条语句相同。

1
2
values := []int{1, 2, 3, 4}
fmt.Println(sum(values...)) // "10"

虽然在可变参数函数内部,…int 型参数的行为看起来很像切片类型,但实际上,可变参数函数和以切片作为参数的函数是不同的。

1
2
3
4
func f(...int) {}
func g([]int) {}
fmt.Printf("%T\n", f) // "func(...int)"
fmt.Printf("%T\n", g) // "func([]int)"

Deferred函数

当defer语句被执行时,跟在defer后面的函数会被延迟执行。直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。

defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。

Panic

Go的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等。这些运行时错误会引起painc异常。
一般而言,当panic异常发生时,程序会中断运行,并立即执行在该goroutine中被延迟的函数(defer 机制)。随后,程序崩溃并输出日志信息。日志信息包括panic value和函数调用的堆栈跟踪信息。panic value通常是某种错误信息。对于每个goroutine,日志信息中都会有与之相对的,发生panic时的函数调用堆栈跟踪信息。通常,我们不需要再次运行程序去定位问题,日志信息已经提供了足够的诊断依据。因此,在我们填写问题报告时,一般会将panic异常和日志信息一并记录。

虽然Go的panic机制类似于其他语言的异常,但panic的适用场景有一些不同。由于panic会引起程序的崩溃,因此panic一般用于严重错误,如程序内部的逻辑不一致。

Recover捕获异常

如果在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recoverrecover会返回nil。

1
2
3
4
5
6
7
8
9
func Parse(input string) (s *Syntax, err error) {
defer func() {
if p := recover(); p != nil {
err = fmt.Errorf("internal error: %v"
, p)
}
}()
// ...parser...
}

deferred函数帮助Parse从panic中恢复。在deferred函数内部,panic value被附加到错误信息中;并用err变量接收错误信息,返回给调用者。我们也可以通过调用runtime.Stack往错误信息中添加完整的堆栈调用信息。

方法

GO不支持类,但支持方法,可以为结构体或其他类型定义方法,方法就是一类带特殊的 接收者 参数的函数。方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。

1
2
3
4
5
6
7
8
9
10
11
12
type Vertex struct {
X, Y float64
}

func (v Vertex) Abs() float64 { //定义结构体方法
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs()) //v.Abs() 调用方法
}

其形式类似于将函数声明中的形参放到函数名之前。 只能为同一个包的类型接收者声明方法,不能为其他包内定义的类型声明方法。 不能为内置类型(如:int)定义方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type MyFloat float64

func (f MyFloat) Abs() float64 { //ture
if f < 0 {
return float64(-f)
}
return float64(f)
}
//cannot define new methods on non-local type float64
//func (f float64) Abs() float64 {
// if f < 0 {
// return float64(-f)
// }
// return float64(f)
//}
func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}

指针接收者

使用指针接收者可以改变接收者自身的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Vertex struct {
X, Y float64
}

func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) { //指针接收者
v.X = v.X * f
v.Y = v.Y * f
}

func main() {
v := Vertex{3, 4}
v.Scale(10) //v = {30,40}
fmt.Println(v.Abs())
}

调用函数时,指针类型的形参必须接受一个指针;调用方法时接收者为变量时可以是指针也可以为值,编译器会自动解引用或取地址。

1
2
3
4
5
6
7
8
var v Vertex
ScaleFunc(v, 5) // 编译错误!
ScaleFunc(&v, 5) // OK

var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK

在现实的程序里,一般会约定如果Point这个类有一个指针作为接收器的方法,那么所有Point 的方法都必须有一个指针接收器,即使是那些并不需要这个指针接收器的函数。

只有类型(Point)和指向他们的指针(Point),才是可能会出现在接收器声明里的两种接收器。 此外,为了避免歧义,在声明方法时,*如果一个类型名本身是一个指针的话,是不允许其出现在接收器中的**,比如下面这个例子:

1
2
type	P	*int 
func (P) f() { /* ... */ } // compile error: invalid receiver type
  1. 不管你的method的receiver是指针类型还是非指针类型,都是可以通过指针/非指针类型 进行调用的,编译器会帮你做类型转换。
  2. 在声明一个method的receiver该是指针还是非指针类型时,你需要考虑两方面的内部,第 一方面是这个对象本身是不是特别大,如果声明为非指针变量时,调用会产生一次拷贝;第二方面是如果你用指针类型作为receiver,那么你一定要注意,这种指针类型指向 的始终是一块内存地址,就算你对其进行了拷贝。熟悉C或者C艹的人这里应该很快能明 白

使用嵌入类型扩展类型

使用嵌入结构体时,被嵌入结构体可以直接调用嵌入结构体的方法,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import "image/color"
type Point struct{ X, Y float64 }
type ColoredPoint struct {
Point
Color color.RGBA
}

red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}
fmt.Println(p.Distance(q.Point)) // "5" Distance是point类型的方法,p的类型为ColoredPoint,但可以直接调用Distance
p.ScaleBy(2)
q.ScaleBy(2)
fmt.Println(p.Distance(q.Point)) // "10" 但参数类型为Point时,必须显示调用point字段

方法值和方法表达式

可以将特定变量的方法调用赋值给变量,通过变量调用方法,其形式类似于函数变量的赋值:

1
2
3
4
5
6
7
p := Point{1, 2}
q := Point{4, 6}
distanceFromP := p.Distance // method value,选择器返回一个方法值
fmt.Println(distanceFromP(q)) // "5"

scaleP := p.ScaleBy // method value,选择器返回一个方法值
scaleP(2) // p becomes (2, 4)

p.distancep.ScaleBy称为选择器,选择器返回一个方法值。

在一个包的API需要一个函数值、且调用方希望操作的是某一个绑定了对象的方法的话,方
法”值”会非常实用。举例来说,下面例子中的time.AfterFunc这个函数的功能是在指定的延迟时间之后来执行一个(译注:另外的)函数。且这个函数操作的是一个Rocket对象r

1
2
3
4
type Rocket struct { /* ... */ }
func (r Rocket) Launch() { / ... */ }
r := new(Rocket)
time.AfterFunc(10 * time.Second, func() { r.Launch() }) //这里相当于将r.Launch封装为一个函数传入

直接用方法”值”传入AfterFunc的话可以更为简短:

1
time.AfterFunc(10 * time.Second, r.Launch)

和方法”值”相关的还有方法表达式。当调用一个方法时,与调用一个普通的函数相比,我们必须要用选择器(p.Distance)语法来指定方法的接收器。当T是一个类型时,方法表达式可能会写作T.f或者(*T).f,会返回一个函数”值”,这种函数会将
其第一个参数用作接收器,所以可以用通常(译注:不写选择器)的方式来对其进行调用:

1
2
3
4
5
6
7
8
9
10
11
p := Point{1, 2}
q := Point{4, 6}
//这里Point是类型名,其拥有一个方法func (p Point) Distance(),
distance := Point.Distance // method expression
fmt.Println(distance(p, q)) // "5"
fmt.Printf("%T\n", distance) // "func(Point, Point) float64"
scale := (*Point).ScaleBy
scale(&p, 2)
fmt.Println(p) // "{2 4}"
fmt.Printf("%T\n"
, scale) // "func(*Point, float64)"

以上的内容相当于将类型的一个方法转化为一个函数,该函数相较于方法多了第一个参数,该参数表明接收器,如上述将方法func (p Point) Distance()float64转化为func(Point, Point) float64

接口

在Go语言中还存在着另外一种类型:接口类型。接口类型是一种抽象的类型。它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合;它们只会展示出它们自己的方法。也就是说当你有看到一个接口类型的值时,你不知道它是什么,唯一知道的就是可以通过它的方法来做什么。

nil 接口值既不保存值也不保存具体类型。

也就是说,如果一个类型声明了某个接口给出的所有方法,则认为该类型继承了该接口,而无需显式说明。

常用内建接口

Stringer 是一个可以用字符串描述自己的类型。fmt 包(还有很多包)都通过此接口来打印值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Person struct {
Name string
Age int
}

func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z) //Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)
}

error接口

Go 程序使用 error 值来表示错误状态。与 fmt.Stringer 类似,error 类型是一个内建接口:

1
2
3
type error interface {
Error() string
}

(与 fmt.Stringer 类似,fmt 包在打印值时也会满足 error。)

http.Handler接口

1
2
3
4
5
package http
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
func ListenAndServe(address string, h Handler) error

ListenAndServe函数需要一个例如“localhost:8000”的服务器地址,和一个所有请求都可以分派的Handler接口实例。它会一直运行,直到这个服务因为一个错误而失败(或者启动失败),它的返回值一定是一个非空的错误。

类型断言

类型断言 提供了访问接口值底层具体值的方式。

1
t := i.(T)

该语句断言接口值 i 保存了具体类型 T,并将其底层类型为 T 的值赋予变量 t。若 i 并未保存 T 类型的值,该语句就会触发一个panic。

这里有两种可能。第一种,如果断言的类型T是一个具体类型,然后类型断言检查x的动态类型是否和T相同。如果这个检查成功了,类型断言的结果是x的动态值,当然它的类型是T。换句话说,具体类型的类型断言从它的操作对象中获得具体的值。如果检查失败,接下来这个操作会抛出panic。例如:

1
2
3
4
var w io.Writer
w = os.Stdout
f := w.(*os.File) // success: f == os.Stdout
c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer

上述中,w为os.Stdout,其类型为*os.File,所以w.(*os.File)断言成功,返回w的动态值,即os.Stdout。因为w的类型与*bytes.Buffer不符,所以c :=w.(*bytes.Buffer)运行时返回panic

如果断言类型T是接口类型,则类型断言检查x的动态类型是否满足T.如果此检查成功,在下面代码中,w.(io.ReadWriter)检查的是w的动态类型(即os.Stdout的动态类型io.ReadWriter),与w的接口io.Writer无关。

1
2
3
4
5
6
7
var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // success: *os.File has both Read and Write
w.Write([]byte("w write "))
// w.Read([]byte("w read")) //w.Read undefined (type io.Writer has no field or method Read)
rw.Read([]byte("rw read"))
rw.Write([]byte("rw write"))

在上面的第一个类型断言后,w和rw都持有os.Stdout因此它们每个有一个动态类型*os.File,但是变量w是一个io.Write, r类型只对外公开出文件的Write方法,然而rw变量同时公开它的Read和write方法。

如果断言操作的对象是一个nil接口值,那么不论被断言的类型是什么这个类型断言都会失败。

为了 判断 一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。

1
t, ok := i.(T)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
var i interface{} = "hello"

s := i.(string) //i保存的值的具体类型为string
fmt.Println(s)

s, ok := i.(string)
fmt.Println(s, ok)

f, ok := i.(float64)
fmt.Println(f, ok)

f = i.(float64) // 报错(panic)
fmt.Println(f)
}

类型选择

类型选择 是一种按顺序从几个类型断言中选择分支的结构。

类型选择与一般的 switch 语句相似,不过类型选择中的 case 为类型(而非值), 它们针对给定接口值所存储的值的类型进行比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}

func main() {
do(21)
do("hello")
do(true)
}

并发

Goroutines

Go 程(goroutine)是由 Go 运行时管理的轻量级线程。

1
go f(x, y, z)

会启动一个新的 Go 程并执行

Channels

信道是带有类型的管道,你可以通过它用信道操作符 <- 来发送或者接收值。

1
2
ch <- v    // 将 v 发送至信道 ch。
v := <-ch // 从 ch 接收值并赋予 v。

(“箭头”就是数据流的方向。)

和映射与切片一样,信道在使用前必须创建:

1
2
ch := make(chan int)
ch := make(chan int, 100) //带缓冲信道

默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。

关闭信道

发送者可通过 close(ch) 关闭一个信道来表示没有需要发送的值了。接收者可以通过为接收表达式分配第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,那么在执行完

1
v, ok := <-ch

之后 ok 会被设置为 false值。循环 for i := range c 会不断从信道接收值,直到它被关闭。

注意: 只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发程序恐慌(panic)。

当一个被关闭的channel中已经发送的数据都被成功接收后,后续的接收操作将不再阻塞,它们会立即返回一个零

还要注意: 信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有需要发送的值时才有必要关闭,例如终止一个 range 循环。

单向channel

Go语言的类型系统提供了单方向的channel类型,分别用于只发送或只接收的channel。类型 chan<- int表示一个只发送int的channel,只能发送不能接收。相反,类型<-chan int 表示一个只接收int的channel,只能接收不能发送。(箭头 <- 和关键字chan的相对位置表明了channel的方向。)这种限制将在编译期检测。

带缓存的channel

带缓存的Channel内部持有一个元素队列。队列的最大容量是在调用make函数创建channel时通过第二个参数指定的。下面的语句创建了一个可以持有三个字符串元素的带缓存Channel。

1
ch = make(chan string, 3)

使用内置函数cap可以获取channel缓存大小, 函数len可以获取channel有效数据个数

1
2
fmt.Println(cap(ch))     //缓冲区大小
fmt.Println(len(ch)) //channel中的有效数据个数
  • make(chan int)make(chan int,1)的区别

select

select 语句使一个 Go 程可以等待多个通信操作。

select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。

select 中的其它分支都没有准备好时,default 分支就会执行。为了在尝试发送或者接收时不发生阻塞,可使用 default 分支:

1
2
3
4
5
6
select {
case i := <-c:
// 使用 i
default:
// 从 c 中接收会阻塞时执行
}

Goroutines和线程

每一个OS线程都有一个固定大小的内存块(一般会是2MB)来做栈,这个栈会用来存储当前正在被调用或挂起(指在调用其它函数时)的函数的内部变量。

相反,一个goroutine会以一个很小的栈开始其生命周期,一般只需要2KB。一个goroutine的栈,和操作系统线程一样,会保存其活跃或挂起的函数调用的本地变量,但是和OS线程不太一样的是一个goroutine的栈大小并不是固定的;栈的大小会根据需要动态地伸缩。

调度

OS线程由操作系统内核调用调用时会产生上下文切换,消耗资源较多

GO自身包含调度器,其调度消耗小于线程切换

Goroutine没有ID号

在大多数支持多线程的操作系统和程序语言中,当前的线程都有一个独特的身份(id),并且这个身份信息可以以一个普通值的形式被被很容易地获取到,典型的可以是一个integer或者指针值。goroutine没有可以被程序员获取到的身份(id)的概念。

HTTP/HTTPS协议

URL/URI

URI 用字符串标识某一互联网资源,而 URL 表示资源的地点(互联 网上所处的位置)。可见 URL 是 URI 的子集。

1
2
3
4
5
6
//URI绝对格式
http://user:pass@www.example.jp:80/dir/index.html?uid=1#ch1
<-! 方案名://登录信息@服务器地址:端口号/文件路径?查询字符#片段标识符 >

https://blog.csdn.net/weixin_39780047/article/details/88850857
<协议>://<host>:<port>/<路径>
  • http默认端口是80,可省略
  • 省略路径时,默认指向主页

    操作过程

  1. 浏览器从 URL 中解析出服务器的主机名;
  2. 浏览器将服务器的主机名转换成服务器的 IP 地址;
  3. 浏览器将端口号(如果有的话)从 URL 中解析出来;
  4. 浏览器建立一条与 Web 服务器的 TCP 连接;
  5. 浏览器向服务器发送一条 HTTP 请求报文;
  6. 服务器向浏览器回送一条 HTTP 响应报文;
  7. 关闭连接,浏览器显示文档
  • http1.0是无状态的,每次访问数据时都要进行tcp的连接和释放。http1.1之后采用持续连接方式,tcp连接在完成请求后依然保留一段时间,可重复使用

    代理服务器

    代理服务器将最近的一些请求和响应暂存在本地中。当新请求到到达时,若代理服务器中包含相同资源,则返回暂存的请求响应。否则通过因特网获取请求响应并复制一份在本地(缓存服务器)

使用代理服务器的理由

利用缓存技术(稍后讲解)减少网络带宽 的流量,组织内部针对特定网站的访问控制,以获取访问日志为主要 目的,等等。

报文结构

http分为请求报文和响应报文

请求报文

响应报文

状态码

1** 通知信息
2** 表示成功
3** 重定向,还需要采取进一步行动
4** 客户端错误,语法错误活无法完成请求
5** 服务器错误,服务器在处理过程中发送生错误

常见状态:

  • 202 Accepted 接受
  • 400 Bad Request 错误请求
  • 404 Not Found 找不到

方法

方法

首部字段名

  1. 通用首部字段
首部字段名 说明
Cache-Control 控制缓存的行为
Connection 逐跳首部、连接的管理
Date 创建报文的日期时间
Pragma 报文指令
Trailer 报文末端的首部一览
Transfer-Encoding 指定报文主体的传输编码方式
Upgrade 升级为其他协议
Via 代理服务器的相关信息
Warning 错误通知
  1. 请求首部字段

    首部字段名 说明
    Accept 用户代理可处理的媒体类型
    Accept-Charset 优先的字符集
    Accept-Encoding 优先的内容编码
    Accept-Language 优先的语言(自然语言)
    Authorization Web认证信息
    Expect 期待服务器的特定行为
    From 用户的电子邮箱地址
    Host 请求资源所在服务器
    If-Match 比较实体标记(ETag)
    If-Modified-Since 比较资源的更新时间
    If-None-Match 比较实体标记(与 If-Match 相反)
    If-Range 资源未更新时发送实体 Byte 的范围请求
    If-Unmodified-Since 比较资源的更新时间(与If-Modified-Since相反)
    Max-Forwards 最大传输逐跳数
    Proxy-Authorization 代理服务器要求客户端的认证信息
    Range 实体的字节范围请求
    Referer 对请求中 URI 的原始获取方
    TE 传输编码的优先级
    User-Agent HTTP 客户端程序的信息
  1. 响应首部字段

    首部字段名 说明
    Accept-Ranges 是否接受字节范围请求
    Age 推算资源创建经过时间
    ETag 资源的匹配信息
    Location 令客户端重定向至指定URI
    Proxy-Authenticate 代理服务器对客户端的认证信息
    Retry-After 对再次发起请求的时机要求
    Server HTTP服务器的安装信息
    Vary 代理服务器缓存的管理信息
    WWW-Authenticate 服务器对客户端的认证信息
    4. 实体首部字段

    实体首部字段

    1. cookie

HTTPS

常用端口:443

  • HTTP+ 加密 + 认证 + 完整性保护 =HTTPS

    1553885477520

  • https采用共享密钥和公钥加密(处理慢,安全性高)两者并用的混合加密机制

    1. 使用公钥加密交换共享密钥
    2. 使用共享密钥进行通信

HTTP追加协议

解决HTTP性能瓶颈

  • Ajax——局部更新

  • Comet——保留响应

    • SPYD

      SPDY 没有完全改写 HTTP 协议,而是在 TCP/IP 的应用层与运输层之 间通过新加会话层的形式运作。同时,考虑到安全性问题,SPDY 规 定通信中使用 SSL。

      1553889339542

    • WebSocket

      一旦 Web 服务器与客户端之间建立起 WebSocket 协议的通信连接, 之后所有的通信都依靠这个专用协议进行。通信过程中可互相发送 JSON、XML、HTML 或图片等任意格式的数据。 由于是建立在 HTTP 基础上的协议,因此连接的发起方仍是客户端, 而一旦确立 WebSocket 通信连接,不论服务器还是客户端,任意一方 都可直接向对方发送报文

      为了实现 WebSocket 通信,需要用到 HTTP 的 Upgrade 首部字 段,告知服务器通信协议发生改变,以达到握手的目的

      特点

      1. 推送功能 ……服务器->客户端
      2. 减少通信量 ……保持连接状态,首部信息较少

Web攻击

  • Web 应用端(服务器端)的验证

  • 跨站脚本攻击

  • 对用户 Cookie 的窃取攻击

  • SQL 注入攻击

  • OS 命令注入攻击

  • 目录遍历攻击

  • 远程文件包含漏洞

总结

思维导图

1553905547631

  • 用单台虚拟主机实现多个域名

    提 供 Web 托管服务(Web Hosting Service)的供应商,可以用一台服务 器为多位客户服务,也可以以每位客户持有的域名运行各自不同的网 站。这是因为利用了虚拟主机(Virtual Host,又称虚拟服务器)的功 能。

    位于同一台服务器上的web服务器经过域名转换后,IP地址相同。因此在发送HTTP请求的时候,必须在host首部内完整指出主机名或域名的URI。

  • 客户端如何知道web服务通过虚拟主机?

  • 在host首部字段说明完整的域名,通过域名来区分同一服务器上的虚拟主机

  • URL和URI的区别

URL:(Uniform/Universal Resource Locator 的缩写,统一资源定位符)

URI:(Uniform Resource Identifier 的缩写,统一资源标识符)(代表一种标准)。

URI 属于 URL 更高层次的抽象,一种字符串文本标准。

URL是一种具体的URI,它是URI的一个子集,它不仅唯一标识资源,而且还提供了定位该资源的信息。URI 是一种语义上的抽象概念,可以是绝对的,也可以是相对的,而URL则必须提供足够的信息来定位,是绝对的。

  • GET和POST的区别
GET POST
回退时 无害 再次请求
cache 需要手动设置
编码 URL 多种编码
浏览器历史记录 保留 不保留
长度限制 受浏览器限制
参数数据类型 ASCII 任意
传递方式 URL request body
传输数据包 1个 2个
  • 两者本质上都是采用TCP协议进行传输,因此在传输过程中没有差异

  • 幂等请求

HTTP幂等方法,是指无论调用这个url多少次,都不会有不同的结果的HTTP方法。

HTTP GET方法用于获取资源,不应有副作用,所以是幂等的。 GET请求可能会每次得到不同的结果,但它本身并没有产生任何副作用,因而是满足幂等性的。

调用多次的结果和调用一次的结果一致,如DELETE调用一次删除指定资源,再次调用无论该资源是否存在,依然删除该资源。因此其是幂等