0x00:楔子

你现在已经学会了写python代码,假如你写了两个python文件a.py和b.py,分别去运行,你就会发现,这两个python的文件分别运行的很好。但是如果这两个程序之间想要传递一个数据,你要怎么做呢?

这个问题以你现在的知识就可以解决了,我们可以创建一个文件,把a.py想要传递的内容写到文件中,然后b.py从这个文件中读取内容就可以了。
1、  网络编程 - 图1

但是当你的a.py和b.py分别在不同电脑上的时候,你要怎么办呢?
类似的机制有计算机网盘,qq等等。我们可以在我们的电脑上和别人聊天,可以在自己的电脑上向网盘中上传、下载内容。这些都是两个程序在通信。

二.软件开发的架构

我们了解的涉及到两个程序之间通讯的应用大致可以分为两种:
第一种是应用类:qq、微信、网盘、优酷这一类是属于需要安装的桌面应用
第二种是web类:比如百度、知乎、博客园等使用浏览器访问就可以直接使用的应用
这些应用的本质其实都是两个程序之间的通讯。而这两个分类又对应了两个软件开发的架构~

1.C/S架构

C/S即:Client与Server ,中文意思:客户端与服务器端架构,这种架构也是从用户层面(也可以是物理层面)来划分的。
这里的客户端一般泛指客户端应用程序EXE,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大。
1、  网络编程 - 图2

2.B/S架构

B/S即:Browser与Server,中文意思:浏览器端与服务器端架构,这种架构是从用户层面来划分的。
Browser浏览器,其实也是一种Client客户端,只是这个客户端不需要大家去安装什么应用程序,只需在浏览器上通过HTTP请求服务器端相关的资源(网页资源),客户端Browser浏览器就能进行增删改查。
1、  网络编程 - 图3

0x01:套接字(通信端点)

在任何类型的通信开始之前,网络应用程序必须创建套接字。

套接字: 最初为同一主机上的应用程序创建的,是主机上运行的一个程序(又名一个进程)与另外一个运行的程序进行通信。

有两种类型的套接字: 基于文件(AF_UNIX/AF_LOCAL)和面向网络(AF_INET)。

基于文件类型的套接字家族

套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

基于网络类型的套接字家族

套接字家族的名字:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

0x02: 套接字地址: 主机——端口对

一个网络地址有主机名和端口号对组成,有效的端口号范围为 0~ 65535(小于1024的端口号都预留给了系统),我们可以在 linux 系统下的 /etc/services 文件中找到预留端口号的列表。

0x03:面向连接的套接字与无连接的套接字

  • 面向连接的套接字: 面向连接的通信提供序列化的、可靠性和不重复的数据交付,而且没有记录边界。 实现这种连接类型的主要协议是传输控制协议(TCP)。 使用 SOCK_STREAM 作为套接字类型。

  • 无连接的套接字:在通信开始之前并不需要建立连接。在数据传输过程中无法保证它的顺序性、可靠性或重复性,保留了记录边界,。这就意味着消息是以整体发送的,并非首先分成多个片段。实现这种连接类型的主要协议是用户数据报协议(UDP)。必须使用SOCK_DGRAM作为套接字的类型。

1、tcp协议和udp协议

TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。

UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。

0x04:socket()模块函数

要创建套接字,必须使用 socket.socket()函数,一般的语法如下:

  1. socket(socket_family, socket_type, protocol=0)
  2. socket_family AF_UNIX AF_INET , socket_typesock_stream 或者 sock_dgramportocol通常省略,默认为0.

套接字对象(内置)方法:

函数 描述
服务器端套接字
s.bind() 绑定地址(host,port)到套接字, 在 AF_INET下,以元组(host,port)的形式表示地址。
s.listen() 开始 TCP 监听。backlog 指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为 1,大部分应用程序设为 5 就可以了。
s.accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来
客户端套接字
s.connect() 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收 TCP 数据,数据以字符串形式返回,bufsize 指定要接收的最大数据量。flag 提供有关消息的其他信息,通常可以忽略。
s.send() 发送 TCP 数据,将 string 中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于 string 的字节大小。
s.sendall() 完整发送 TCP 数据。将 string 中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回 None,失败则抛出异常。
s.recvfrom() 接收 UDP 数据,与 recv() 类似,但返回值是(data,address)。其中 data 是包含接收数据的字符串,address 是发送数据的套接字地址。
s.sendto() 发送 UDP 数据,将数据发送到套接字,address 是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close() 关闭套接字
s.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
s.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value) 设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。
面向阻塞的套接字方法
s.settimeout(timeout) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
s.setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
面向文件的套接字方法
s.makefile() 创建一个与该套接字相关连的文件
s.fileno() 返回套接字的文件描述符。
数据属性
s.family 套接字家族
s.type 套接字类型
s.proto 套接字协议

