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