python核心编程第3版第2章 网络编程【读书笔记】

客户端/服务器架构

服务器是一系列硬件或软件,为一个或多个客户端(服务的用户)提供所需的“服务”。它存在的唯一目的就是等待客户端的请求,并响应它们(提供服务),然后等待更多请求。
客户端因特定的请求而联系服务器,并发送必要的数据,然后等待服务器的回应,最后完成请求或给出故障的原因。
客户端/服务器架构既可以应用于计算机硬件,也可以应用于软件。

硬件客户端/服务器架构

打印服务器、文件服务器

软件客户端/服务器架构

web服务器
数据库服务器
窗体服务器

客户端/服务器网络编程

在服务器响应客户端请求之前,首先会创建一个通信端点,它能够使服务器监听请求。一旦一个通信端点已经建立,监听服务器就可以进入无限循环中,等待客户端的连接并响应它们的请求。
客户端所需要做的只是创建它的单一通信端点,然后建立一个到服务器的连接。然后,客户端就可以发出请求,该请求包括任何必要的数据交换,一旦请求被服务器处理,且客户端收到结果或某种确认信息,此次通信就会被终止。

套接字:通信端点

套接字

在任何类型的通信开始之前,网络应用程序必须创建套接字,没有它将无法进行通信。
有两种类型的套接字:基于文件的和面向网络的。

基于文件的:
AF_UNIX,它代表地址家族:UNIX。
基于网络的:
AF_INET,它代表地址家族:因特网,使用最广泛。
AF_INET6,用于第6版因特网协议(IPv6)寻址。
AF_NETLINK,无连接的套接字。
AF_TIPC,支持TIPC,TIPC(进程间通信协议)允许计算机及群之中的及其相互通信。

python只支持AF_UNIX、AF_NETLINK、AF_TIPC、AF_INET家族。

套接字地址:主机-端口对

一个网络地址由主机名和端口号对组成,有效的端口号范围 0-65535。

面向连接的套接字 TCP

在进行通信之前必须先建立一个连接,这种类型的通信也成为虚拟电路或者流套接字。
实现这种连接类型的主要协议是传输控制协议(TCP)
为了创建TCP套接字,必须使用SOCK_STREAM作为套接字类型。

无连接的套接字 UDP

数据报类型的套接字,它是一种无连接的套接字,在通信开始之前并不需要建立连接。
实现这种连接类型的主要协议是用户数据报协议(UDP)
为了创建UDP套接字,必须使用SOCK_DGRAM作为套接字类型。

python中的网络编程 socket模块

socket()函数,用于创建套接字对象。

socket()模块函数

创建套接字:

socket(socket_family, socket_type, protocol=0) 

socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。 protocol 通常省略,默认值为 0。

创建 TCP/IP 的套接字:

tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 

创建一UDP/IP 的套接字

udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 

*使用from socket import

tcpSock = socket(AF_INET, SOCK_STREAM) 
udpSock = socket(AF_INET, SOCK_DGRAM)

套接字对象内置方法

名称 描述
服务器端套接字函数
s.bind() 绑定地址(主机,端口号对)到套接字
s.listen() 设置并启动 TCP 监听器
s.accept() 被动接受 TCP 客户端的连接,一直等待直到连接到达(阻塞)
客户端套接字函数
s.connect() 主动初始化 TCP 服务器连接
s.connect_ex() connect()函数的扩展版本,此时会以错误码的形式返回问题,而不是抛出一个异常
普通的套接字函数
s.recv() 接收 TCP 消息
s.recv_into() 接收 TCP 消息到指定的缓冲区
s.send() 发送 TCP 消息
s.sendall() 完整地发送 TCP 消息
s.recvfrom() 接收 UDP 消息
s.recvfrom_into() 接收 UDP 消息到指定的缓冲区
s.sendto() 发送 UDP 消息
s.getpeername() 连接到套接字(TCP)的远程地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回给定套接字选项的值
s.setsockopt() 设置给定套接字选项的值
s.shutdown() 关闭连接
s.close() 关闭套接字
s.detach() 在未关闭文件描述符的情况下关闭套接字,返回文件描述符
s.ioctl() 控制套接字的模式
面向阻塞的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字关联的文件对象
数据属性
s.family() 套接字家族
s.type 套接字类型
s.proto 套接字协议

创建TCP服务器

伪代码:
ss = socket()                 # 创建服务器套接字
ss.bind()                     # 套接字与地址绑定
ss.listen()                   # 监听连接
inf_loop:                     # 服务器无限循环
    cs = ss.accept()          # 接受客户端连接
    comm_loop:                # 通信循环
        cs.recv()/cs.send()   # 对话(接收/发送)
    cs.close()                # 关闭客户端套接字
