Python网络编程02 socket模块

1、socket模块的socket类

  • 要创建套接字,必须使用socket.socket()类
    • socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
      • 地址簇(family)应为AF_INET(默认)、AF_INET6、AF_UNIX、AF_CAN、AF_PACKET或AF_RDS其中之一。
      • 套接字类型(type)应为SOCK_STREAM(默认)、SOCK_DGRAM、SOCK_RAW或其他SOCK_常量之一。
      • 协议号(proto)通常为0,可以省略,或者在地址簇为AF_CAN的情况下,协议号应为CAN_RAW、CAN_BCM或CAN_ISOTP之一。
      • 如果指定了 fileno,那么将从这一指定的文件描述符中自动检测 family、type 和 proto 的值。如果调用本函数时显式指定了 family、type 或 proto 参数,可以覆盖自动检测的值。
  • 创建TCP/IP套接字
    • tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  • 创建UDP/IP套接字
    • udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  • socket类的方法和属性

2、创建TCP服务器及客户端

  • python3中数据的传输必须是byte类型
    • bytes(data,'utf8')    #将字符串转换为byte类型
    • str(data,'utf8')         #将其他类型转换为字符串

1、创建TCP服务器

  • 创建通用TCP服务器的一般伪代码:
ss = socket(family,type)         #创建服务器套接字
#ss.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)    #因为前一次运行使套接字处于 TIME_WAIT 状态,无法立即重用。SO_REUSEADDR 标志告诉内核将处于 TIME_WAIT 状态的本地套接字重新使用,而不必等到固有的超时到期。
ss.bind(address)                 #套接字与地址绑定
ss.listen(backlog)               #监听连接
inf_loop:                        #服务器无限循环
    connection, address = ss.accept()          #接受客户端连接
    comm_loop:                   #通信循环
        cs.recv() / cs.send()    #对话(接收/发送)
    cs.close()                   #关闭客户端套接字
ss.close()                       #关闭服务器套接字#(可选)
  • 创建套接字:socket.socket(family, type)
    • family参数代表地址家族,可为AF_INETAF_UNIX。AF_INET家族包括Internet地址,AF_UNIX家族用于同一台机器上的进程间通信。
    • type参数代表套接字类型,可为SOCK_STREAM(流套接字,就是TCP套接字)和SOCK_DGRAM(数据报套接字,就是UDP套接字)。
    • 默认为family=AF_INET,type=SOCK_STREM
    • 返回一个整数描述符,用这个描述符来标识这个套接字
  • 绑定套接字:bind(address)        #因为服务器需要占用一个端口并等待客户端的请求,所以它们必须绑定到一个本地地址。
    • 由AF_INET所创建的套接字,address地址必须是一个双元素元组,格式是(host,port)。host代表主机,port代表端口号。
    • 如果端口号正在使用、主机名不正确或端口是已被保留的,bind方法将引发socket.error异常。
  • 监听套接字:listen(backlog)          #TCP服务器必须监听(传入)的连接
    • backlog指定最多允许多少个客户连接到服务器。它的值至少为1。收到连接请求后,这些请求需要排队,如果队列满,就拒绝请求。
  • 等待接受连接:connection, address = ss.accept()
    • 调用accept()函数之后,就开启了一个简单的(单线程)服务器,它会等待客户端的连接。默认情况下,accept()是阻塞的,这意味着执行将被暂停,直到一个连接到达。
    • 一旦服务器接受了一个连接,就会返回(利用accept())一个独立的客户端套接字,用来与即将到来的消息进行交换。
    • accept方法返回一个含有两个元素的元组(connection, address)。
      • 第一个元素connection是所连接的客户端的socket对象(实际上是该对象的内存地址),服务器必须通过它与客户端通信;
      • 第二个元素address是客户端的Internet地址。
  • 处理阶段:
    • connection.recv(bufsize[, flag])
      • 接收套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略
    • connection.send(string[, flag])
      • 将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。
  • 传输结束,关闭连接:ss.close()
    • 关闭套接字
  • 每当一个传入的请求到达时,服务器会创建一个新的通信端口来直接与客户端进行通信,再次空出主要的端口,以使其能够接受新的客户端连接。
  • 当一方关闭连接或者向对方发送一个空字符串时,通常就会关闭连接。

示例1:

###python3.8
#!/usr/bin/env python3
from socket import *
from time import ctime, sleep

HOST = ''                                      #HOST变量是空白的,这是对bind()方法的标识,表示它可以使用任何可用的地址。
PORT = 21567
BUFSIZ = 1024                                  #缓冲区大小设置为1KB
ADDR = (HOST, PORT)

tcpSerSock = socket(AF_INET, SOCK_STREAM)
#print('******tcpSerSock:', tcpSerSock)        #tcpSerSock: <socket.socket fd=448, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
tcpSerSock.bind(ADDR)
tcpSerSock.listen(5)                           #listen()方法的参数是在连接被转接或拒绝之前,传入连接请求的最大数。

