python学习笔记之--网络编程

基本知识

  • 计算机网络知识,主要了解一下OSI的七层架构,以及TCP/IP的四层架构。
  • 网络通信中最基本的概念:套接字,也叫通信端点、socket
  • 网络通信地址表示:一般是主机+端口号
  • 最常用的两种连接方式:面向连接方式(TCP,SOCK_STREAM)和面向无连接方式(UDP, SOCK_DGRAM)

这些基本的知识,网上有很多相关介绍,不是本章重点,有需要可以在网上查找相关资料。
这里学习一下python语言对底层网络编程的支持,这里只学习最常用的socket编程相关知识,详细说明可以参考官方文档:底层网络接口

socket模块

python有一个专门的socket模块来支持底层网络通信。是对Unix系统调用和套接字库接口的直译。

socket编程基本套路

  1. TCP服务端
    s = socket()  # 创建一个服务端socket
    s.bind()      # 绑定socket地址(ip+port)
    s.listen()    # 监听此地址是否有连接
    loop:
        cs = s.accept() # 阻塞等待客户端连接,连接成功返回新的套接字对象,用于在此连接上收发数据
        comm_loop:
            cs.recv()/cs.send() # 通信:接收数据/发送数据
        cs.close() # 关闭通信套接字对象
    s.close() # 关闭服务端套接字对象
    
  2. TCP客户端
    cs = socket()  # 创建一个客户端socket
    cs.connect()      # 通过服务端地址(ip + port)尝试连接服务端
    comm_loop:
        cs.send()/cs.recv() # 通信:发送数据/接收数据
    cs.close() # 关闭客户端套接字对象
    
  3. UDP服务端
    ss = socket()  # 创建一个服务端socket
    ss.bind()      # 绑定服务端地址
    comm_loop:
        cs.recvfrom()/cs.sendto() # 通信:接收数据/发送数据
    cs.close() # 关闭客户端套接字对象
    

说明: UDP无连接,不需要监听。先被动接收数据,会返回客户端的地址,后面就可以根据客户端地址主动发送消息。

  1. UDP客户端
    cs = socket()  # 创建一个客户端socket
    comm_loop:
        cs.sendto()/cs.recvfrom() # 通信:发送数据/接收数据
    cs.close() # 关闭客户端套接字对象
    

socket通信实例

先从一个TCP实例开始学习,功能比较简单:

  1. 一个客户端,一个服务器端,两个python程序可以放在两台电脑上运行
  2. 客户端给服务端发送一个消息;服务器端接收到消息之后,加上一个时间戳再返回给客户端
  3. 无消息则结束
  • 服务端代码

    from socket import *
    from time import ctime
    
    HOST = ''
    PORT = 21567
    BUFF = 1024
    
    ADDR = (HOST, PORT)
    
    tcpSvrSocket = socket(AF_INET, SOCK_STREAM)
    tcpSvrSocket.bind(ADDR)
    tcpSvrSocket.listen(5)
    
    while True:
        print("Waiting for connect...")
        tcpCliSocket, addr = tcpSvrSocket.accept()
        print(f"...connected from {addr}")
    
        while True:
            data = tcpCliSocket.recv(BUFF)
            if not data:
                break;
    
            data = data.decode('utf-8')
    
            print(f'recive data from client: {data}')
    
            resMsg = f'[{ctime()}] {data}'
            tcpCliSocket.send(bytes(resMsg, 'utf-8'))
    
        tcpCliSocket.close()
    
    tcpSvrSocket.close()
    
  • 客户端代码

    from socket import *
    
    HOST = ''
    PORT = 21567
    BUFF = 1024
    
    ADDR = (HOST, PORT)
    
    tcpCliSocket = socket(AF_INET, SOCK_STREAM)
    tcpCliSocket.connect(ADDR)
    
    while True:
        data = input(">>> ")
        if not data:
            break
    
        tcpCliSocket.send(bytes(data, 'utf-8'))
        data = tcpCliSocket.recv(BUFF)
        if not data:
            break
    
        print(data.decode('utf-8'))
    
    tcpCliSocket.close()
    

说明:socket类实现了__enter____exit__,可以使用with语句自动实现close操作,也即使用with语句可以不用主动调用socket.close()接口

模块说明

  • 常量
    socket模块定义了很多常量,最常使用的如下表
常量名 说明
socket.AF_INET 一种用于ipv6的地址族,一个四元组(host, port, flowinfo, scope_id)表示地址
socket.AF_INET6 一种socket地址族(用于ipv4,也是目前常用的)使用一个(ip, port)元组表示地址
socket.SOCK_STREAM 面向连接(TCP)的socket类型
socket.SOCK_DGRAM 面向无连接(UDP)的socket类型
  • 接口
    socket模块中有一些函数可以使用,但是最常用的还是socket类生成socket对象,以及类中的接口。类原型如下:
    class socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
    