ss.close()                    # 关闭服务器套接字(可选)

TCP时间戳服务器:这个脚本创建一个TCP服务器,它接收来自客户端的消息,然后将消息加上时间戳前缀并发送回客户端。

from socket import *
from time import ctime
HOST = ''  # 变量是空白的,表示它可以使用任何可用的地址
PORT = 21567  # 选择一个随机的端口号
BUFSIZ = 1024  # 对于该应用程序,将缓冲区大小设置为1KB
ADDR = (HOST, PORT)   # 套接字地址,主机-端口对

tcpSerSock = socket(AF_INET, SOCK_STREAM)  # 分配了TCP服务器套接字
tcpSerSock.bind(ADDR)  # 将套接字绑定到服务器地址,下一行开启TCP监听器的调用
tcpSerSock.listen(5)  # listen()方法的参数实在连接被转接或拒绝之前,接入连接请求的最大数

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()  # 永远不会执行

创建TCP客户端

伪代码:
cs = socket()                 # 创建客户端套接字
cs.connect()                  # 尝试连接服务器
ss.listen()                   # 通信循环
comm_loop:                    # 服务器无限循环
    cs.recv()/cs.send()       # 对话(接收/发送)
cs.close()                    # 关闭客户端套接字

TCP时间戳客户端:这个脚本创建一个TCP客户端,它提示用户输入发送到服务器端的消息,并接受从服务器端返回的添加了时间戳前缀的相同消息,然后将结果展示给用户。

from socket import *

HOST = 'localhost'  # 指定服务器的主机名
PORT = 21567   # 指定服务器的端口号,端口号PORT应该与你为服务器设置的完全相同
BUFSIZ = 1024  # 对于该应用程序,将缓冲区大小设置为1KB
ADDR = (HOST, PORT)  # 套接字地址,主机-端口对

tcpCliSock = socket(AF_INET, SOCK_STREAM)  # 分配了TCP客户端套接字
tcpCliSock.connect(ADDR)  # 主动调用并连接到服务器

while True:  # 并不会永远进行下去
    data = raw_input('> ')  # 第一种条件:用户没有输入
    if not data:
        break
    tcpCliSock.send(data)
    data = tcpCliSock.recv(BUFSIZ)  # 服务器终止且对recv()方法的调用失败
    if not data:
        break
    print data
tcpCliSock.close()

创建UDP服务器

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

UDP时间戳服务器:这个脚本创建一个UDP服务器,它接受客户端发来的消息,并将加了时间戳前缀的该消息返回给客户端。

from socket import *
from time import ctime

HOST = ''  # 变量是空白的,表示它可以使用任何可用的地址
PORT = 21567  # 选择一个随机的端口号
BUFSIZ = 1024  # 对于该应用程序,将缓冲区大小设置为1KB
ADDR = (HOST, PORT)  # 套接字地址,主机-端口对

udpSerSock = socket(AF_INET, SOCK_DGRAM)  # 分配了UDP服务器套接字
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()  # 永远不会执行

创建UDP客户端

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

UDP时间戳客户端:这个脚本创建一个UDP客户端,它提示用户输入发送给服务器的消息,并接收服务器加了时间戳前缀的消息,然后将它们显示给用户。

from socket import *

HOST = 'localhost'  # 指定服务器的主机名
PORT = 21567   # 指定服务器的端口号,端口号PORT应该与你为服务器设置的完全相同
BUFSIZ = 1024  # 对于该应用程序,将缓冲区大小设置为1KB
ADDR = (HOST, PORT)  # 套接字地址,主机-端口对

udpCliSock = socket(AF_INET, SOCK_DGRAM)  # 分配了UDP客户端套接字

while True:  # 并不会永远进行下去
    data = raw_input('> ')  # 第一种条件:用户没有输入
    if not data:
        break
    udpCliSock.sendto(data, ADDR)  # 简单的发送一条消息并等待副武器的回复
    data, ADDR = udpCliSock.recvfrom(BUFSIZ)  # 服务器终止且对recvfrom()方法的调用失败
    if not data:
        break
    print data
udpCliSock.close()

socket模块属性

