python基础网络编程--转
Posted Luminous安全杂谈
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python基础网络编程--转相关的知识,希望对你有一定的参考价值。
本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类:
-
消息传递(管道、FIFO、消息队列)
-
同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
-
共享内存(匿名的和具名的)
-
远程过程调用(Solaris门和Sun RPC)
但这些都不是本文的主题!我们要讨论的是网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆socket”。
网络编程对所有开发语言都是一样的,Python也不例外。用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。
(1) IP、TCP和UDP
当您编写socket应用程序的时候,您可以在使用TCP还是使用UDP之间做出选择。它们都有各自的优点和缺点。
TCP是流协议,而UDP是数据报协议。换句话说,TCP在客户机和服务器之间建立持续的开放连接,在该连接的生命期内,字节可以通过该连接写出(并且保证顺序正确)。然而,通过 TCP 写出的字节没有内置的结构,所以需要高层协议在被传输的字节流内部分隔数据记录和字段。
另一方面,UDP不需要在客户机和服务器之间建立连接,它只是在地址之间传输报文。UDP的一个很好特性在于它的包是自分隔的(self-delimiting),也就是一个数据报都准确地指出它的开始和结束位置。然而,UDP的一个可能的缺点在于,它不保证包将会按顺序到达,甚至根本就不保证。当然,建立在UDP之上的高层协议可能会提供握手和确认功能。
对于理解TCP和UDP之间的区别来说,一个有用的类比就是电话呼叫和邮寄信件之间的区别。在呼叫者用铃声通知接收者,并且接收者拿起听筒之前,电话呼叫不是活动的。只要没有一方挂断,该电话信道就保持活动,但是在通话期间,他们可以自由地想说多少就说多少。来自任何一方的谈话都按临时的顺序发生。另一方面,当你发一封信的时候,邮局在投递时既不对接收方是否存在作任何保证,也不对信件投递将花多长时间做出有力保证。接收方可能按与信件的发送顺序不同的顺序接收不同的信件,并且发送方也可能在他们发送信件是交替地接收邮件。与(理想的)邮政服务不同,无法送达的信件总是被送到死信办公室处理,而不再返回给发送。
(2)对等方、端口、名称和地址
除了TCP和UDP协议以外,通信一方(客户机或者服务器)还需要知道的关于与之通信的对方机器的两件事情:IP地址或者端口。IP地址是一个32位的数据值,为了人们好记,一般用圆点分开的4组数字的形式来表示,比如:64.41.64.172。端口是一个16位的数据值,通常被简单地表示为一个小于65536的数字。大多数情况下,该值介于10到100的范围内。一个IP地址获取送到某台机器的一个数据包,而一个端口让机器决定将该数据包交给哪个进程/服务(如果有的话)。这种解释略显简单,但基本思路是正确的。
上面的描述几乎都是正确的,但它也遗漏了一些东西。大多数时候,当人们考虑Internet主机(对等方)时,我们都不会记忆诸如64.41.64.172这样的数字,而是记忆诸如gnosis.cx这样的名称。为了找到与某个特定主机名称相关联的IP地址,一般都使用域名服务器(DNS),但是有时会首先使用本地查找(经常是通过/etc/hosts的内容)。对于本教程,我们将一般地假设有一个IP地址可用,不过下面讨论编写名称查找代码。
(3)主机名称解析
命令行实用程序nslookup可以被用来根据符号名称查找主机IP地址。实际上,许多常见的实用程序,比如ping或者网络配置工具,也会顺便做同样的事情。但是以编程方式做这样的事情很简单。
======================TCP/IP======================
应用层: 它只负责产生相应格式的数据 ssh ftp nfs cifs dns http smtp pop3
-----------------------------------
传输层: 定义数据传输的两种模式:
TCP(传输控制协议:面向连接,可靠的,效率相对不高)
UDP(用户数据报协议:非面向连接,不可靠的,但效率高)
-----------------------------------
网络层: 连接不同的网络如以太网、令牌环网
IP (路由,分片) 、ICMP、 IGMP
ARP ( 地址解析协议,作用是将IP解析成MAC )
-----------------------------------
数据链路层: 以太网传输
-----------------------------------
物理层: 主要任务是规定各种传输介质和接口与传输信号相关的一些特性
-----------------------------------
TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
TCP socket 由于在通向前需要建立连接,所以其模式较 UDP socket 负责些。
UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。如图:
UDP Socket图:
UDP socket server 端代码在进行bind后,无需调用listen方法。
TCP/IP协议族包括运输层、网络层、链路层,
而socket所在位置如图,Socket是应用层与TCP/IP协议族通信的中间软件抽象层。
Socket是什么
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open
–> 读写write/read –>
关闭close”模式来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭).
说白了Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
注意:其实socket也没有层的概念,它只是一个facade设计模式的应用,让编程变的更简单。是一个软件抽象层。在网络编程中,我们大量用的都是通过socket实现的。
Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。
TCP编程
socket中TCP的三次握手建立连接详解
我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:
- 客户端向服务器发送一个SYN J
- 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
- 客户端再想服务器发一个确认ACK K+1
只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:
图1、socket中发送的TCP三次握手
从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。
总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
5、socket中TCP的四次握手释放连接详解
上面介绍了socket中TCP的三次握手建立过程,及其涉及的socket函数。现在我们介绍socket中的四次握手释放连接的过程,请看下图:
图2、socket中发送的TCP四次握手
图示过程如下:
-
某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
-
另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
-
一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
-
接收到这个FIN的源发送端TCP对它进行确认。
这样每个方向上都有一个FIN和ACK。
Python3 网络编程
Python 提供了两个级别访问的网络服务。:
- 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法。
- 高级别的网络服务模块 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。
什么是 Socket?
Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。
socket和file的区别:
- file模块是针对某个指定文件进行【打开】【读写】【关闭】
- socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
socket()函数
Python 中,我们用 socket()函数来创建套接字,语法格式如下:
1
|
socket.socket([family[, type [, proto]]]) |
参数
- family: 套接字家族可以使AF_UNIX或者AF_INET
- type: 套接字类型可以根据是面向连接的还是非连接分为
SOCK_STREAM
或SOCK_DGRAM
- protocol: 一般不填默认为0.
简单实例
服务端
我们使用 socket 模块的 socket 函数来创建一个 socket 对象。socket 对象可以通过调用其他函数来设置一个 socket 服务。
现在我们可以通过调用 bind(hostname, port) 函数来指定服务的 port(端口)。
接着,我们调用 socket 对象的 accept 方法。该方法等待客户端的连接,并返回 connection 对象,表示已连接到客户端。
完整代码如下:
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
27
28
29
30
31
|
#!/usr/bin/python3 # 文件名:server.py # 导入 socket、sys 模块 import socket import sys # 创建 socket 对象 serversocket = socket.socket( socket.AF_INET, socket.SOCK_STREAM) # 获取本地主机名 host = socket.gethostname() port = 9999 # 绑定端口 serversocket.bind((host, port)) # 设置最大连接数,超过后排队 serversocket.listen( 5 ) while True : # 建立客户端连接 clientsocket,addr = serversocket.accept() print ( "连接地址: %s" % str (addr)) msg = \'欢迎访问python教程!\' + "\\r\\n" clientsocket.send(msg.encode( \'utf-8\' )) clientsocket.close() |
客户端
接下来我们写一个简单的客户端实例连接到以上创建的服务。端口号为 12345。
socket.connect(hosname, port ) 方法打开一个 TCP 连接到主机为 hostname 端口为 port 的服务商。连接后我们就可以从服务端后期数据,记住,操作完成后需要关闭连接。
完整代码如下:
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
|
#!/usr/bin/python3 # 文件名:client.py # 导入 socket、sys 模块 import socket import sys # 创建 socket 对象 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 获取本地主机名 host = socket.gethostname() # 设置端口好 port = 9999 # 连接服务,指定主机和端口 s.connect((host, port)) # 接收小于 1024 字节的数据 msg = s.recv( 1024 ) s.close() print (msg.decode( \'utf-8\' )) |
先执行server端,然后打开client端就能看到结果
客户端
大多数连接都是可靠的TCP连接。创建TCP连接时,主动发起连接的叫客户端,被动响应连接的叫服务器。
举个例子,当我们在浏览器中访问新浪时,我们自己的计算机就是客户端,浏览器会主动向新浪的服务器发起连接。如果一切顺利,新浪的服务器接受了我们的连接,一个TCP连接就建立起来的,后面的通信就是发送网页内容了。
所以,我们要创建一个基于TCP连接的Socket,可以这样做:
1
2
3
4
5
6
7
|
# 导入socket库: import socket # 创建一个socket: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 建立连接: s.connect(( \'www.sina.com.cn\' , 80 )) |
创建Socket
时,AF_INET
指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6
。SOCK_STREAM
指定使用面向流的TCP协议,这样,一个Socket
对象就创建成功,但是还没有建立连接。
客户端要主动发起TCP连接,必须知道服务器的IP地址和端口号。新浪网站的IP地址可以用域名www.sina.com.cn
自动转换到IP地址,但是怎么知道新浪服务器的端口号呢?
答案是作为服务器,提供什么样的服务,端口号就必须固定下来。由于我们想要访问网页,因此新浪提供网页服务的服务器必须把端口号固定在80
端口,因为80
端口是Web服务的标准端口。其他服务都有对应的标准端口号,例如SMTP服务是25
端口,FTP服务是21
端口,等等。端口号小于1024的是Internet标准服务的端口,端口号大于1024的,可以任意使用。
因此,我们连接新浪服务器的代码如下:
1
|
s.connect(( \'www.sina.com.cn\' , 80 )) |
注意参数是一个tuple
,包含地址和端口号。
建立TCP连接后,我们就可以向新浪服务器发送请求,要求返回首页的内容:
1
2
|
# 发送数据: s.send(b \'GET / HTTP/1.1\\r\\nHost: www.sina.com.cn\\r\\nConnection: close\\r\\n\\r\\n\' ) |
TCP连接创建的是双向通道,双方都可以同时给对方发数据。但是谁先发谁后发,怎么协调,要根据具体的协议来决定。例如,HTTP协议规定客户端必须先发请求给服务器,服务器收到后才发数据给客户端。
发送的文本格式必须符合HTTP标准,如果格式没问题,接下来就可以接收新浪服务器返回的数据了:
接收数据时,调用recv(max)
方法,一次最多接收指定的字节数,因此,在一个while循环中反复接收,直到recv()
返回空数据,表示接收完毕,退出循环。
当我们接收完数据后,调用close()
方法关闭Socket,这样,一次完整的网络通信就结束了:
1
2
|
# 关闭连接: s.close() |
接收到的数据包括HTTP头和网页本身,我们只需要把HTTP头和网页分离一下,把HTTP头打印出来,网页内容保存到文件:
1
2
3
4
5
|
header, html = data.split(b \'\\r\\n\\r\\n\' , 1 ) print (header.decode( \'utf-8\' )) # 把接收的数据写入文件: with open ( \'sina.html\' , \'wb\' ) as f: f.write(html) |
现在,只需要在浏览器中打开这个sina.html
文件,就可以看到新浪的首页了。
服务器
和客户端编程相比,服务器编程就要复杂一些。
服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来了,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。
所以,服务器会打开固定端口(比如80)监听,每来一个客户端连接,就创建该Socket连接。由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket。
但是服务器还需要同时响应多个客户端的请求,所以,每个连接都需要一个新的进程或者新的线程来处理,否则,服务器一次就只能服务一个客户端了。
我们来编写一个简单的服务器程序,它接收客户端连接,把客户端发过来的字符串加上Hello
再发回去。
首先,创建一个基于IPv4和TCP协议的Socket:
1
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
然后,我们要绑定监听的地址和端口。服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,也可以用0.0.0.0
绑定到所有的网络地址,还可以用127.0.0.1
绑定到本机地址。127.0.0.1
是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。
端口号需要预先指定。因为我们写的这个服务不是标准服务,所以用9999
这个端口号。请注意,小于1024
的端口号必须要有管理员权限才能绑定:
1
2
|
# 监听端口: s.bind(( \'127.0.0.1\' , 9999 )) |
紧接着,调用listen()
方法开始监听端口,传入的参数指定等待连接的最大数量:
1
2
|
s.listen( 5 ) print ( \'Waiting for connection...\' ) |
接下来,服务器程序通过一个永久循环来接受来自客户端的连接,accept()
会等待并返回一个客户端的连接:
1
2
3
4
5
6
|
while True : # 接受一个新连接: sock, addr = s.accept() # 创建新线程来处理TCP连接: t = threading.Thread(target = tcplink, args = (sock, addr)) t.start() |
每个连接都必须创建新线程(或进程)来处理,否则,单线程在处理连接的过程中,无法接受其他客户端的连接:
1
2
3
4
5
6
7
8
9
10
11
|
def tcplink(sock, addr): print ( \'Accept new connection from %s:%s...\' % addr) sock.send(b \'Welcome!\' ) while True : data = sock.recv( 1024 ) time.sleep( 1 ) if not data or data.decode( \'utf-8\' ) = = \'exit\' : break sock.send(( \'Hello, %s!\' % data.decode( \'utf-8\' )).encode( \'utf-8\' )) sock.close() print ( \'Connection from %s:%s closed.\' % addr) |
连接建立后,服务器首先发一条欢迎消息,然后等待客户端数据,并加上Hello
再发送给客户端。如果客户端发送了exit
字符串,就直接关闭连接。
要测试这个服务器程序,我们还需要编写一个客户端程序:
1
2
3
4
5
6
7
8
9
10
11
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 建立连接: s.connect(( \'127.0.0.1\' , 9999 )) # 接收欢迎消息: print (s.recv( 1024 ).decode( \'utf-8\' )) for data in [b \'Michael\' , b \'Tracy\' , b \'Sarah\' ]: # 发送数据: s.send(data) print (s.recv( 1024 ).decode( \'utf-8\' )) s.send(b \'exit\' ) s.close() |
我们需要打开两个命令行窗口,一个运行服务器程序,另一个运行客户端程序,就可以看到效果了:
UDP编程
TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。
使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。
虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。
我们来看看如何通过UDP协议传输数据。和TCP类似,使用UDP的通信双方也分为客户端和服务器。服务器首先需要绑定端口:
1
2
3
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定端口: s.bind(( \'127.0.0.1\' , 9999 )) |
创建Socket时,SOCK_DGRAM
指定了这个Socket的类型是UDP。绑定端口和TCP一样,但是不需要调用listen()
方法,而是直接接收来自任何客户端的数据:
1
2
3
4
5
6
|
print \'Bind UDP on 9999...\' while True : # 接收数据: data, addr = s.recvfrom( 1024 ) print \'Received from %s:%s.\' % addr s.sendto( \'Hello, %s!\' % data, addr) |
recvfrom()
方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用sendto()
就可以把数据用UDP发给客户端。
注意这里省掉了多线程,因为这个例子很简单。
客户端使用UDP时,首先仍然创建基于UDP的Socket,然后,不需要调用connect()
,直接通过sendto()
给服务器发数据:
1
2
3
4
5
6
7
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for data in [ \'Michael\' , \'Tracy\' , \'Sarah\' ]: # 发送数据: s.sendto(data, ( \'127.0.0.1\' , 9999 )) # 接收数据: print s.recv( 1024 ) s.close() |
从服务器接收数据仍然调用recv()
方法。
小结
UDP的使用与TCP类似,但是不需要建立连接。此外,服务器绑定UDP端口和TCP端口互不冲突,也就是说,UDP的9999端口与TCP的9999端口可以各自绑定。
Python 提供了两个级别访问的网络服务。:
- 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法。
- 高级别的网络服务模块 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。
Socket 对象(内建)方法
函数 | 描述 |
---|---|
服务器端套接字 | |
s.bind() | 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。 |
s.l 以上是关于python基础网络编程--转的主要内容,如果未能解决你的问题,请参考以下文章 django.core.exceptions.ImproperlyConfigured: Requested setting DEFAULT_INDEX_TABLESPACE的解决办法(转)(代码片段 django.core.exceptions.ImproperlyConfigured: Requested setting DEFAULT_INDEX_TABLESPACE的解决办法(转)(代码片段 |