常用接口信息如下表

接口名 连接方式 说明
socket.bind(address) TCP/UDP 服务端socket绑定到地址address
socket.listen([backlog]) TCP 启动一个服务器用于接受连接。backlog表示系统允许暂未 accept 的连接数,超过后将拒绝新连接。最低为 0(小于 0 会被置为 0),未指定则自动设为合理的默认值。
socket.accept() TCP 接受一个连接。此 socket 必须绑定到一个地址上并且监听连接。返回一个 (conn, address) 对:其中 conn 是一个新的套接字对象,用于在此连接上收发数据,address 是连接另一端的套接字所绑定的地址。
socket.connect(address) TCP/UDP 连接到 address 处的远程套接字。
socket.recv(bufsize[, flags]) TCP 从套接字接收数据。返回值是一个字节对象,表示接收到的数据。bufsize 指定一次接收的最大数据量。可选参数 flags 的含义请参阅 Unix 手册页 recv(2),它默认为零。
socket.send(bytes[, flags]) TCP 发送数据给套接字。本套接字必须已连接到远程套接字。可选参数 flags 的含义与上述 recv() 中的相同。
socket.recvfrom(bufsize[, flags]) UDP 从套接字接收数据。返回值是一对 (bytes, address),其中 bytes 是字节对象,表示接收到的数据,address 是发送端套接字的地址。可选参数 flags 的含义请参阅 Unix 手册页 recv(2),它默认为零。
socket.sendto(bytes, address) UDP 发送数据给套接字。由 address 指定目标套接字。可选参数 flags 的含义与上述 recv() 中的相同。
  • 异常
异常名 说明
socket.error 一个被弃用的 OSError 的别名。
socket.herror OSError的子类,本异常通常表示与地址相关的错误。
socket.gaierror OSError的子类,来自 getaddrinfo()getnameinfo(),表示与地址相关的错误。附带的值是一对 (error, string),代表库调用返回的错误。
socket.timeout OSError的子类,当套接字发生超时,且事先已调用过 settimeout()(或隐式地通过 setdefaulttimeout())启用了超时,则会抛出此异常。

SocketServer模块

socketserver模块是一个用于网络服务器编写的框架,简化了编写网络服务器的任务。例如上面TCP服务器端代码可以使用此模块改写如下:

import socketserver

class MyTCPHandler(socketserver.StreamRequestHandler):

    def handle(self):
        # self.rfile is a file-like object created by the handler;
        # we can now use e.g. readline() instead of raw recv() calls
        self.data = self.rfile.readline().strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # Likewise, self.wfile is a file-like object used to write back
        # to the client
        self.wfile.write(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 21567

    # Create the server, binding to localhost on port 9999
    with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
        # Activate the server; this will keep running until you
        # interrupt the program with Ctrl-C
        server.serve_forever()

模块说明

常用实体服务器类:

  • TCPServer类,该类使用互联网 TCP 协议,它可以提供客户端与服务器之间的连续数据流。函数原型如下:
    class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)
  • UDPServer类,该类使用数据包,即一系列离散的信息分包,它们可能会无序地到达或在传输中丢失。函数原型如下:
    class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)

请求处理句柄对象:

  • BaseRequestHandler类,这是所有请求处理句柄对象的超类。 它定义了下文列出的接口。 一个实体请求处理句柄子类必须定义新的 handle()方法,并可重载任何其他方法。 对于每个请求都会创建一个新的子类的实例。

    接口名 说明
    setup() 会在 handle() 方法之前被调用以执行任何必要的初始化操作。 默认实现不执行任何操作。
    handle() 此函数必须执行为请求提供服务所需的全部操作。 默认实现不执行任何操作。 它有几个可用的实例属性;请求为 self.request;客户端地址为 self.client_address;服务器实例为 self.server,如果它需要访问特定服务器信息的话。
    finish() handle()方法之后调用以执行任何需要的清理操作。 默认实现不执行任何操作。
  • StreamRequestHandler和DatagramRequestHandler类,是BaseRequestHandler的子类,重载了 setup()finish()方法,并提供了 self.rfile(读取以获取请求数据) 和 self.wfile(写入以将数据返回给客户端) 属性。

当然此框架还提供了更加强大的功能,具体可以参考官方文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,928评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,748评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,282评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,065评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,101评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,855评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,521评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,414评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,931评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,053评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,191评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,873评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,529评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,074评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,188评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,491评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,173评论 2 357

推荐阅读更多精彩内容