socket简述
socket
英文是“孔”或“插座”的意思,也称为套接字,用于描述 IP 地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在 internet
上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个socket,并绑到一个端口上,不同的端口对应不同的服务。所以正如英文愿意那样,像一个多孔插座一个主机犹如布满插座的房间,每个插座都有一个编号(端口号),有的插座提供220v交流电,有的提供110v电流,有的则提供有线电视节目。客户端将插头插到不同编号的插座,就可以得到不同的服务了。

连接原理:
套接字之间的连接可以分为三个步骤:服务器监听、客户端请求、连接确认
- 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
- 客户端请求:客户端的套接字提出连接目标服务器端的套接字请求,为此,客户端必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后向服务器端套接字提出连接请求。
- 连接确认:当服务器端的套接字监听或者说接收客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的进程,把服务器端套接字的描述发送给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
一、初步
在客户端,实现socket
连接代码如下:
import socket
import ssl
from utils import log
s = socket.socket()
host = 'www.zhihu.com'
port = 80
# 参数为元祖
s.connect((host, port))
(ip, port) = s.getsockname()
log(ip, port)
http_request = 'GET / HTTP/1.1\r\nhost:{}\r\n\r\n'.format(host)
rqu = http_request.encode()
s.send(rqu)
res = s.recv(1024)
- 实例化
socket
- 确定连接的服务器和端口,利用
connect()
函数进行连接, - 发送请求:
send()
函数; - 接收响应:
recv()
函数,参数为接收的字节数。(这里应该建立一个循环)
注意:
-
connect()
的参数为元祖; - 发送请求前,字符串需要经过
encode()
方法编码,接收响应后再通过decode()
方法解码,因为电脑是通过二进制传输的; -
https
的话,包裹一层ssl
:s = ssl.wrap_socket(socket.socket())
二、升级
import socket
import ssl
def parsed_url(url):
if 'http://' in url:
protocol = 'http'
u = url[7:]
else:
protocol = 'https'
u = url[8:]
if '/' in u:
host_port, path = u.split('/', 1)
path = '/' + path
else:
path = '/'
host_port = u
if ':' in host_port:
host, port = host_port.split(':', 1)
else:
# 这里端口的获取根据前面的protocol来
p = {
'http': 80,
'https': 443
}
host = host_port
port = p[protocol]
return protocol, host, port, path
def response_by_socket(s):
response = b''
buffer_size = 1024
while True:
r = s.recv(buffer_size)
response += r
if len(r) < buffer_size:
return response
def parsed_response(r):
header, body = r.split('\r\n\r\n', 1)
# h是一个列表
h = header.split('\r\n')
status_code = h[0].split()[1]
status_code = int(status_code)
headers = {}
for line in h[1:]:
k, v = line.split(':', 1)
headers[k] = v
return status_code, headers, body
// 包装起来
def socket_by_protocol(protocol):
s = socket.socket()
if protocol == 'https':
s = ssl.wrap_socket(s)
else:
s = socket.socket()
return s
def get(url):
protocol, host, port, path = parsed_url(url)
s = socket_by_protocol(protocol)
s.connect((host, port))
request = "GET {} HTTP/1.1\r\nhost:{}\r\n\r\n".format(path, host)
http_req = request.encode()
s.send(http_req)
response = response_by_socket(s)
r = response.decode()
status_code, headers, body = parsed_response(r)
if status_code == 301:
url = headers['Location']
return get(url)
else:
return response, status_code
if __name__ == '__main__':
url = 'https://movie.douban.com/top250'
get(url)
逻辑:
- 定义
get()
函数,发送请求,接收响应 - 发送请求前,根据
parsed_url()
函数,获得protocol、host、port、path
,生成请求头 - 根据
response_by_socket()
函数,接收响应 - 根据
parsed_response()
函数判断是否有重定向
小知识点:
-
split()
函数可以实现字符串的切割,变量要么是一个,那么生成的就是一个列表,要么与切割开的字符串一样多。 -
path
最前面必须带:“/”,就是这地方出错了; -
__name__
,在当前文件运行时,name = main
,被其他脚本调用时,neme
为其他脚本的文件名。
测试函数
def test_parsed_url():
http = 'http'
https = 'https'
port = 80
host = 'g.cn'
examples = [
('https://g.cn', (https, host, 443, '/')),
('http://g.cn', (http, host, port, '/')),
('http://g.cn/majun', (http, host, port, '/majun')),
('https://g.cn:8000/majun', (https, host, '8000', '/majun')),
]
for line in examples:
url, ans = line
u = parsed_url(url)
e = 'Pased Error: ({}), ({}), ({})'.format(url, u, ans)
assert u == ans, e
就是拿解析的结果和正确答案进行比较,这里用到了assert,如果不合适就打印出错误的地方。
注意:解析出来的端口是个字符串
网友评论