首页 - 技术 - Python之路自动化日08_2网络编程

Python之路自动化日08_2网络编程

2023-10-06 21:13
-->

本节内容

  1. 插座介绍
  2. Socket参数介绍
  3. 基本套接字示例
  4. Socket实现多连接处理
  5. 通过 Socket 的简单 SSH
  6. 通过Socket进行文件传输
  7. 作业:开发一个支持多用户在线的FTP程序

1。插座介绍

概念

WANetwork Socket 是计算机网络连接的端点。今天。大多数网络套接字都是互联网套接字。更准确地说,套接字是本地程序可以传递给网络应用程序编程接口 (API) 以使用连接的句柄(抽象引用),例如“在此套接字上发送此数据”。

例如发送“Hello, world!”通过 TCP 连接到地址为 1.2.3.4 的主机的端口 80,人们可能会获得一个套接字,将其连接到远程主机,发送字符串,然后关闭套接字。

实现套接字至少需要以下步骤,(伪代码)

1
2
3
4
Socket 套接字 = getSocket(type = "TCP") #设置协议类型
连接(套接字,地址 = "1.2.3.4",端口 ="80") #连接远程机器
send(socket, “你好,世界!”)#发送消息
关闭(插座)#关闭连接

socket API 是一个应用程序编程接口 (API),通常由操作系统提供,允许应用程序控制和使用网络套接字。互联网套接字 API 通常基于 Berkeley 套接字标准。在伯克利套接字标准中,套接字是文件描述符的一种形式(文件 句柄),这是由于“一切都是文件”的 Unix 哲学,以及套接字和文件之间的类比:您可以阅读,写入、打开和关闭两者。   

套接字地址 是 IP 地址和端口号的组合,就像电话连接的一端是电话号码和特定分机号的组合一样。套接字不需要有地址(例如仅发送数据),但如果程序将套接字绑定到某个地址,则该套接字可用于接收发送到该地址的数据。根据此地址,互联网套接字将传入的数据包传送到适当的应用程序进程或线程。

插座家族(地址簇)

插座。AF_UNIX unix本机进程间通信

插座。AF_INET IPV4 

插座。AF_INET6 IPV6

这些常量代表地址(和协议)系列,用于socket()的第一个参数。如果未定义 AF_UNIX 常量,则不支持此协议。根据系统的不同,可能会有更多常数可用。

插座类型

套接字。SOCK_STREAM #for tcp

套接字。SOCK_DGRAM #用于 udp

socket.SOCK_RAW #原始套接字,普通套接字无法处理ICMP、IGMP等网络消息,但SOCK_RAW可以;其次,SOCK_RAW还可以处理特殊的IPv4消息;此外,使用原始的 For 套接字,用户可以通过 IP_HDRINCL 套接字选项构建 IP 标头。

socket.SOCK_RDM #是UDP的可靠形式,保证数据报的传送,但不保证顺序。 SOCK_RAM用于提供对原始协议的低级访问,并在需要执行某些特殊操作(例如发送ICMP消息)时使用。 SOCK_RAM 通常仅限于高级用户或管理员运行的程序。

套接字。SOCK_SEQPACKET #已过时

这些常量表示套接字类型,用于 socket() 的第二个参数。根据系统的不同,可能有更多常数可用。 (只有 SOCK_STREAMSOCK_DGRAM 似乎普遍有用。)

2。插座参数介绍

插座。插座(family=AF_INET, type=SOCK_STREAM
原型=0fileno=无)一定会

使用给定的地址族、套接字类型和协议号创建新套接字。地址族应为 AF_INET(默认)、AF_INET6、 AF_UNIXAF_CANAF_RDS。套接字类型应为SOCK_STREAM(默认)、SOCK_DGRAMSOCK_RAW或也许是其他 SOCK_ 常量之一。协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应为CAN_RAWCAN_BCM之一。如果指定了 fileno,则忽略其他参数,导致返回具有指定文件描述符的套接字。与 socket.fromfd() 不同,fileno 将返回相同的套接字,而不是重复的。这可能有助于使用socket.close().

关闭分离的套接字

插座。插座对([family[, 类型[, 原型]]])

使用给定的地址族、套接字类型和协议号构建一对连接的套接字对象。地址族、套接字类型和协议号与上面的 socket() 函数相同。默认系列为AF_UNIX(如果在平台上定义);否则,默认值为AF_INET

套接字。创建连接地址[,超时[,来源地址]])

