第十章 python socker编程
10.1 弄懂HTTP、Socker、TCP这几个概念
整个计算机网络都是由协议组成的。
A是client端,B是服务器端,当A向B发送请求时,数据传输的时候(平时使用requests、urllib请求数据),在我们操作系统和网络之中要经历5层网络模型。
应用层:和具体的包相关,如果是requests,则走http协议,如果是一些邮件则走SMTP协议,如果用的FTP的包,则走FTP协议。
传输层:
网络层:从一个ip到另一个ip时,网络该如何发包
数据链路层:
物理层:
A传送数据是从上到下的(拆包),B接受数据是从下到上的(组包)。
浏览器也是实现了http协议,http协议是构建与TCP、IP...协议之上。可以看到应用层有很多协议,HTTP、SMTP、DNS.......。
如果我们要实现TCP、UDP协议,自己写协议比较复杂,一般都是操作系统提供的。操作系统提供了一个Socket,可以理解为一个API,不属于任何一个协议,我们通过Socket可以和传输层打交道。
把Socket理解为一个插座,数据理解为电力,如果我们要充电,就需要插座,而且电在传输过程中要经过很多变压器(传送层下面的层),将设备和插座连接起来,就可以使用电了。
在TCP协议上提供了一个接口(Socket),通过Socket和底层协议建立了一个连接。
HTTP协议是单向的,A发送请求,B响应,但B不能主动发送消息给A。
但是TCP协议是双向的,只要不主动断开,就一直在存在联系。
Socket可以操控TCP,这样就可以实现自己的协议,不需要去TCP协议上进行开发。这个在很多情况下是很实用的,比如聊天工具,会在应用层实现一套自己的协议,通过Socket和TCP打交道。
10.2 通过Socket实现client和Server通信
Socket编程一般会涉及Socket_client和Socket_server编程。
左侧是server端,右侧是client端。
server端,比如写一个web系统,部署到服务器。
client端,比如常见的PC软件、浏览器。
爬虫是一个很典型的client端,请求server端的数据。
自己实现一个web server端,有个特点是必须随时处于监听和服务的状态,因为不知道客户端什么时候发送请求过来。
绑定:
协议(Socket本身是接口,用来连接应用层和传输层)、
地址(Socket可以监听本地ip,也可以监听对外Ip)
端口(每一个应用程序只能占用一个端口,使用Socket指定端口,就可以把应用程序和端口绑定起来,这样在多个应用程序下数据传输不会混乱,特定的应用程序使用某一个端口进行数据传输),相同的应用程序可以使用一个端口(多线程中运行了相同的程序)。
listen:
监听Socker连接请求
绑定完之后就可以开始监听,监听之后调用accept函数会阻塞连接请求,直到client端发起连接请求(TCP,三次握手),成功后就创建一个Socket连接。然后运行recv函数获取数据(需要client端send发送数据过来),之后可以打印并做各种操作(服务端可以send数据给client端,理论上只要有Socket连接,Server端就可以一直向client端发送数据,但是在HTTP请求中,一般只进行一次server和client之间的数据传输,之后连接就会关闭)。
可以看一个服务端的简单例子
server:
import socket
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # AF_INET是ipv4,socket.SOCK_STREAM是一个协议
server.bind(('0.0.0.0', 8000))# IP地址,端口
server.listen()
sock, addr = server.accept() # accept 会返回一个sock和addr
#获取从客户端发送的数据
data = sock.recv(1024) #1024=1k的数据
print(data.decode("utf8")) # data是byte类型
server.close()
sock.close()
bobby
client:
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8000)) # 要明确指明ip地址
client.send("bobby".encode("utf8"))
client.close()
这样通信就完成了,再修改一下,让服务端发送数据给client端
server:
import socket
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # AF_INET是ipv4,socket.SOCK_STREAM是一个协议
server.bind(('0.0.0.0', 8000))# IP地址,端口
server.listen()
sock, addr = server.accept() # accept 会返回一个sock和addr
#获取从客户端发送的数据
data = sock.recv(1024) #1024=1k的数据
print(data.decode("utf8")) # data是byte类型
sock.send("hello {}".format(data.decode("utf8")).encode("utf8")) # 从client传过来的数据是byte类型,先decode为str,再encode为byte,传入client端
server.close()
sock.close()
bobby
client:
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8000)) # 要明确指明ip地址
client.send("bobby".encode("utf8"))
data = client.recv(1024)
print (data.decode("utf8"))
client.close()
hello bobby
10.3 Socker实现聊天和多用户连接
server:
import socket
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # AF_INET是ipv4,socket.SOCK_STREAM是一个协议
server.bind(('0.0.0.0', 8000))# IP地址,端口
server.listen()
sock, addr = server.accept() # accept 会返回一个sock和addr
# server是用来监听的,sock才是用户连接的socket
#获取从客户端发送的数据
#一次获取1k的数据
while True:
data = sock.recv(1024) # 假设1024字节就可以把数据取完
print(data.decode("utf8"))
re_data = input()
sock.send(re_data.encode("utf8"))
client:
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 对用户来说,只需要一个client就可以了
client.connect(('127.0.0.1', 8000)) # 要明确指明ip地址
while True:
re_data = input()
client.send(re_data.encode("utf8"))
data = client.recv(1024)
print (data.decode("utf8"))
但是问题是server端只能接受一个请求,一个用户进来后,就一直在while循环里出不来。
如何实现多用户连接呢,可以用线程,每一个socket是一个线程,主线程接受请求。
server:
import socket
import threading
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # AF_INET是ipv4,socket.SOCK_STREAM是一个协议
server.bind(('0.0.0.0', 8000))# IP地址,端口
server.listen()
# server是用来监听的,sock才是用户连接的socket
#获取从客户端发送的数据
#一次获取1k的数据
def handle_sock(sock, addr):
while True:
data = sock.recv(1024) # 假设1024字节就可以把数据取完
print(data.decode("utf8"))
re_data = input()
sock.send(re_data.encode("utf8"))
while True:
sock, addr = server.accept()
# 用线程去处理新接收的连接(用户)
client_thread = threading.Thread(target=handle_sock, args=(sock, addr)) # target必须为函数名
client_thread.start()
client:
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 对用户来说,只需要一个client就可以了
client.connect(('127.0.0.1', 8000)) # 要明确指明ip地址
while True:
re_data = input()
client.send(re_data.encode("utf8"))
data = client.recv(1024)
print (data.decode("utf8"))
10.4 socket模拟http请求
requests ->urllib -> socket
requests 基于urllib ,urllib 基于socket,凡是网络相关的连接(比如数据库连接,进程间的通信等)最底层都是用socket来连接的。
#requests -> urlib -> socket
import socket
from urllib.parse import urlparse # urlparse是用来解析url的,不是用来发起请求的
def get_url(url):
#通过socket请求html
url = urlparse(url) # 需要先进行url解析
host = url.netloc # url主域名
path = url.path # url子路径
if path == "": # 如果直接请求主域名
path = "/"
# 建立socket连接
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((host, 80)) # HTTP请求都是80端口
# http协议是基于TCP协议的
# GET {} 第一行必须这样写
client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path, host).encode("utf8"))
data = b"" # 从server获取的数据为byte
while True:
d = client.recv(1024) # 临时获取的数据
if d:
data += d
else: # 如果没有数据,则退出
break
data = data.decode("utf8")
html_data = data.split("\r\n\r\n")[1] # 去掉header信息
print(html_data)
client.close()
if __name__ == "__main__":
get_url("http://www.baidu.com")