浅析python-socket编程
1. 什么是socket?
1. socket编程基本流程图
2. socket、threading实现聊天通信
# coding:utf-8
# 服务端:
import socket import threading HOST, PORT = "localhost", 8020 address = (HOST, PORT) # socket.AF_INET代表IPV4, socket.AF_INET6代表IPV6 # socket.SOCK_STREAM代表TCP, SOCK_DGRAM代表UDP server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(address) server.listen() def handlesock(sock, addr): while True: data = sock.recv(1024).decode("utf-8") if data == "exit": break print("收到来自客户端[{0}]的消息, 内容是[{1}]".format(addr, data)) response = input("回复[{0}]:\t".format(addr)).encode("utf8") sock.send(response) while True: sock, addr = server.accept() cur_sock = threading.Thread(target=handlesock, args=(sock, addr)) cur_sock.start()
# 客户端:
import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("localhost", 8020)) while True: response = input("回复服务器:").encode("utf8") client.send(response) if response=="exit": break data = client.recv(1024).decode("utf8") print("收到来自服务器的消息:%s" % data) client.close()
解释:对于单用户、单服务来说,一个服务器只服务一个用户对象, 那么要想实现两端通信, 能够通过循环来实现输入与输出;若是服务于多个用户对象, 就需要保留每一个连接(socket实例)对象来单独处理,这里我们采用了线程的方式,即每接收一个连接请求,就新开辟一个线程来处理(考虑到内存开销也可以用线程池来替代)。
3. socket模拟浏览器发送http请求
在此之前,我们必须弄懂相关概念,
###:socket并不是一种协议,而是对TCP/IP协议(或UDP/IP协议)的高级封装,其与TCP连接是可以存在包含关系的,换一句话说,socket就是一种通信接口,拥有实现可靠连接的功能。此外,当socket(网络套接字)采用TCP来建立连接的时候,是面向长连接的。
###:http连接显著的特点是客户端发送的每次请求都需要服务器回送相应响应,在请求结束后,会主动释放连接,从建立连接到关闭连接的过程称之为“一次连接”。此外HTTP连接是无状态的,所以在请求过程中需要发送请求头来标明请求身份。
(HTTP请求底层是一种特殊处理后的socket,是面向短连接的)
#coding:utf-8 from urllib.parse import urlparse import socket class SocketHttp(): """模拟http请求""" def __init__(self, url: str): self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.url = url def parse_url(self): """url解析""" self.url = urlparse(self.url) domain = self.url.netloc path = self.url.path if path == "": path = "/" return domain, path def request(self): """建立socket连接, 请求url""" domain, path = self.parse_url() self.client.connect((domain, 80)) request_data = """GET {0} HTTP/1.1\r\nHost: {1}\r\nConnection: close\r\n\r\n""".format(path, domain).encode("utf8") self.client.send(request_data) # 读取数据 response = b"" while True: cur_data = self.client.recv(1024) if cur_data: response+=cur_data else: break self.client.close() return response.decode() if __name__ == '__main__': client_http = SocketHttp("https://www.baidu.com/") print(client_http.request())
输出:
我们也可以采用异步非阻塞IO来获取http响应
#coding:utf-8 from urllib.parse import urlparse import socket class SocketHttp(): """模拟http请求""" def __init__(self, url: str): self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.client.setblocking(False) self.url = url def parse_url(self): """url解析""" self.url = urlparse(self.url) domain = self.url.netloc path = self.url.path if path == "": path = "/" return domain, path def request(self): """建立socket连接, 请求url""" domain, path = self.parse_url() try: self.client.connect((domain, 80)) except BlockingIOError: pass request_data = """GET {0} HTTP/1.1\r\nHost: {1}\r\nConnection: close\r\n\r\n""".format(path, domain).encode("utf8") # 轮询尝试发送,直到与服务器连接成功 while 1: try: self.client.send(request_data) except OSError as e: pass else: break # 读取数据 response = b"" while True: # 不断轮询接收数据 while 1: try: cur_data = self.client.recv(1024) except BlockingIOError as e: pass else: break if cur_data: response+=cur_data else: break self.client.close() return response.decode() if __name__ == '__main__': client_http = SocketHttp("https://www.baidu.com/") print(client_http.request())
4.非租塞IO
当两个通信实体成功建立连接之后,recv方法调用之后,其内核空间与应用空间的发生的数据复制行为如下
可以看出,若调用recv方法之后,数据未准备好, socket所处线程一直处于阻塞状态,直至数据复制到用户空间。这时,CPU未得到有效利用。那么如何才能利用起cpu资源呢?轮询方式便产生了(非阻塞I/O模式)
当线程执行recv方法时,不再让该线程睡眠,而是立即返回一个错误状态,以此不断轮询直到返回正确状态才退出循环,值得注意的是该轮询的方式需要我们手动实现。如下:
这里我们将客户端程序设置为非阻塞I/O模式,服务器程序不作修改,结果出现运行错误:
此处出错误是合理的,因为在非阻塞I/O模式下,模式发生改变,调用connnect方法便出现异常,此外,recv方法在调用时数据可能未准备好,所以这里需要手动实现轮询,如下:
import socket import threading HOST, PORT = "localhost", 8020 address = (HOST, PORT) server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(address) server.listen() print("server is listening...") def handlesock(sock, addr): while True: data = sock.recv(1024).decode("utf-8") if data == "exit": break print("收到来自客户端[{0}]的消息, 内容是[{1}]".format(addr, data)) response = input("回复[{0}]:\t".format(addr)).encode("utf8") sock.send(response) while True: sock, addr = server.accept() cur_sock = threading.Thread(target=handlesock, args=(sock, addr)) cur_sock.start()
import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.setblocking(0) try: client.connect(("localhost", 8020)) except BlockingIOError: pass while True: response = input("回复服务器:").encode("utf8") client.send(response) if response=="exit": break # 非阻塞I/O轮询方式 while True: try: data = client.recv(1024) except BlockingIOError as e: pass else: if data: data = data.decode("utf8") break print("收到来自服务器的消息:%s" % data) client.close()