0x05:创建TCP服务端和客户端

Python3 TCP 时间戳服务端代码示例

  1. from socket import * # 导入套接字模块
  2. from time import ctime # 导入时间戳模块
  3. HOST = '' # 内容为空,表示任何可用的地址
  4. PORT = 21567 # 设置端口号
  5. BUFSIZ = 1024 # 缓冲区大小为1KB
  6. ADDR = (HOST,PORT) # 元组的形式保存ip 地址和端口
  7. tcpSer = socket(AF_INET,SOCK_STREAM) # 创建套接字
  8. tcpSer.bind(ADDR) # 绑定套接字的地址
  9. tcpSer.listen(5) # 设置TCP服务端的监听器
  10. while True: # 循环
  11. print('wating for connection....')
  12. tcpCli,addr = tcpSer.accept() # 接受TCP客户端的连接
  13. print('.... connected from: ',addr)
  14. while True: # 再创建一个子循环
  15. data = tcpCli.recv(BUFSIZ).decode() #服务器接受到客户端发送来的数据是字节类型,需要转换为字符串类型
  16. if not data: # 判断消息是否为空,为空就停止
  17. break
  18. tcpCli.send(('[%s] %s' % (ctime(),data)).encode()) # # 网络传输数据前将字符串数据转化为字节类型
  19. tcpCli.close() # 断开客户端的连接
  20. tcpSer.close()

Python3 TCP 时间戳客户端代码示例

  1. from socket import * # 导入套接字模块
  2. HOST = '127.0.0.1' # 服务器的主机名
  3. PORT = 21567 # 端口号
  4. BUFFER_SIZE = 1024 # 缓冲区大小为 1KB
  5. ADDR = (HOST,PORT) # 元组的形式存储 ip 地址和端口
  6. tcpCli = socket(AF_INET,SOCK_STREAM) # 创建套接字
  7. tcpCli.connect(ADDR) # 与服务端进行连接
  8. while True: # 创建循环
  9. data = input('> ') # 用户输入数据
  10. if not data: # 对用户的输入数据进行判断,如果为空,跳出循环
  11. break
  12. tcpCli.send(data.encode()) # 用户发送字符串数据
  13. data = tcpCli.recv(BUFFER_SIZE).decode() # 客户端收到加了时间戳的字符串
  14. if not data:
  15. break
  16. print(data)
  17. tcpCli.close()

image.png

0x06:创建UDP服务端和客户端

Python3 UDP 时间戳服务端代码示例

  1. from socket import * # 导入socket模块
  2. from time import ctime # 导入时间戳模块
  3. HOST = '' # 设置主机
  4. PORT = 21567 # 设置端口
  5. BUFSIZ = 1024 # 消息的缓存大小
  6. ADDR = (HOST,PORT) # 将主机和端口保存为元组
  7. udpSer = socket(AF_INET,SOCK_DGRAM) # 建立UDP的套接字
  8. udpSer.bind(ADDR) # 绑定套接字的地址和端口
  9. while True: # 创建一个循环
  10. print(' wating for message ....')
  11. data,addr = udpSer.recvfrom(BUFSIZ) #接受客户端发送来的数据
  12. print(data.decode())
  13. udpSer.sendto(ctime().encode(),addr ) #给客户端发送消息
  14. print(' ..... received from and returned to:',addr)
  15. udpSer.close()

Python3 UDP 时间戳客户端代码示例

  1. from socket import * # 导入socket模块
  2. HOST = '127.0.0.1' # 设置服务器主机
  3. PORT = 21567 # 设置端口
  4. BUFSIZ = 1024 # 消息的缓存大小
  5. ADDR = (HOST,PORT) # 将主机和端口保存为元组
  6. udpCli = socket(AF_INET,SOCK_DGRAM) # 建立UDP的套接字
  7. while True:
  8. data = input('> ') # 判断输入的数据是否为空
  9. if not data:
  10. break
  11. udpCli.sendto((data.encode()),ADDR) # 客户端向服务端发送消息
  12. data,ADDR = udpCli.recvfrom(BUFSIZ) # 接受服务端发来的消息
  13. data = data.decode()
  14. if not data:
  15. break
  16. print(data)
  17. udpCli.close()

image.png

0x07: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 包含核心服务器功能和 mix-in类的钩子,仅用于推导,这样不会创建这个类的示例,可以用StreamRequestHandler 或DatagramRequestHandler创建类的实例
StreamRequestHandler / DatagramRequestHandler 实现TCP/UDP服务器的服务处理器

事件包括消息的发送和接收,事实上,你会看到类定义只包括一个用来接收客户端消息的事件处理程序。所有的其他功能都来自使用的 SocketServer 类。