连接到在互联网上侦听的 TCP 服务地址(2 元组(主机,端口)),并返回套接字对象。这是一个比socket.connect()更高级别的函数:如果host是非数字主机名,它将尝试为AF_INET解析它和AF_INET6,然后依次尝试连接所有可能的地址,直到连接成功。这使得编写兼容 IPv4 和 IPv6 的客户端变得容易。

传递可选的timeout参数将在尝试连接之前设置套接字实例的超时。如果未提供timeout,则使用由getdefaulttimeout()返回的全局默认超时设置。

L如果提供,源地址 必须是 2 元组 (主机、端口),以便将套接字绑定到其源面连接。如果主机或端口分别为 '' 或 0,则将使用操作系统默认行为。

套接字。getaddrinfo主机端口, 家庭=0,类型=0, proto=0, flags=0) #获取要连接的对端主机地址 必须知道

sk.bind(地址)一定会

  s.bind(address) 将套接字绑定到地址。地址的格式取决于地址族。在AF_INET下,地址以元组(主机,端口)的形式表示。

sk.listen(backlog)一定会

   开始侦听传入连接。积压指定在连接被拒绝之前可以挂起的最大连接数。

backlog等于5,表示内核已经收到连接请求,但服务端还没有调用accept进行处理。最大连接数为5
这个值不能无限,因为连接队列必须在内核中维护

sk.setblocking(bool) 一定会

  是否阻塞(默认True),如果设置为False,如果accept和recv时没有数据,就会报错。

sk.accept() 一定会

  接受连接并返回(conn,address),其中conn是一个新的socket对象,可用于接收和发送数据。地址是连接到客户端的地址。

  接收TCP客户端连接(阻塞)等待连接到来

sk.connect(地址)一定会

  连接到地址处的套接字。一般来说,地址的格式是一个元组(主机名,端口)。如果发生连接错误,则返回socket.error错误。

sk.connect_ex(地址)

   与上面相同,只是会有一个返回值。连接成功时返回0,连接失败时返回代码,例如:10061

sk.close() 一定会

  关闭插座

sk.recv(bufsize[,flag]) 一定会

   从套接字接受数据。数据以字符串形式返回,bufsize指定可以接收的最大数量。 flag 提供有关消息的附加信息,通常可以忽略。

sk.recvfrom(bufsize[.flag])

  与recv()类似,但返回值为(数据,地址)。其中data是包含接收到的数据的字符串,address是数据发送到的套接字地址。

sk.send(string[,flag]) 一定会

   将字符串中的数据发送到连接的套接字。返回值是要发送的字节数,它可能小于字符串的字节大小。即:所有指定的内容都可以不发送。

sk.sendall(string[,flag]) 一定会

   将字符串中的数据发送到连接的套接字,但在返回之前尝试发送所有数据。成功时返回 None,失败时抛出异常。

内部递归调用send发送所有内容。

sk.sendto(字符串[,标志],地址)

   向套接字发送数据,地址是(ipaddr,端口)形式的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。

sk.settimeout(timeout) 一定会

   设置套接字操作的超时时间。超时是一个以秒为单位的浮点数。 None 值表示没有超时时间。一般情况下,超时时间应该在第一次创建socket时设置,因为它们可能会用于连接操作(比如客户端连接等待长达5s)

sk.getpeername() 一定会

   返回已连接套接字的远程地址。返回值通常是一个元组(ipaddr、端口)。

sk.getsockname()

  返回套接字自己的地址。通常是一个元组 (ipaddr,port)

sk.fileno()

  套接字的文件描述符

插座。发送文件文件偏移=0计数=无)

发送文件,但大多数情况下没什么用。

SocketServer

socketserver模块简化了编写网络服务器的任务。

有四种基本的具体服务器类:

socketserver。TCPServer(server_address,请求处理程序类, bind_and_activate=True)

它使用 Internet TCP 协议,该协议提供客户端和服务器之间的连续数据流。如果 bind_and_activate 为 true,则构造函数会自动尝试调用server_bind() 和server_activate()。其他参数传递给BaseServer基类。