属性 描述
数据属性
AF_UNIX, AF_INET, AF_INET6、AF_NETLINK、AF_TIPC Python 支持的套接字地址家族
SO_STREAM, SO_DGRAM 套接字类型 (TCP = 流, UDP = 数据报)
has_ipv6 表示是否支持 IPv6 的布尔标记
异常
error 套接字相关错误
herror 主机和地址相关的错误
gaierror 地址相关的错误
timeout 超时
函数
socket() 用指定的地址家族,套接字类型和协议类型(可选)创建一个套接字对象
socketpair() 用指定的地址家族,套接字类型和协议类型(可选)创建一对套接字对象
create_connection() 常规函数,它接收一个地址(主机名,端口号)对,返回套接字对象
fromfd() 用一个已经打开的文件描述符创建一个套接字对象
ssl() 通过套接字启动一个安全套接字层(SSL)连接;不做证书验证。
getaddrinfo() 获取一个五元组序列形式的地址消息
getnameinfo() 给定一个套接字地址,返回(主机名,端口号)二元组
getfqdn() 返回完整的域名
gethostname() 返回当前主机名
gethostbyname() 将一个主机名映射到它的IP地址
gethostbyname_ex() gethostbyname()的扩展版本,返回主机名,主机所有的别名和 IP 地址列表。
gethostbyaddr() 由 IP 地址得到 DNS 信息,返回一个类似 gethostbyname_ex() 的 3 元组。
getprotobyname() 将一个协议名(如‘tcp’)映射到一个数字
getservbyname()/getservbyport() 将一个服务名映射到一个端口号,或者反过来;对于任何一个函数来说,协议名都是可选的
ntohl()/ntohs() 将来自网络的整数转换为主机字节顺序
htonl()/htons() 将来自主机的整数转换为网络字节顺序
inet_aton()/inet_ntoa() 将IP地址八进制字符串转换为32位的包格式,或者反过来(仅用于IPv4地址)
inet_pton()/net_ntop() 将IP地址字符串转换成打包的二进制格式,或者反过来(同时适用于IPv4和IPv6地址)
getdefaulttimeout()/setdefaulttimeout() 以秒(浮点数)为单位返回默认套接字超时时间;以秒(浮点数)为单位设置默认套接字超时时间

SocketServer模块

SocketServer模块

描述
BaseServer 包含服务器的核心功能与混合(mix-in)类的钩子功能;仅用于推到,这样不会创建这个类的实例;可以用TCPServer或UDPServer创建类的实例
TCPServer/ UDPServer 基础的网络同步 TCP/UDP 服务器
UnixStreamServer/ UnixDatagramServer 基于文件的基础同步 TCP/UDP 服务器
ForkingMixIn/ThreadingMixIn 核心派出或线程功能;只用作mix-in类与一个服务器类配合实现一些异步性;不能直接实例化这个类
ForkingTCPServer/ ForkingUDPServer ForkingMixIn 和 TCPServer/UDPServer 的组合
ThreadingTCPServer/ ThreadingUDPServer ThreadingMixIn 和 TCPServer/UDPServer 的组合
BaseRequestHandler 包含处理服务请求的核心功能;仅仅用于推导,这样无法创建这个类的实例;可以使用 StreamRequestHandler 或 DatagramRequestHandler创建类的实例
StreamRequestHandler/ DatagramRequestHandler 实现TCP/UDP 服务器的服务处理器

创建SocketServerTCP 服务器

SocketServer 时间戳服务器:通过使用 SocketServer 类、TCPServer 和 StreamRequestHandler ,创建一个时间戳 TCP 服务器。

from SocketServer import (TCPServer as TCP, StreamRequestHandler as SRH)
from time import ctime

HOST = ''  # 变量是空白的,表示它可以使用任何可用的地址
PORT = 21567  # 选择一个随机的端口号
ADDR = (HOST, PORT)  # 套接字地址,主机-端口对


class MyRequestHandler(SRH):  # 得到了请求处理程序MyRequestHandler,作为SocketServer中StreamRequestHandler的一个子类

    def handle(self):   # 重写handle方法,当街收到一个来自客户端的消息时,就调用它
        print '...connected from:', self.client_address
        self.wfile.write('[%s] %s' % (ctime(), self.rfile.readline()))  # 使用readline()来获取客户端消息,并利用write()将字符串发送回客户端

tcpServ = TCP(ADDR, MyRequestHandler)  # 用给定的主机信息和请求处理类创建了TCP服务器
print 'waiting for connection...'
tcpServ.serve_forever()   # 无限循环的等待并服务于客户端请求

创建SocketServerTCP 客户端

SocketServer 时间戳客户端:它知道如何与类似文件的SocketServer类StreamRequest Handler 对象通信。

from socket import *

HOST = 'localhost'  # 指定服务器的主机名
PORT = 21567   # 指定服务器的端口号,端口号PORT应该与你为服务器设置的完全相同
BUFSIZ = 1024  # 对于该应用程序,将缓冲区大小设置为1KB
ADDR = (HOST, PORT)  # 套接字地址,主机-端口对