while True:
    print('waiting for connection...')
    tcpCliSock, addr = tcpSerSock.accept()
    #print('******tcpCliSock:', tcpCliSock)    #tcpCliSock: <socket.socket fd=496, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 21567), raddr=('127.0.0.1', 51383)>
    #print('******addr:', addr)                #addr: ('127.0.0.1', 51383)
    while True:
        data = tcpCliSock.recv(BUFSIZ)
        if not data:
            break
        #sleep(5)
        tcpCliSock.send(bytes('[%s] %s' % (ctime(), str(data, 'utf-8')), 'utf8'))
    tcpCliSock.close()
tcpSerSock.close()                             #最后一行永远不会执行,它只是用来提醒读者,有这种优雅的退出方式

示例2:

###python2.7
#!/usr/bin/env python
from socket import *
from time import ctime

HOST = ''
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

tcpSerSock = socket(AF_INET, SOCK_STREAM)
tcpSerSock.bind(ADDR)
tcpSerSock.listen(5)

while True:
    print 'waiting for connection...'
    tcpCliSock, addr = tcpSerSock.accept()
    print '...connected from:', addr
    while True:
        data = tcpCliSock.recv(BUFSIZ)
        if not data:
            break
        tcpCliSock.send('[%s] %s' % (ctime(), data))
    tcpCliSock.close()
tcpSerSock.close()

2、创建TCP客户端

  • 创建客户的伪代码:
cs = socket()                # 创建客户端套接字
cs.connect()                 # 尝试连接服务器
comm_loop:                   # 通信循环
    cs.send() / cs.recv()    # 对话(发送/接收)
cs.close()                   # 关闭客户端套接字
  • 创建socket对象:cs = socket()
  • 连接至服务器端:cs.connect(address)
    • 连接到服务器address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
  • 处理阶段
    • cs.recv(bufsize[,flag])
      • 接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。
    • cs.send(string[,flag])
      • 将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。
  • 连接结束,关闭套接字
    • cs.close()

示例1:

###python3.8
#!/usr/bin/env python
from socket import *

HOST = '127.0.0.1'                        #HOST和PORT变量指服务器的主机名与端口号
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

tcpClisock = socket(AF_INET, SOCK_STREAM)
#print('******tcpClisock', tcpClisock)    #tcpClisock <socket.socket fd=452, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
tcpClisock.connect(ADDR)

while True:                               #两种条件下将会跳出:用户没有输入,或者服务器终止且对recv()方法的调用失败。
    data = input('> ')
    if not data:
        break
    tcpClisock.send(bytes(data, 'utf-8'))
    data = tcpClisock.recv(BUFSIZ)
    if not data:
        break
    print(str(data, 'utf-8'))
tcpClisock.close()

示例2:

###python2.7
#!/usr/bin/env python
from socket import *

HOST = '192.168.248.128'
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

tcpClisock = socket(AF_INET, SOCK_STREAM)
tcpClisock.connect(ADDR)

while True:
    data = raw_input('> ')
    if not data:
        break
    tcpClisock.send(data)
    data = tcpClisock.recv(BUFSIZ)
    if not data:
        break
    print data
tcpClisock.close()

3、创建UDP服务器和客户端

1、创建UDP服务器

  • 创建UDP服务器伪代码
ss=socket()                         #创建服务器套接字
ss.bind()                           #绑定服务器套接字
inf_loop:                           #服务器无限循环
    cs=ss.recvfrom()/ss.sendto()    #关闭(接收/发送)
ss.close()                          #关闭服务器套接字

示例1:

###python3.8
#!/usr/bin/env python
from socket import *
from time import ctime

HOST = ''
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

udpSerSock = socket(AF_INET, SOCK_DGRAM)
udpSerSock.bind(ADDR)

while True:
    print('waiting for message...')
    data, addr = udpSerSock.recvfrom(BUFSIZ)
    udpSerSock.sendto(bytes('[%s] %s' % (ctime(), str(data, 'utf8')), 'utf8'), addr)
    print('...received from and returned to :', addr)
udpSerSock.close()

示例2:

###python2.7
#!/usr/bin/env python
from socket import *
from time import ctime

HOST = ''
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

udpSerSock = socket(AF_INET, SOCK_DGRAM)
udpSerSock.bind(ADDR)

while True:
    print 'waiting for message...'
    data, addr = udpSerSock.recvfrom(BUFSIZ)
    udpSerSock.sendto('[%s] %s' % (ctime(), data), addr)
    print '...received from and returned to :', addr
udpSerSock.close()

2、创建UDP客户端

  • 创建UDP客户端伪代码
