Socket是什么
- Socket是 计算机 网络中进程间数据流的端点
- Socket是操作系统的通信机制
- 应用程序通过Socket进行网络数据的传输
简单TCP过程
Scoket通信过程
Socket通信方式
- Socket分为
UDP
和TCP
两种不同的通信方式。 - 下面是socket的实现过程:
Socket参数
-
family:地址簇(cu)
- socket.AF_INET IPv4(默认)
- socket.AF_INET6 IPv6
- socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
-
type:类型
- socket.SOCK_STREAM 流式socket,for TCP(默认)
- socket.SOCK_DGRAM 数据报文方式socket,for UDP
- socket.SOCK_RAM 原始套接字
- socket.SOCK_RDM 可靠UDP形式
- socket.SOCK_SEQPACKET 可靠的连续数据包服务
-
proto:协议号
- 0:默认,可以省略
- CAN_RAM或CAN_BCM:地址簇为AF_CAN时
TCP/IPv4的一个例子
以下两个程序:分别充当服务器端和客户端的角色:
- socket_server.py
- socket_client.py
socket_server.py
代码
import socket
import random
sk = socket.socket() # 创建实例
ip_port = ("127.0.0.1", 8000) # 定义&绑定ip和端口
sk.bind(ip_port) # 绑定监听
sk.listen(5) # 最大连接数
while True: # 不断循环不断接收数据
# 提示信息
print("正在等待一次连接...")
# 接收数据
conn, address = sk.accept()
# 定义消息
msg = "你好呀,初次建立连接。"
# 返回信息
# python3.x中,网络数据的发送和接收都是byte类型
# 如果发送的数据是str型的则需要进行编码
conn.send(msg.encode())
while True: # 不断接收客户端发来的消息,exit通知
# 接收客户端的消息
try:
data = conn.recv(1024)
except Exception as result:
print("出现错误1:", result)
break
# 退出指令
if data == b'exit':
print("client主动关闭了连接.")
break
# 测试:打印出数据
print("接收的数据是:", data.decode())
# 处理客户端的数据并返回
try:
conn.send(("回声:[" + data.decode() + "],随机数[%s]" % random.randint(1, 1000)).encode())
except Exception as result:
print("出现错误2:", result)
print("此次连接关闭")
break
# 主动关闭连接
conn.close()
# 提示信息
print("这次连接已结束!")
socket_client.py
代码
import socket
# 实例初始化
client = socket.socket()
# 要访问的服务器ip和port
ip_port = ("127.0.0.1", 8000)
# 连接主机
client.connect(ip_port)
print("已和主机", ip_port[0], ':', ip_port[1], "建立连接信息...")
while True:
# 接收主机返回的消息
data = client.recv(1024)
# 打印接收的数据,在Python3.x中此处是byte型数据
print("接收到的数据是:", data.decode())
# 不断和主机发送信息
# msg_input = input("输入要发送的信息:").strip()
msg_input = ""
while msg_input.isspace() or msg_input == "": # 解决换行的问题
msg_input = input("输入要发送的信息:")
# 发送信息
client.send(msg_input.encode())
# 结束会话连接条件
if msg_input == 'exit':
break
# 接收到的回复
# print(client.recv(1024).decode())
print("断开主机", ip_port[0], ':', ip_port[1], "的连接!")
UDP/IPv4的一个例子
- socket_server_udp.py
- socket_client_udp.py
socket_server_udp.py
代码
import socket
# 创建实例:指定IPv4, UDP方式
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ip_port = ("127.0.0.1", 8000)
# 绑定监听
sk.bind(ip_port)
while True:
# 接收数据
data = sk.recv(1024)
# 打印数据
print(data.decode())
socket_client_udp.py
代码
import socket
ip_port = ("127.0.0.1", 8000)
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
msg_input = input("请输入发送的消息:")
# 退出循环条件
if msg_input == "exit":
break
# 数据发送
sk.sendto(msg_input.encode(), ip_port)
# 发送关闭信息...推荐主动关闭
sk.close()
socketserver的一个例子
socket_server_tcp2.py
代码
import socketserver
# 原因:多线程
import random
class MyServer(socketserver.BaseRequestHandler):
# 下面三个方法被重写了, 三个方法的执行顺序从上到下
# 如果handle方法出现报错,则会进行跳过
# 但setup方法和finish方法无论如何都会被执行,所以只对handle方法重写
def setup(self):
pass
def handle(self):
# 定义连接变量
conn = self.request
# 发送消息定义
msg = "Hello world"
# 消息发送
conn.send(msg.encode())
# 不断接收客户端的消息
while True:
# 接收客户端消息
data = conn.recv(1024)
# 打印消息
print(data.decode())
if data == b"exit".strip():
break
conn.send((data.decode() + '[' + str(random.randint(1, 1000)) + ']').encode())
print("客户端主动断开连接!")
conn.close()
def finish(self):
pass
# def main():
# pass
if __name__ == '__main__':
# main()
# 创建多线程实例
server = socketserver.ThreadingTCPServer(("127.0.0.1", 8000), MyServer)
# 开启异步多线程,连续等待
server.serve_forever()
Web端的一个例子
socket实现:
socket_browser.py
文件
# -*- coding:utf-8 -*-
import socket
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("127.0.0.1", 8000))
# host = socket.gethostname()
# print("host:", host)
# sock.bind((host, 8000))
sock.listen(5)
while True:
# 等待浏览器访问
conn, addr = sock.accept()
# print("conn:", conn) # <class 'socket.socket'>
# print(type(conn))
# # conn: <socket.socket fd=544, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8000), raddr=('127.0.0.1', 50395)>
# print("addr:", addr) # <class 'tuple'>
# print(type(addr))
# # addr: ('127.0.0.1', 50395)
# 接收浏览器发送来的请求内容
data = conn.recv(1024)
# print("data:", data)
# # data: b'GET / HTTP/1.1
Host: 127.0.0.1:8000
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
'
# print(type(data)) # <class 'bytes'>
# 给浏览器回复内容
conn.send(b"HTTP/1.1 200 OK
Content-Type:text/html; charset=utf-8
")
conn.send("hello world".encode("utf-8"))
# 关闭和浏览器创建的socket连接
conn.close()
if __name__ == '__main__':
main()
wsgi实现
目录结构:
D:.
│ wsgi_server.py
│
└─static_data
└─imgs
haha.png
wsgi_server.py
文件
# -*- coding:utf-8 -*-
from wsgiref.simple_server import make_server
import os
import re
# 基于WSGI开发一个web服务器
# 1. 路由的分发器,负责把url匹配到对应的函数
# 2. 开发好对应的业务函数
# 3. 一个请求来了之后,先走路由分发器,如果找到对应的function就执行,如果没有找到就执行404
def f1(environ, start_response):
environ
print("f1 Page")
start_response("200 OK", [('Content-Type', 'text/html;charset=utf-8')])
return [bytes('<h2>F1 Page</h2>', encoding='utf-8'), ]
def f2(environ, start_response):
environ
print("f2 Page")
start_response("200 OK", [('Content-Type', 'text/html;charset=utf-8')])
return [bytes('<h2>F2 Page</h2>', encoding='utf-8'), ]
def f3(environ, start_response):
environ
print("f3 Page")
data = """
<h1>图片展示:</h1>
<img src='/static/imgs/haha.png' />
<p>上面是一张图片</p>
"""
start_response("200 OK", [('Content-Type', 'text/html;charset=utf-8')])
return [bytes(data, encoding='utf-8'), ]
def url_dispacher():
"""
负责把url与对应的方法关联起来
:return:
"""
urls = {
'/f1': f1,
'/f2': f2,
'/f3': f3, # 注意f3是与定义的函数对应的,要搞懂,,,
}
return urls
def img_handler(request_url):
"""
:param request_url: /static_data/imgs/haha.png
:return:
"""
# BASEE_DIR = os.path.abspath(__file__) # BASEE_DIR: D:JetBrainsProjectPythonPySocketTest4wsgi_server.py
# BASEE_DIR = os.path.dirname(BASEE_DIR) # BASEE_DIR: D:JetBrainsProjectPythonPySocketTest4
# base_dir = os.path.dirname(os.path.abspath(__file__))
img_path = re.sub('/static', 'static_data', request_url)
# print("img_path:", img_path)
if os.path.isfile(img_path):
# print("haha来了")
f = open(img_path, "rb")
data = f.read()
f.close()
# print("data:", data)
return [data, 0] # 0代表正确, 1代表错误
return [None, 1] # 发生错误
def run_server(environ, start_response):
"""
当有用户在浏览器上访问:http://127.0.0.1:8000/,立即执行该函数并将函数的返回值返回给用户浏览器
:param environ:请求相关内容,比如浏览器类型、版本、来源地址、url等
:param start_response:响应相关
:return:
"""
# print("environ:", environ)
# print("start_response:", start_response)
url_list = url_dispacher() # 屎一样的代码
request_url = environ.get("PATH_INFO")
print("request_url:", request_url) # /hhh
if request_url in url_list:
func_data = url_list[request_url](environ, start_response)
return func_data # 返回路由指定的内容
elif request_url.startswith("/static/"): # 图片
# /static_data/imgs/haha.png
# /static_data/代表是图片
# print("有图片啦!")
img_data, img_status = img_handler(request_url)
if img_status == 0: # 图片正确
start_response('200 OK ', [('Content-Type', 'text/jpeg;charset=utf-8')])
return [img_data, ]
# print("但是图片没找到!!")
else:
start_response('404 ', [('Content-Type', 'text/html;charset=utf-8')])
return [bytes('<h1>我是 Onefine.哈哈哈页面不存在!</h1>', encoding='utf-8'), ]
if __name__ == '__main__':
httpd = make_server('127.0.0.1', 8000, run_server) # 注意第二个参数没搞懂
httpd.serve_forever()
总结
虽然自己写Web Server比较麻烦,但是我们也从中了解到Web框架的本质:
- 浏览器是socket客户端,网站是socket服务端
- wsgi是一个规范,wsgiref实现了这个规范并在其内部实现了socket服务端
- 根据url的不同执行不同函数,即:路由系统
- 函数,即:处理业务逻辑
- 图片、css、js文件统一称为静态文件,需要读取内存直接返回给用户浏览器