socketserver。UDPServer(server_address,RequestHandlerClassbind_and_activate=True

这使用数据报,数据报是离散的信息包,可能会无序到达或在传输过程中丢失。参数与TCPServer相同。

socketserver。UnixStreamServer(server_address,RequestHandlerClassbind_and_activate=True
socketserver。UnixDatagramServerserver_addressRequestHandlerClass ,bind_and_activate=True)

这些不常用的类与 TCP 和 UDP 类类似,但使用 Unix 域套接字;它们在非 Unix 平台上不可用。参数与TCPServer相同。

这四个类同步处理请求;每个请求必须在下一个请求开始之前完成。如果每个请求需要很长时间才能完成,那么这是不合适的,因为它需要大量计算,或者因为它返回客户端处理速度很慢的大量数据。解决方案是创建一个单独的进程或线程来处理每个请求; ForkingMixInThreadingMixIn混合类可用于支持异步行为。

继承图中有五个类,其中四个代表四种类型的同步服务器:

+------------+
|基础服务器 |
+------------+
|
v
+------------+ +------------------+
| TCP服务器 |------->| UnixStreamServer |
+------------+ +------------------+
|
v
+------------+ +--------------------+
| UDP服务器|------->| UnixDatagramServer |
+------------+ +--------------------+

请注意,UnixDatagramServer源自UDPServer,而不是UnixStreamServer — IP 和 Unix 流服务器之间的唯一区别是地址族,即只需在两个 Unix 服务器类中重复即可。

套接字服务器。ForkingMixIn
套接字服务器。ThreadingMixIn

可以使用这些混合类创建每种类型服务器的分叉和线程版本。例如,ThreadingUDPServer创建如下:

类 ThreadingUDPServer(ThreadingMixIn, UDPServer):
经过

混合类首先出现,因为它覆盖了UDPServer中定义的方法。设置各种属性也会改变底层服务器机制的行为。

套接字服务器。ForkingTCPServer
套接字服务器。ForkingUDP服务器
套接字服务器。ThreadingTCPServer
套接字服务器。ThreadingUDP服务器

这些类是使用混合类预定义的。

请求处理程序对象

套接字服务器。BaseRequestHandler

这是所有请求处理程序对象的超类。它定义了接口,如下所示。具体的请求处理程序子类必须定义一个新的 handle() 方法,并且可以重写任何其他方法。为每个请求创建一个新的子类实例。

设置()

handle()方法之前调用,以执行所需的任何初始化操作。默认实现不执行任何操作。

手柄()

此函数必须完成服务请求所需的所有工作。默认实现不执行任何操作。它有几个可用的实例属性;该请求可作为 self.request 获得;客户端地址为self.client_address;服务器实例为self.server,以防需要访问每个服务器的信息。

self.request对于数据报或流服务来说是不同的。对于流服务,self.request 是一个套接字对象;对于数据报服务,self.request 是一对字符串和套接字。

完成()

handle()方法之后调用,以执行所需的任何清理操作。默认实现不执行任何操作。如果 setup() 引发异常,则不会调用此函数。

socketserver.TCPServer 示例

服务器端

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
导入 套接字服务器
 MyTCPHandler(socketserver.BaseRequestHandler):
    """
    我们服务器的请求处理程序类。
    每次连接到服务器时都会实例化一次,并且必须
    重写handle()方法实现与的通信
    客户。
    """
    def 手柄(自身):
        # self.request是连接到客户端的TCP套接字
        self.data = self.request.recv() ).strip()
        打印("{}写道:".格式(self.client_address[0]))
        打印(自己.data)
        #只发回相同的数据,但是大写
        self.request.sendall(self.data.upper())
if __名称__ == “__main__”:
    主机,端口= “本地主机”9999
    # 创建服务器,绑定到 localhost 端口 9999
    服务器= socketserver.TCPServer((HOST, PORT), MyTCPHandler)
    #激活服务器;这将继续运行,直到您
    # 使用 Ctrl-C 中断程序
    server.serve_forever()

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
进口 插座
导入 sys
主机,端口= “本地主机”9999
数据="".join(sys.argv[1:])
#创建套接字(SOCK_STREAM表示TCP套接字)
袜子= socket.socket(www.gsm-guard.net_INET,socket.SOCK_STREAM)
尝试
    #连接服务器并发送数据
    sock.connect((主机,端口))
    sock.sendall(字节(数据+"\n""utf-8" ))
    #从服务器接收数据并关闭
    已收到= str(sock.recv(1024), “utf-8”)
终于
    sock.close()
打印("已发送:     {}".格式(数据))
打印("已收到:{}".格式(已收到))

上面这个例子你会发现,依然不能实现多并发,哈哈,在server端做一下更改就可以了

1
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)

改成

1
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
-->