python之socket编程TCP API 作者:马育民 • 2018-12-19 14:56 • 阅读:10621 需要掌握:[socket](http://www.malaoshi.top/show_1EF2xX1vPbGs.html "socket") # 概述 本节介绍的是socket编程,是采用TCP协议,在编程中要有服务器端和客户端,一般先实现服务器端,服务器监听IP和端口,然后实现客户的,客户端连接服务器的IP和端口,然后发送、接收数据参见,过程参见 [socket](http://www.malaoshi.top/show_1EF2xX1vPbGs.html "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()参数实验](https://www.malaoshi.top/show_1EF3N46D2NeU.html "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类型 address类型 ('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 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 原文出处:http://www.malaoshi.top/show_1EF2SE6SsVBs.html