python之socket编程TCP API

需要掌握: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


原文出处:/show_1EF2SE6SsVBs.html