Python网络编程

Socket是什么

  • Socket是 计算机 网络中进程间数据流的端点
  • Socket是操作系统的通信机制
  • 应用程序通过Socket进行网络数据的传输

简单TCP过程

客户端服务器端发送SYN报文,设置序号XSYN = 1 Seq = XACK = 0设置SYN+ACK报文,设置序号YSYN = 1 Seq = YACK = X+1发送ACK报文,设置序号XSeq = X + 1 ACK =Y+1客户端服务器端

Scoket通信过程

客户端服务器端socket,connectscoket,bind,listen发送SYN报文,设置序号Xaccept设置SYN+ACK报文,设置序号Yconnect发送ACK报文,设置序号Xaccept客户端服务器端

Socket通信方式

  • Socket分为UDPTCP两种不同的通信方式。
  • 下面是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框架的本质:

  1. 浏览器是socket客户端,网站是socket服务端
  2. wsgi是一个规范,wsgiref实现了这个规范并在其内部实现了socket服务端
  3. 根据url的不同执行不同函数,即:路由系统
  4. 函数,即:处理业务逻辑
  5. 图片、css、js文件统一称为静态文件,需要读取内存直接返回给用户浏览器
原文地址:https://www.cnblogs.com/onefine/p/10499380.html