需要掌握:socket
概述
本节介绍的是socket编程,是采用TCP协议,在编程中要有服务器端和客户端,一般先实现服务器端,服务器监听IP和端口,然后实现客户的,客户端连接服务器的IP和端口,然后发送、接收数据参见,过程参见 socket
socket是常见开发语言(C/C++/JAVA/C#)的功能之一
需要导入socket模块
socket源码
socket源代码位置:
python安装根目录/Lib/socket.py
导入_socket模块,socket类继承_socket.socket,
模块_socket的位置:
python安装根目录/DLLs/_socket.pyd
什么是.pyd文件?见下面链接:
http://www.malaoshi.top/show_1EF2Zg2nxB6r.html
创建socket对象
socket编程中,无论实现服务器端还是客户端,首先要创建socket对象,然后才能进行操作
语法
socket([family], [type], [proto])
参数说明:
family:套接字家族,有三个值
- socket.AF_INET 使用IPv4(默认)
- socket.AF_INET6 使用IPv6
- socket.AF_UNIX 只用于单一的Unix系统进程间通信
type:套接字类型,有五个值:
- socket.SOCK_STREAM:流式socket , 即TCP (默认)
- socket.SOCK_DGRAM:数据报式socket ,即UDP
- socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
- socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
- socket.SOCK_SEQPACKET 可靠的连续数据包服务
protocol:协议。默认是0,与特定的套接字家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议
例子
s = socket.socket()
服务器端api说明
绑定地址
服务器端创建socket对象后,将套接字绑定地址。地址格式取决于套接字族。
语法
bind(address)
参数说明:
- address:在AF_INET下(即IPv4),以元组(host,port)的形式表示地址。
例子
s.bind(('0.0.0.0', 9999))
绑定IP地址0.0.0.0,端口号是9999
关于IP地址0.0.0.0,见下面链接:
http://www.malaoshi.top/show_1EF2SXCO6P0v.html
开始监听
绑定IP和端口后,开始监听传入连接(让socket由主动套接字变为被动套接字)
语法
listen(backlog)
参数说明:
- backlog:指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5。
比如:backlog设置5,表示操作系统可以接到5个连接请求,有一个连接请求正在处理,其余4个处于等待状态,此时有第6个请求,操作系统会直接拒绝
注意:
这个值 不能无限大,因为要在操作系统中维护连接队列
与操作系统有关
python之socket.listen()参数实验
例子:
s.listen(5)
接收客户的连接
然后接收客户端连接,是阻塞式的,即:当没有客户端连接时,程序一直停在这里,不在执行下面代码
语法
accept()
参数说明:
无
返回值说明:
返回元祖类型:(conn,address),其中:
- conn:客户端的套接字对象,可以用来接收和发送数据。
- address:客户端的地址
例子:
conn, address = s.accept()
完整服务器代码
s = socket.socket()
s.bind(('127.0.0.1', 9999))
s.listen(5)
print('开始监听')
conn, address = s.accept()
print('等待接收到客户端请求')
print('conn类型',type(conn))
print('address类型',type(address))
通过执行结果,可以发现只打印开始监听,程序运行到conn, address = s.accept()就不在向下执行了,这就是阻塞式
要观察该服务器端的执行结果,要按照下面编写客户端代码,为了便于观察,这里直接给出执行结果:
开始监听
接收到客户端请求
conn类型 <class 'socket.socket'>
address类型 <class 'tuple'> ('127.0.0.1', 51222)
可知代码
conn, address = s.accept()
返回的conn是socket类型,该conn是当前客户端的socket
客户端api说明
连接服务器-需指明IPv4、IPv6
连接服务器,address的格式为元组(ip,port),如果连接出错,抛出异常。
语法
connect(address)
参数说明:
- address:在AF_INET下(即IPv4),以元组
(ip,port)的形式表示。
例子1:
连接一个不存在的IP,观察错误,代码如下:
import socket
s = socket.socket()
s.connect(('33.0.0.1', 9999))
错误如下:
TimeoutError: [WinError 10060] 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。
例子2:
连接本机但不通的端口,观察错误,代码如下:
import socket
s = socket.socket()
s.connect(('127.0.0.1', 2999))
错误如下:
ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。
例子3:
连接正确的服务器IP和端口,观察执行结果,代码如下:
import socket
s = socket.socket()
s.connect(('127.0.0.1', 9999))
观察上面的完整服务器代码的执行结果
连接服务器-高级
连接到一个监听地址addr的TCP服务上,并返回一个新的套接字对象。
这个函数比socket.connetc()更高级,如果addr(host, port)中的host是一个非数值的值,那么该函数会同时尝试 AF_INET 和 AF_INET6 来解析它,然后尝试所有获得的可能地址,该方法可以用来编写同时支持 IPv4 和 IPv6 的客户端。
语法
socket.create_connection(addr[, timeout[, source_addr]])
参数:
- addr —— 服务器套接字地址
- timeout —— 套接字的超时限制,如果没有提供,那么使用getdefaulttimeout()的设置
- source_address —— 如果提供的话,该参数必须是(host, port)形式的二元组,套接字会在连接前绑定到这个指定的地址。
返回:
返回一个连接到目标地址的新的套接字对象。
例子:
import socket
s = socket.create_connection(('33.0.0.1', 9999))
通用api说明
完整发送TCP数据
完整发送TCP数据,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
sendall(data)
参数说明:
- data:bytes类型,发送到连接的套接字。
返回值:
成功返回None
例子:
import socket
s = socket.socket()
s.connect(('127.0.0.1', 9999))
s.sendall('Hello Server!'.encode('utf-8'))
发送TCP数据(不推荐)
发送TCP数据,当数据长度大于MTU时,网络层将对数据进行分片,分片后只会发送一次,并返回该长度,不会将剩下的数据发送出去
语法
send(data)
参数说明:
- data:bytes类型,发送到连接的套接字。
返回值:
发送的数据长度(单位:字节),该长度可能小于data的实际长度。
例子:
import socket
s = socket.socket()
s.connect(('127.0.0.1', 9999))
s.send('Hello Python!'.encode('utf-8'))
接收TCP数据
接收TCP数据。
语法
b=recv(bufsize,flag)
参数说明:
- bufsize指定要接收的最大数据量。
- flag提供有关消息的其他信息,通常可以忽略。
返回值:
返回bytes类型数据
抛异常:
远端程序直接退出(没有执行socket的close()),会抛出异常,如下:
Traceback (most recent call last):
File "c:/Users/mym/Desktop/python/tcp_test.py", line 16, in <module>
data=conn.recv(1024)
ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。
注意:
当 长度为0时,表示 远端socket已经close
详见:http://www.malaoshi.top/show_1EF30VlKWoVx.html
例子:
在服务器端接着写如下代码:
data=conn.recv(1024)
print(data.decode('utf-8'))
关闭套接字
使用后应及时关闭socket,回收资源。服务器端运行结束后,或者调用close()方法,才会释放端口
语法
close()
参数说明:
无
返回值:
无
更多api说明
见:
http://www.runoob.com/python3/python3-socket.html
完整代码
服务器端代码
import socket
s = socket.socket()
s.bind(('0.0.0.0', 9999))
s.listen(5)
print('开始监听')
conn, address = s.accept()
print('等待接收到客户端请求')
print('conn类型',type(conn))
print('address类型',type(address),address)
conn.sendall('server is ok!'.encode('utf-8'))
data=conn.recv(1024)
print("来自%s:%s的消息:"%(address[0],address[1]),data.decode('utf-8'))
conn.close()
客户端代码
import socket
s = socket.socket()
s.connect(('127.0.0.1', 9999))
data=s.recv(1024)
print('来自server的消息:',data.decode('utf-8'))
s.sendall('土豆土豆,我是茄子!'.encode('utf-8'))
s.close()
将服务器端部署在另一台计算机并运行,然后运行客户端代码
缺点
当服务器接收一个客户端请求后,服务器端就执行结束了,这显然是不合理的,服务器可以接收多个客户端多次请求
优化服务器端代码
让服务器端可以接收多次请求
import socket
s = socket.socket()
s.bind(('0.0.0.0', 9999))
s.listen(5)
print('开始监听')
#在这里写一个死循环,永远接收客户端请求
while True:
conn, address = s.accept()
print('等待接收到客户端请求')
print('conn类型',type(conn))
print('address类型',type(address),address)
conn.sendall('server is ok!'.encode('utf-8'))
data=conn.recv(1024)
print("来自%s:%s的消息:"%(address[0],address[1]),data.decode('utf-8'))
conn.close()
缺点
上面代码中,服务器只能接收客户端一次数据,就close了,这显然是不合理的,服务器可以接收一个客户端多次发送的数据
感谢以下文章:
http://www.runoob.com/python/python-socket.html
https://www.cnblogs.com/fanweibin/p/5053328.html
https://www.cnblogs.com/aylin/p/5572104.html
https://www.cnblogs.com/Security-Darren/p/4751391.html