Day14 - 网络编程入门
计算机基础
mark一本《计算机网络》书籍
计算机网络发展史
TCP/IP模型
协议,就是通信计算机双方必须共同遵守的一组约定,网络协议三要素:语法、语义、时序。协议族,就是一系列的协议及其构成的通信模型,我们通常把这套东西成为TCP/IP模型。与国际标准化组织发布的OSI/RM七层模型不同,TCP/IP只是一个四层模型,自下而上依次是:网络接口层、网络层、传输层、应用层。
IP,常被翻译为网际协议,它服务于网络层,主要实现寻址和路由的功能。接入网络的每一台及其都要有自己的IP地址,IP地址就是主机在计算机网络上的身份标识。由于IPv4地址匮乏,我们平常使用IP地址并不是全球唯一的IP地址,而是一个局域网中的内部IP地址,通过网络地址转换服务我们也可以实现对网络的访问。计算机网络有大量被我们称为“路由器”的中继设备,它们会存储转发我们发送到网络上的数据分组,让从源头发出的数据最终能够找到传送到的目的地通路,这项功能就是所谓的路由。
TCP全称是传输控制协议,它是基于IP提供的寻址和路由服务而建立起来的负责实现端到端可靠传输的协议,之所以将TCP成为可靠的传输协议是因为TCP向调用者承诺了三件事:
- 数据不传丢不传错(利用握手,校验,重传机制)
- 流量控制(通过窗口活动匹配数据发送者和接受者之间的传输速度)
- 拥塞控制(通过RTT时间以及对滑动窗口的控制缓解网络拥堵)
网络应用模式
- C/S和B/S模式,即通过客户端到服务器,或者浏览器到服务器,进行网络访问。
- 去中心化的网络应用模式。意思就是去中心化的网络应用通常没有固定的服务器或者固定的客户端,所有应用的使用者既可以作为资源的提供者也可以作为资源的访问者。
基于HTTP协议的网络资源访问
HTTP超文本传输协议
HTTP是一种用于分布式、协作式和超媒体信息系统的应用层协议,它是万维网数据通信的基础,设计HTTP最初目的是为了提供一种发布和接受HTML页面的方法,通过HTTP或者HTTPS(超文本传输安全协议)请求的资源由URL(统一资源标识符)来标识。
ps.有空翻翻,Mark一篇博客《HTTP协议入门》
JSON格式
json是一种轻量级的数据交换语言,为了让人容易阅读的基础上,用来传输由属性值或者序列性的值组成的数据对象。
requests库
requests 是一个基于HTTP协议来使用网络的第三方库,它可以很方便使用HTTP,避免安全缺陷、冗余代码以及重复造轮子。
敲一个例子,下载图片
from time import time
from threading import Thread
import requests
class DownloadHanlder(Thread):
def __init__(self, url):
super().__init__()
self.url = url
def run(self):
# rfind 返回字符串最后一次出现的位置,从右向左查询,没有匹配到则返回-1
filename = self.url[self.url.rfind('/') + 1:]
print(filename)
resp = requests.get(self.url)
with open('data/' + filename, 'wb') as f:
f.write(resp.content)
def main():
resp = requests.get(
'http://api.tianapi.com/meinv/?key=19f7261742115ad6d5656318d2c4b778&num=10')
data_model = resp.json()
for mm_dict in data_model['newslist']:
url = mm_dict['picUrl']
DownloadHanlder(url).start()
if __name__ == '__main__':
main()
基于传输层协议的套接字编程
套接字就是一套用C语言携程的应用程序开发库,主要用于实现进程间的通信和网络编程,在网络应用开发中被广泛使用。在Python中也可以基于套接字来使用传输层提供的传输服务,并基于此开发自己的网络应用。实际开发中使用的套接字分三类:流套接字(TCP套接字)、数据报套接字和原始套接字。
TCP套接字
TCP套接字就是使用TCP协议提供的传输服务来实现网络通信的编程接口。在Python中可通过创建socket对象并指定type属性为SOCK_STREAM来使用TCP套接字。由于一台主机可有多个IP地址,而且很可能会配置多个不同服务,所以作为服务器端的程序,需要在创建套接字对象后将其绑定到指定的IP地址和端口上。
实现一个提供时间日期的服务器(注释代码很详细就不删掉了)
from socket import socket, SOCK_STREAM, AF_INET
from datetime import datetime
def main():
# 1.创建套接字对象并指定使用哪种传输服务
# family=AF_INET - IPv4地址
# family=AF_INET6 - IPv6地址
# type=SOCK_STREAM - TCP套接字
# type=SOCK_DGRAM - UDP套接字
# type=SOCK_RAW - 原始套接字
server = socket(family=AF_INET, type=SOCK_STREAM)
# 2.绑定IP地址和端口(端口用于区分不同的服务)
# 同一时间在同一个端口上只能绑定一个服务否则报错
server.bind(('192.168.1.2', 6789))
# 3.开启监听 - 监听客户端连接到服务器
# 参数512可以理解为连接队列的大小
server.listen(512)
print('服务器启动开始监听...')
while True:
# 4.通过循环接收客户端的连接并作出相应的处理(提供服务)
# accept方法是一个阻塞方法如果没有客户端连接到服务器代码不会向下执行
# accept方法返回一个元组其中的第一个元素是客户端对象
# 第二个元素是连接到服务器的客户端的地址(由IP和端口两部分构成)
client, addr = server.accept()
print(str(addr) + '连接到了服务器.')
# 5.发送数据
client.send(str(datetime.now()).encode('utf-8'))
# 6.断开连接
client.close()
if __name__ == '__main__':
main()
实现客户端
from socket import socket
def main():
# 1.创建套接字对象默认使用IPv4和TCP协议
client = socket()
# 2.连接到服务器(需要指定IP地址和端口)
client.connect(('192.168.1.2', 6789))
# 3.从服务器接收数据
print(client.recv(1024).decode('utf-8'))
client.close()
if __name__ == '__main__':
main()
上述例子没有使用多线程或者异步I/O的处理方式,只能一个一个客户执行。
下面设计一个多线程处理多用户请求的服务器,服务器向用户发送一张图片。
from socket import socket, SOCK_STREAM, AF_INET
from base64 import b64encode
from json import dumps
from threading import Thread
def main():
# 自定义线程类
class FileTransferHandler(Thread):
def __init__(self, cclient):
super().__init__()
self.cclient = cclient
def run(self):
my_dict = {}
my_dict['filename'] = '131.jpg'
# JSON是纯文本不能携带二进制数据
# 所以图片的二进制数据要处理成base64编码
my_dict['filedata'] = data
# 通过dumps函数将字典处理成JSON字符串
json_str = dumps(my_dict)
# 发送JSON字符串
self.cclient.send(json_str.encode('utf-8'))
self.cclient.close()
# 1.创建套接字对象并指定使用哪种传输服务
server = socket()
# 2.绑定IP地址和端口(区分不同的服务)
server.bind(('127.0.0.1', 5566))
# 3.开启监听 - 监听客户端连接到服务器
server.listen(512)
print('服务器启动开始监听...')
with open('131.jpg', 'rb') as f:
# 将二进制数据处理成base64再解码成字符串
data = b64encode(f.read()).decode('utf-8')
while True:
client, addr = server.accept()
# 启动一个线程来处理客户端的请求
FileTransferHandler(client).start()
if __name__ == '__main__':
main()
客户端
from socket import socket
from json import loads
from base64 import b64decode
def main():
client = socket()
client.connect(('127.0.0.1', 5566))
# 定义一个保存二进制数据的对象
in_data = bytes()
# 由于不知道服务器发送的数据有多大每次接收1024字节
data = client.recv(1024)
while data:
# 将收到的数据拼接起来
in_data += data
data = client.recv(1024)
# 将收到的二进制数据解码成JSON字符串并转换成字典
# loads函数的作用就是将JSON字符串转成字典对象
my_dict = loads(in_data.decode('utf-8'))
filename = my_dict['filename']
filedata = my_dict['filedata'].encode('utf-8')
with open('data/' + filename, 'wb') as f:
# 将base64格式的数据解码成二进制数据并写入文件
f.write(b64decode(filedata))
print('图片已保存.')
if __name__ == '__main__':
main()
地址改成127.0.0.1,否则会报下面的错误。没搞清楚为啥。
OSError: [WinError 10049] 在其上下文中,该请求的地址无效。
ps.JSON并不能携带二进制数据,因此对图片的二进制数据进行了Base64编码的处理。Base64是一种用64个字符表示所有二进制数据的编码方式,通过将二进制数据每6位一组的方式重新组织,刚好可以使用0~9的数字、大小写字母以及“+”和“/”总共64个字符表示从000000到111111的64种状态。
UDP套接字
UDP,即用户数据报协议。TCP和UDP都是提供端到端传输服务的协议。UDP不对传输的可靠性和可达性做出任何承诺从而避免了TCP中握手和重传的开销,所以在强调性能和不要求数据完整性的场景中(例如传输网络音视频数据),UDP可能是更好的选择。
网络应用开发
发送电子邮件
发送邮件使用的是SMTP(简单邮件传输协议),SMTP是建立在TCP提供的可靠数据传输服务的基础上的应用级协议,它规定邮件的发送者如何跟发送邮件的服务器进行通信的细节,而Python中的smtplib模块将这些操作简化成几个函数。
利用Python发送邮件,可以参考菜鸟教程的文章很详细。《Python SMTP发送邮件》
发送短信
发送短信,其实就是看使用的短信平台提供的API,根据API写代码就完事了。
mark一下示例,用到的时候会写就完事了。
import urllib.parse
import http.client
import json
def main():
host = "106.ihuyi.com"
sms_send_uri = "/webservice/sms.php?method=Submit"
# 下面的参数需要填入自己注册的账号和对应的密码
params = urllib.parse.urlencode({'account': '你自己的账号', 'password' : '你自己的密码', 'content': '您的验证码是:147258。请不要把验证码泄露给其他人。', 'mobile': '接收者的手机号', 'format':'json' })
print(params)
headers = {'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'text/plain'}
conn = http.client.HTTPConnection(host, port=80, timeout=30)
conn.request('POST', sms_send_uri, params, headers)
response = conn.getresponse()
response_str = response.read()
jsonstr = response_str.decode('utf-8')
print(json.loads(jsonstr))
conn.close()
if __name__ == '__main__':
main()