在原始服务器循环中,我们阻塞等待请求,当收到请求时就对其提供服务,然后继续等待。再此处的服务器循环中,并非在服务器中创建代码,而是定义一个处理程序,这样当服务器接收到一个传入的请求时,服务器就可以调用你的函数。

创建SocketServer TCP 服务端

  1. from socketserver import TCPServer as TCP
  2. from socketserver import StreamRequestHandler as SRH
  3. from time import ctime
  4. HOST = ''
  5. PORT = 21567
  6. ADDR = (HOST,PORT)
  7. class MyRequestHandler(SRH): # 重写自己的RequestHandlerClass类
  8. def handle(self):
  9. # 打印客户端的地址信息,该信息被储存在self.client_address中
  10. print('...connected from:',self.client_address)
  11. # self.rfile.readline()读取客户端发送的信息(bytes类型),并解码为字符串类型
  12. # 写入字符串类型信息,编码为bytes(此处没有send()方法)
  13. self.wfile.write(('[%s] %s ' % (ctime(),self.rfile.readline().decode())).encode())
  14. # 构造socketserver.TCPServer类,传入地址和handler方法参数
  15. tcpServ = TCP(ADDR,MyRequestHandler)
  16. print('wating for connection...')
  17. # 开启该服务,直至中断
  18. tcpServ.serve_forever()

创建SocketServer TCP 客户端

  1. from socket import *
  2. HOST = '127.0.0.1'
  3. PORT = 21567
  4. ADDR = (HOST,PORT)
  5. BUFSIZ = 1024
  6. while True:
  7. tcpCli = socket(AF_INET,SOCK_STREAM)
  8. tcpCli.connect(ADDR)
  9. data = input('> ')
  10. if not data:
  11. break
  12. tcpCli.send('{}\r\n'.format(data).encode('utf8')) #发送数据
  13. data = tcpCli.recv(BUFSIZ).decode() #接收数据
  14. if not data:
  15. break
  16. print(data.strip())
  17. tcpCli.close()

0x08:Twisted框架

Twisted 是一个完整的事件驱动的网络框架,利用它既能使用也能完整的开发异步网络应用程序和协议。提供了大量的支持来建立完整的系统,包括网络线程、安全性和身份效验、聊天/IM、DBM 、Web/因特网、电子邮件、GUI集成工具包等。

创建 Twisted Reactor TCP 服务器

  1. from twisted.internet import protocol,reactor #使用 Twisted internet类,导入 protocol,reactor 模块
  2. from time import ctime
  3. PORT = 21568
  4. class TCP(protocol.Protocol): # 定义一个类
  5. def connectionMade(self): #重写connectionMade方法
  6. clnt = self.clnt = self.transport.getPeer().host # 获取到客户端的连接的信息
  7. print('...connected from:',clnt)
  8. def dataReceived(self, data): #重写dataReceived方法
  9. # self.transport.write('[%s] %s' % (ctime(),data)) # 向客户端发送数据
  10. data = ('[%s] %s' %(ctime(),data.decode()))
  11. print(data)
  12. self.transport.write(data.encode())
  13. factory = protocol.Factory() #创建一个工厂
  14. factory.protocol = TCP # 调用一个类
  15. # 使用reactor安装一个TCP监听器,检查服务请求。
  16. # 当它接收到一个请求时,就会创建一个TSServProtocol实例来处理那个客户端的事务
  17. print('wating for connection ...')
  18. reactor.listenTCP(PORT, factory) #监听器的作用
  19. reactor.run() # 运行reactor ,启动服务端

创建 Twisted Reactor TCP 客户端

  1. from twisted.internet import protocol,reactor #使用 Twisted internet类,导入 protocol,reactor 模块
  2. HOST = '127.0.0.1' #服务端的IP
  3. PORT = 21568 # 服务器的端口
  4. class TCPCli(protocol.Protocol): #自定义一个类
  5. def sendData(self): #自定义方法sendData
  6. data = input('> ')
  7. if data:
  8. print('.....sending %s...' %data)
  9. self.transport.write(data.encode()) #给服务端发送消息
  10. else:
  11. self.transport.loseConnection() # 与服务器丢失连接
  12. def connectionMade(self): #重写connectionMade方法,与服务器进行连接时,调用此方法
  13. self.sendData() #调用senData 方法
  14. def dataReceived(self, data): #重写dataReceived方法,接受服务的消息
  15. print(data.decode())
  16. self.sendData()
  17. class TCPClifac(protocol.ClientFactory): # 创建一个类,定义一个工厂
  18. protocol = TCPCli # 给之前的类赋值
  19. clientConnectionLost = clientConnectionFailed = \
  20. lambda self,connector,reason: reactor.stop()
  21. #连接失败停止reactor
  22. reactor.connectTCP(HOST,PORT,TCPClifac()) # 与服务器进行连接
  23. reactor.run()