cs = socket()                    #创建客户端套接字
comm_loop:                       #通信循环
    cs.sendto()/cs.recvfrom()    #对话(发送/接收)
cs.close()                       #关闭客户端套接字  

示例1:

###python3.8
#!/usr/bin/env python
from socket import *
from time import ctime

HOST = 'localhost'
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

udpCliSock = socket(AF_INET, SOCK_DGRAM)

while True:
    data = input('> ')
    if not data:
        break
    udpCliSock.sendto(bytes(data, 'utf8'), ADDR)
    data, ADDR = udpCliSock.recvfrom(BUFSIZ)
    if not data:
        break
    print(str(data, 'utf8'))
udpCliSock.close()

示例2:

###python2.7
#!/usr/bin/env python
from socket import *
from time import ctime

HOST = 'localhost'
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

udpCliSock = socket(AF_INET, SOCK_DGRAM)

while True:
    data = raw_input('> ')
    if not data:
        break
    udpCliSock.sendto(data, ADDR)
    data, ADDR = udpCliSock.recvfrom(BUFSIZ)
    if not data:
        break
    print data
udpCliSock.close()

4、socket模块内容

  • 除了socket.socket()函数之外,socket模块还提供了更多用于网络应用开发的属性。

5、超时与非阻塞

  • 一个套接字对象可以处于以下三种模式之一:阻塞、非阻塞或超时。套接字默认以阻塞模式创建,但是可以调用setdefaulttimeout()来更改
    • 在blocking mode(阻塞模式)中,操作将阻塞,直到操作完成或系统返回错误(如连接超时)。
    • 在non-blocking mode(非阻塞模式)中,如果操作无法立即完成,则操作将失败(不幸的是,不同系统返回的错误不同):位于select中的函数可用于了解套接字何时以及是否可以读取或写入。
    • 在timeout mode(超时模式)下,如果无法在指定的超时内完成操作(抛出timeout异常),或如果系统返回错误,则操作将失败。
  • 注解:在操作系统层面上,超时模式下的套接字在内部都设置为非阻塞模式。同时,阻塞和超时模式在文件描述符和套接字对象之间共享,这些描述符和对象均应指向同一个网络端点。

1、超时与connect方法

  • connect()操作也受超时设置的约束,通常建议在调用connect()之前调用settimeout(),或将超时参数直接传递给create_connection()。但是,无论Python套接字超时设置如何,系统网络栈都有可能返回自带的连接超时错误。

2、超时与accept方法

  • 如果getdefaulttimeout()的值不是None,则accept()方法返回的套接字将继承该超时值。若是None,返回的套接字行为取决于侦听套接字的设置:
    • 如果侦听套接字处于阻塞模式或超时模式,则accept()返回的套接字处于阻塞模式;
    • 如果侦听套接字处于非阻塞模式,那么accept()返回的套接字是阻塞还是非阻塞取决于操作系统。如果要确保跨平台时的正确行为,建议手动覆盖此设置。

3、非阻塞

  • accept()和recv()默认是阻塞的。
  • 当ss.setblocking(False)将服务连接设置为非阻塞时,accept()和recv()也都是非阻塞的了。

示例:非阻塞服务端

#!/usr/bin/env python3
from socket import *
from time import ctime, sleep

ss = socket(AF_INET, SOCK_STREAM)
ss.bind(('127.0.0.1', 10000))
ss.listen(5)

while True:
    print('waiting for connection...')
    try:
        ss.setblocking(False)       #设置非阻塞,将accept、revc都设置为非阻塞啦
        cs, addr = ss.accept()
        print(cs, addr)
    except Exception as e:          #异常时,执行的代码
        print(e)
        sleep(3)
    else:                           #无异常时,执行的代码
        while True:
            cs.setblocking(True)    #有客户端连接时,将该连接修改为阻塞的(即将recv改为阻塞的)
            data = cs.recv(1024)
            if not data:
                break
            cs.send(bytes('[%s] %s' % (ctime(), str(data, 'utf-8')), 'utf8'))
        cs.close()
tcpSerSock.close()  # 最后一行永远不会执行,它只是用来提醒读者,有这种优雅的退出方式

示例:客户端

#!/usr/bin/env python
from socket import *

cs = socket(AF_INET, SOCK_STREAM)
cs.connect(('127.0.0.1',10000))

while True:
    data = input('> ')
    if not data:
        break
    cs.send(bytes(data, 'utf-8'))
    data = cs.recv(1024)
    if not data:
        break
    print(str(data, 'utf-8'))
cs.close()

4、注意

  • socket.socket类
    • 设置套接字超时时间:s.settimeout()
    • 设置套接字是否阻塞:s.setblocking()
  • socket模块
    • 设置套接字默认超时时间:setdefaulttimeout()

 

posted @ 2021-06-21 17:06  麦恒  阅读(180)  评论(0编辑  收藏  举报