while True:  # 并不会永远进行下去
    tcpCliSock = socket(AF_INET, SOCK_STREAM)  # 分配了TCP客户端套接字
    tcpCliSock.connect(ADDR)  # 主动调用并连接到服务器
    data = raw_input('> ')  # 第一种条件:用户没有输入
    if not data:
        break
    tcpCliSock.send('%s\r\n' % data)  # 因为这里使用的处理程序类对待套接字通信就像文件一样,所以必须发送行终止符
    data = tcpCliSock.recv(BUFSIZ)  # 服务器终止且对recv()方法的调用失败
    if not data:
        break
    print data.strip()   # 当得到从服务器返回的消息时,用strip()函数对其进行处理并使用print声明自动提供的换行符
    tcpCliSock.close()  # SocketServer请求处理程序的默认行为是接受连接、获取请求,然后关闭连接

Twisted 框架介绍

Twisted 是一个完整的事件驱动的网络框架。利用它既能使用也能开发完整的异步网络应用程序和协议。

创建 Twisted Reactor TCP 服务器

Twisted Reactor 时间戳 TCP 服务器:这是一个使用 Twisted Internet 类的时间戳 TCP 服务器。

from twisted.internet import protocol, reactor
from time import ctime

PORT = 21567  # 设置常用端口号


class TSServProtocol(protocol.Protocol):  # 获得protocol类并为时间戳服务器调用TSServProtocol

    def connectionMade(self):  # 重写 connectionMade,当一个客户端连接到服务器时就会执行
        clnt = self.clnt = self.transport.getPeer().host  # 获取主机信息
        print '...connected from:', clnt

    def dataReceived(self, data):  # 重写 dataReceived,当服务器接收到客户端通过网络发送的一些数据时就会调用
        self.transport.write('[%s] %s' % (ctime(), data))

factory = protocol.Factory()  # 创建一个协议工厂,因为每次得到一个接入连接时,都能制造协议的一个实例
factory.protocol = TSServProtocol
print 'waiting for connection...'  # reactor中安装一个TCP监听器,以此检查服务请求,当它接收到一个请求时,就会创建一个TSServProtocol实例来处理那个客户端的事务
reactor.listenTCP(PORT, factory)  # reactor作为该方法的一个参数在数据中传输,能在无须自己提取它的情况下访问它
reactor.run()

创建 Twisted Reactor TCP 客户端

Twisted Reactor Timestamp TCP 客户端:用 Twisted 重写我们已经熟悉的时间戳 TCP 客户端。

from twisted.internet import protocol, reactor
from time import ctime

PORT = 21567  # 设置常用端口号


class TSServProtocol(protocol.Protocol):  # 获得protocol类并为时间戳服务器调用TSServProtocol

    def connectionMade(self):  # 重写 connectionMade,当一个客户端连接到服务器时就会执行
        clnt = self.clnt = self.transport.getPeer().host  # 获取主机信息
        print '...connected from:', clnt

    def dataReceived(self, data):  # 重写 dataReceived,当服务器接收到客户端通过网络发送的一些数据时就会调用
        self.transport.write('[%s] %s' % (ctime(), data))

factory = protocol.Factory()  # 创建一个协议工厂,因为每次得到一个接入连接时,都能制造协议的一个实例
factory.protocol = TSServProtocol
print 'waiting for connection...'  # reactor中安装一个TCP监听器,以此检查服务请求,当它接收到一个请求时,就会创建一个TSServProtocol实例来处理那个客户端的事务
reactor.listenTCP(PORT, factory)  # reactor作为该方法的一个参数在数据中传输,能在无须自己提取它的情况下访问它
reactor.run()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 最近在看《UNIX网络编程 卷1》和《FREEBSD操作系统设计与实现》这两本书,我重点关注了TCP协议相关的内容...
    腩啵兔子阅读 1,149评论 0 7
  • 定义 网络协议为计算机网络中进行数据交换而建立的规则、标准或约定的集合。网络协议主要由三个要素组成:语义、语法及时...
    FlyAndroid阅读 979评论 0 10
  • 前言 本章主要介绍如下知识,通过了解这些知识,进一步理解node为何适合在分布式网络中扮演各种角色。另外,由于no...
    白昔月阅读 1,902评论 0 3
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • “假如你老了十岁,我当然也同样老了十岁,世界也老了十岁,上帝也老了十岁,一切都是一样。” 01. 今天在拥挤的公交...
    叮当的当阅读 409评论 2 3