40、网络编程与Web编程技术详解

网络编程与Web编程技术详解

在网络编程和Web编程领域,有许多实用的技术和方法可以帮助我们实现各种功能。本文将详细介绍一些常见的网络编程任务及其解决方案,包括创建TCP和UDP服务器、生成IP地址范围、创建REST接口、实现远程过程调用等。

1. 网络请求与httpbin的使用

在进行网络请求实验时,使用像httpbin这样的网站通常比直接在真实网站上进行实验更可取。例如,在进行HTTP认证客户端的学习时,若在银行网站上进行实验,可能会因多次登录失败而导致账户被锁定。以下是一个使用 requests 库发送请求的示例:

headers = { 'User-agent': 'goaway/1.0' }
resp = r.json
print(resp['headers'])
print(resp['args'])

这里, requests 库支持更高级的客户端HTTP协议,如OAuth。若想深入了解,可查阅 requests 的官方文档。

2. 创建TCP服务器
2.1 问题与解决方案

若要实现一个通过TCP协议与客户端通信的服务器,可使用 socketserver 库。以下是一个简单的回声服务器示例:

from socketserver import BaseRequestHandler, TCPServer

class EchoHandler(BaseRequestHandler):
    def handle(self):
        print('Got connection from', self.client_address)
        while True:
            msg = self.request.recv(8192)
            if not msg:
                break
            self.request.send(msg)

if __name__ == '__main__':
    serv = TCPServer(('', 20000), EchoHandler)
    serv.serve_forever()

在这个示例中,我们定义了一个 EchoHandler 类,它继承自 BaseRequestHandler ,并实现了 handle 方法来处理客户端连接。 request 属性是客户端套接字, client_address 包含客户端地址。

2.2 测试服务器

为了测试服务器,可以启动服务器并在另一个Python进程中连接它:

from socket import socket, AF_INET, SOCK_STREAM
s = socket(AF_INET, SOCK_STREAM)
s.connect(('localhost', 20000))
s.send(b'Hello')
print(s.recv(8192))
2.3 使用 StreamRequestHandler

在许多情况下,使用 StreamRequestHandler 可以更方便地处理连接,它可以为套接字提供类似文件的接口:

from socketserver import StreamRequestHandler, TCPServer

class EchoHandler(StreamRequestHandler):
    def handle(self):
        print('Got connection from', self.client_address)
        for line in self.rfile:
            self.wfile.write(line)

if __name__ == '__main__':
    serv = TCPServer(('', 20000), EchoHandler)
    serv.serve_forever()
2.4 多客户端处理

默认情况下, socketserver 创建的TCP服务器是单线程的,只能同时处理一个客户端连接。若要处理多个客户端,可以创建 ForkingTCPServer ThreadingTCPServer 的实例:

from socketserver import ThreadingTCPServer

if __name__ == '__main__':
    serv = ThreadingTCPServer(('', 20000), EchoHandler)
    serv.serve_forever()

然而,创建新进程或线程的服务器会为每个客户端连接创建一个新的进程或线程,这可能会被恶意黑客利用,导致服务器崩溃。为了避免这种情况,可以创建一个预先分配的工作线程或进程池:

from threading import Thread

if __name__ == '__main__':
    NWORKERS = 16
    serv = TCPServer(('', 20000), EchoHandler)
    for n in range(NWORKERS):
        t = Thread(target=serv.serve_forever)
        t.daemon = True
        t.start()
    serv.serve_forever()
2.5 套接字参数设置

通常, TCPServer 在创建实例时会绑定并激活套接字。但有时可能需要通过传递参数来配置套接字,可通过设置 bind_and_activate=False 来实现:

if __name__ == '__main__':
    serv = TCPServer(('', 20000), EchoHandler, bind_and_activate=False)
    serv.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    serv.server_bind()
    serv.server_activate()
    serv.serve_forever()

也可以通过设置类变量 allow_reuse_address 来允许服务器重新绑定到之前使用的端口号:

if __name__ == '__main__':
    TCPServer.allow_reuse_address = True
    serv = TCPServer(('', 20000), EchoHandler)
    serv.serve_forever()
2.6 StreamRequestHandler 的高级设置

StreamRequestHandler 类更灵活,支持通过设置额外的类变量来启用一些功能:

import socket

class EchoHandler(StreamRequestHandler):
    timeout = 5
    rbufsize = -1
    wbufsize = 0
    disable_nagle_algorithm = False

    def handle(self):
        print('Got connection from', self.client_address)
        try:
            for line in self.rfile:
                self.wfile.write(line)
        except socket.timeout:
            print('Timed out!')
2.7 直接使用 socket 库创建服务器

除了使用 socketserver 库,也可以直接使用 socket 库创建服务器:

from socket import socket, AF_INET, SOCK_STREAM

def echo_handler(address, client_sock):
    print('Got connection from {}'.format(address))
    while True:
        msg = client_sock.recv(8192)
        if not msg:
            break
        client_sock.sendall(msg)
    client_sock.close()

def echo_server(address, backlog=5):
    sock = socket(AF_INET, SOCK_STREAM)
    sock.bind(address)
    sock.listen(backlog)
    while True:
        client_sock, client_addr = sock.accept()
        echo_handler(client_addr, client_sock)

if __name__ == '__main__':
    echo_server(('', 20000))
3. 创建UDP服务器
3.1 问题与解决方案

若要实现一个通过UDP协议与客户端通信的服务器,同样可以使用 socketserver 库。以下是一个简单的服务器示例,它会返回当前时间:

from socketserver import BaseRequestHandler, UDPServer
import time

class TimeHandler(BaseRequestHandler):
    def handle(self):
        print('Got connection from', self.client_address)
        msg, sock = self.request
        resp = time.ctime()
        sock.sendto(resp.encode('ascii'), self.client_address)

if __name__ == '__main__':
    serv = UDPServer(('', 20000), TimeHandler)
    serv.serve_forever()
3.2 测试服务器

为了测试服务器,可以在另一个Python进程中发送消息:

from socket import socket, AF_INET, SOCK_DGRAM
s = socket(AF_INET, SOCK_DGRAM)
s.sendto(b'', ('localhost', 20000))
print(s.recvfrom(8192))
3.3 UDP服务器的特点与处理

典型的UDP服务器接收客户端的消息和地址,并将响应消息发送回客户端。与TCP服务器不同,UDP协议不建立连接,因此UDP服务器通常更容易编写,但也存在不可靠的缺点,消息可能会丢失。若需要确保消息的可靠性,需要在程序中添加序列号、重试、超时等机制。 UDPServer 类是单线程的,通常情况下对于UDP连接来说影响较小,但如果需要并发处理,可以创建 ForkingUDPServer ThreadingUDPServer 的实例:

from socketserver import ThreadingUDPServer

if __name__ == '__main__':
    serv = ThreadingUDPServer(('', 20000), TimeHandler)
    serv.serve_forever()

也可以直接使用 socket 库实现UDP服务器:

from socket import socket, AF_INET, SOCK_DGRAM
import time

def time_server(address):
    sock = socket(AF_INET, SOCK_DGRAM)
    sock.bind(address)
    while True:
        msg, addr = sock.recvfrom(8192)
        print('Got message from', addr)
        resp = time.ctime()
        sock.sendto(resp.encode('ascii'), addr)

if __name__ == '__main__':
    time_server(('', 20000))
4. 生成IP地址范围
4.1 问题与解决方案

若有一个CIDR格式的网络地址,如 123.45.67.89/27 ,想生成它所代表的所有IP地址范围,可以使用 ipaddress 模块:

import ipaddress
net = ipaddress.ip_network('123.45.67.64/27')
for a in net:
    print(a)

同样,对于IPv6地址也可以进行类似的操作:

net6 = ipaddress.ip_network('12:3456:78:90ab:cd:ef01:23:30/125')
for a in net6:
    print(a)
4.2 网络对象的操作

网络对象支持数组式的索引操作,还可以进行成员关系检查:

print(net.num_addresses)
print(net[0])
print(net[-1])
a = ipaddress.ip_address('123.45.67.69')
print(a in net)

IP地址和网络地址可以一起定义为IP接口:

inet = ipaddress.ip_interface('123.45.67.73/27')
print(inet.network)
print(inet.ip)
4.3 注意事项

ipaddress 模块与其他网络相关模块(如 socket 库)之间的关联有限,通常不能直接将 IPv4Address 实例用作地址字符串的替代,需要使用 str() 进行显式转换:

a = ipaddress.ip_address('127.0.0.1')
from socket import socket, AF_INET, SOCK_STREAM
s = socket(AF_INET, SOCK_STREAM)
s.connect((str(a), 8080))
5. 创建简单的REST接口
5.1 问题与解决方案

若想通过简单的REST接口远程控制程序或与之通信,且不想安装完整的Web框架,可以基于Python的WSGI标准编写代码。以下是一个示例:

# resty.py
import cgi

def notfound_404(environ, start_response):
    start_response('404 Not Found', [ ('Content-type', 'text/plain') ])
    return [b'Not Found']

class PathDispatcher:
    def __init__(self):
        self.pathmap = { }

    def __call__(self, environ, start_response):
        path = environ['PATH_INFO']
        params = cgi.FieldStorage(environ['wsgi.input'], environ=environ)
        method = environ['REQUEST_METHOD'].lower()
        environ['params'] = { key: params.getvalue(key) for key in params }
        handler = self.pathmap.get((method,path), notfound_404)
        return handler(environ, start_response)

    def register(self, method, path, function):
        self.pathmap[method.lower(), path] = function
        return function

使用这个调度器,我们可以编写不同的处理函数:

import time
_hello_resp = '''\
<html>
    <head>
        <title>Hello {name}</title>
    </head>
    <body>
        <h1>Hello {name}!</h1>
    </body>
</html>'''

def hello_world(environ, start_response):
    start_response('200 OK', [ ('Content-type','text/html')])
    params = environ['params']
    resp = _hello_resp.format(name=params.get('name'))
    yield resp.encode('utf-8')

_localtime_resp = '''\
<?xml version="1.0"?>
<time>
    <year>{t.tm_year}</year>
    <month>{t.tm_mon}</month>
    <day>{t.tm_mday}</day>
    <hour>{t.tm_hour}</hour>
    <minute>{t.tm_min}</minute>
    <second>{t.tm_sec}</second>
</time>'''

def localtime(environ, start_response):
    start_response('200 OK', [ ('Content-type', 'application/xml') ])
    resp = _localtime_resp.format(t=time.localtime())
    yield resp.encode('utf-8')

if __name__ == '__main__':
    from resty import PathDispatcher
    from wsgiref.simple_server import make_server
    dispatcher = PathDispatcher()
    dispatcher.register('GET', '/hello', hello_world)
    dispatcher.register('GET', '/localtime', localtime)
    httpd = make_server('', 8080, dispatcher)
    print('Serving on port 8080...')
    httpd.serve_forever()
5.2 测试服务器

可以通过浏览器或 urllib 来测试这个服务器:

from urllib.request import urlopen
u = urlopen('http://localhost:8080/hello?name=Guido')
print(u.read().decode('utf-8'))
u = urlopen('http://localhost:8080/localtime')
print(u.read().decode('utf-8'))
5.3 REST接口的应用与特点

REST接口通常用于处理普通的HTTP请求,与完整的网站不同,它们通常只是传递数据,数据可以编码为不同的格式,如XML、JSON或CSV。提供这样的API对于各种应用非常有用,例如长时间运行的程序可以使用REST API进行监控或诊断,大数据应用可以使用REST构建按需数据提取系统,甚至可以用于控制设备。实现简单的REST接口通常基于Python的WSGI标准,它得到了标准库和大多数第三方框架的支持,使代码更灵活。

6. 实现简单的远程过程调用(XML-RPC)
6.1 问题与解决方案

若需要在远程计算机上执行Python程序中的函数或方法,最简便的方法之一是使用XML-RPC。以下是一个简单的键值存储服务器示例:

from xmlrpc.server import SimpleXMLRPCServer

class KeyValueServer:
    _rpc_methods_ = ['get', 'set', 'delete', 'exists', 'keys']
    def __init__(self, address):
        self._data = {}
        self._serv = SimpleXMLRPCServer(address, allow_none=True)
        for name in self._rpc_methods_:
            self._serv.register_function(getattr(self, name))

    def get(self, name):
        return self._data[name]

    def set(self, name, value):
        self._data[name] = value

    def delete(self, name):
        del self._data[name]

    def exists(self, name):
        return name in self._data

    def keys(self):
        return list(self._data)

    def serve_forever(self):
        self._serv.serve_forever()

if __name__ == '__main__':
    kvserv = KeyValueServer(('', 15000))
    kvserv.serve_forever()
6.2 客户端调用

可以在客户端远程调用服务器的方法:

from xmlrpc.client import ServerProxy
s = ServerProxy('http://localhost:15000', allow_none=True)
s.set('foo', 'bar')
s.set('spam', [1, 2, 3])
print(s.keys())
print(s.get('foo'))
s.delete('spam')
print(s.exists('spam'))
6.3 XML-RPC的特点与局限性

XML-RPC是实现远程过程调用服务的一种简单方法,只需创建服务器实例、注册函数并启动服务器即可。但它有一些局限性,例如只支持部分数据类型,性能相对较低, SimpleXMLRPCServer 是单线程的,不适合大型应用。不过,由于XML-RPC将数据序列化为XML,很多编程语言都能理解这种编码方式,因此非Python客户端也能调用服务。

7. 简单的解释器间交互
7.1 问题与解决方案

若要在多个Python解释器实例(可能在不同计算机上)之间通过消息交换数据,可以使用 multiprocessing.connection 模块。以下是一个简单的回声服务器示例:

from multiprocessing.connection import Listener
import traceback

def echo_client(conn):
    try:
        while True:
            msg = conn.recv()
            conn.send(msg)
    except EOFError:
        print('Connection closed')

def echo_server(address, authkey):
    serv = Listener(address, authkey=authkey)
    while True:
        try:
            client = serv.accept()
            echo_client(client)
        except Exception:
            traceback.print_exc()

echo_server(('', 25000), authkey=b'peekaboo')
7.2 客户端连接与消息发送

客户端可以连接到服务器并发送消息:

from multiprocessing.connection import Client
c = Client(('localhost', 25000), authkey=b'peekaboo')
c.send('hello')
print(c.recv())
c.send(42)
print(c.recv())
c.send([1, 2, 3, 4, 5])
print(c.recv())
7.3 注意事项

与底层套接字不同,使用 multiprocessing.connection 模块发送的消息保持不变,对象通过 pickle 进行序列化,因此任何与 pickle 兼容的对象都可以通过连接发送或接收。但不建议使用 multiprocessing 实现公共服务, authkey 参数有助于验证连接的端点,该模块更适合长时间连接。

8. 实现远程过程调用(RPC)
8.1 问题与解决方案

若要在消息传输层(如套接字、 multiprocessing 连接或ZeroMQ)上实现简单的远程过程调用(RPC),可以使用 pickle 对请求、参数和函数返回值进行打包,并在解释器之间传输打包后的字节字符串。以下是一个简单的RPC处理程序示例:

# rpcserver.py
import pickle

class RPCHandler:
    def __init__(self):
        self._functions = { }

    def register_function(self, func):
        self._functions[func.__name__] = func

    def handle_connection(self, connection):
        try:
            while True:
                func_name, args, kwargs = pickle.loads(connection.recv())
                try:
                    r = self._functions[func_name](*args,**kwargs)
                    connection.send(pickle.dumps(r))
                except Exception as e:
                    connection.send(pickle.dumps(e))
        except EOFError:
            pass

将这个处理程序添加到消息服务器中,以下是一个RPC服务器示例:

from multiprocessing.connection import Listener
from threading import Thread

def rpc_server(handler, address, authkey):
    sock = Listener(address, authkey=authkey)
    while True:
        client = sock.accept()
        t = Thread(target=handler.handle_connection, args=(client,))
        t.daemon = True
        t.start()

def add(x, y):
    return x + y

def sub(x, y):
    return x - y

handler = RPCHandler()
handler.register_function(add)
handler.register_function(sub)

rpc_server(handler, ('localhost', 17000), authkey=b'peekaboo')

客户端需要创建一个对应的RPC代理类来转发请求:

import pickle

class RPCProxy:
    def __init__(self, connection):
        self._connection = connection

    def __getattr__(self, name):
        def do_rpc(*args, **kwargs):
            self._connection.send(pickle.dumps((name, args, kwargs)))
            result = pickle.loads(self._connection.recv())
            if isinstance(result, Exception):
                raise result
            return result
        return do_rpc

使用代理类调用远程函数:

from multiprocessing.connection import Client
c = Client(('localhost', 17000), authkey=b'peekaboo')
proxy = RPCProxy(c)
print(proxy.add(2, 3))
print(proxy.sub(2, 3))
8.2 注意事项

由于使用了 pickle ,安全性存在风险,不应该允许来自不可信或未认证客户端的RPC请求,且RPC应在防火墙内的网络中使用。也可以尝试使用JSON、XML或其他数据编码方式进行序列化,例如将 pickle.loads() pickle.dumps() 替换为 json.loads() json.dumps()

9. 简单的客户端认证
9.1 问题与解决方案

若要在分布式系统中实现客户端与服务器连接的简单认证,且不需要像SSL那样复杂的解决方案,可以使用 hmac 模块进行握手认证。以下是示例代码:

import hmac
import os

def client_authenticate(connection, secret_key):
    message = connection.recv(32)
    hash = hmac.new(secret_key, message)
    digest = hash.digest()
    connection.send(digest)

def server_authenticate(connection, secret_key):
    message = os.urandom(32)
    connection.send(message)
    hash = hmac.new(secret_key, message)
    digest = hash.digest()
    response = connection.recv(len(digest))
    return hmac.compare_digest(digest,response)

将这些函数集成到现有的网络代码或消息交换系统中,以下是一个服务器端示例:

from socket import socket, AF_INET, SOCK_STREAM

secret_key = b'peekaboo'
def echo_handler(client_sock):
    if not server_authenticate(client_sock, secret_key):
        client_sock.close()
        return
    while True:
        msg = client_sock.recv(8192)
        if not msg:
            break
        client_sock.sendall(msg)

def echo_server(address):
    s = socket(AF_INET, SOCK_STREAM)
    s.bind(address)
    s.listen(5)
    while True:
        c,a = s.accept()
        echo_handler(c)

echo_server(('', 18000))

客户端代码如下:

from socket import socket, AF_INET, SOCK_STREAM

secret_key = b'peekaboo'
s = socket(AF_INET, SOCK_STREAM)
s.connect(('localhost', 18000))
client_authenticate(s, secret_key)
s.send(b'Hello World')
resp = s.recv(1024)
9.2 认证原理与注意事项

认证的基本原理是服务器在建立连接前向客户端发送随机字节消息,客户端和服务器使用 hmac 和共享的密钥计算这些随机数据的加密哈希值,客户端将计算得到的摘要发送回服务器进行比较,以决定是否接受连接。比较摘要时应使用 hmac.compare_digest() 函数,以防止基于时间分析的攻击。需要注意的是,这种认证方式并不等同于加密,后续通信仍可能被监听。

10. 添加SSL到网络服务
10.1 问题与解决方案

若要实现一个使用套接字的网络服务,使服务器和客户端通过SSL相互认证并加密传输数据,可以使用 ssl 模块。以下是一个简单的回声服务器示例:

from socket import socket, AF_INET, SOCK_STREAM
import ssl

KEYFILE = 'server_key.pem'
CERTFILE = 'server_cert.pem'

def echo_client(s):
    while True:
        data = s.recv(8192)
        if data == b'':
            break
        s.send(data)
    s.close()
    print('Connection closed')

def echo_server(address):
    s = socket(AF_INET, SOCK_STREAM)
    s.bind(address)
    s.listen(1)
    s_ssl = ssl.wrap_socket(s, keyfile=KEYFILE, certfile=CERTFILE, server_side=True)
    while True:
        try:
            c,a = s_ssl.accept()
            print('Got connection', c, a)
            echo_client(c)
        except Exception as e:
            print('{}: {}'.format(e.__class__.__name__, e))

echo_server(('', 20000))

客户端连接服务器的示例:

from socket import socket, AF_INET, SOCK_STREAM
import ssl
s = socket(AF_INET, SOCK_STREAM)
s_ssl = ssl.wrap_socket(s, cert_reqs=ssl.CERT_REQUIRED, ca_certs = 'server_cert.pem')
s_ssl.connect(('localhost', 20000))
s_ssl.send(b'Hello World?')
print(s_ssl.recv(8192))
10.2 为现有服务添加SSL

对于现有的基于 socketserver 的网络服务,可以使用混合类添加SSL支持。以下是一个XML-RPC服务器的示例:

import ssl
from xmlrpc.server import SimpleXMLRPCServer

class SSLMixin:
    def __init__(self, *args, keyfile=None, certfile=None, ca_certs=None, cert_reqs=ssl.NONE, **kwargs):
        self._keyfile = keyfile
        self._certfile = certfile
        self._ca_certs = ca_certs
        self._cert_reqs = cert_reqs
        super().__init__(*args, **kwargs)

    def get_request(self):
        client, addr = super().get_request()
        client_ssl = ssl.wrap_socket(client, keyfile = self._keyfile, certfile = self._certfile, ca_certs = self._ca_certs, cert_reqs = self._cert_reqs, server_side = True)
        return client_ssl, addr

class SSLSimpleXMLRPCServer(SSLMixin, SimpleXMLRPCServer):
    pass

class KeyValueServer:
    _rpc_methods_ = ['get', 'set', 'delete', 'exists', 'keys']
    def __init__(self, *args, **kwargs):
        self._data = {}
        self._serv = SSLSimpleXMLRPCServer(*args, allow_none=True, **kwargs)
        for name in self._rpc_methods_:
            self._serv.register_function(getattr(self, name))

    def get(self, name):
        return self._data[name]

    def set(self, name, value):
        self._data[name] = value

    def delete(self, name):
        del self._data[name]

    def exists(self, name):
        return name in self._data

    def keys(self):
        return list(self._data)

    def serve_forever(self):
        self._serv.serve_forever()

if __name__ == '__main__':
    KEYFILE='server_key.pem'
    CERTFILE='server_cert.pem'
    kvserv = KeyValueServer(('', 15000), keyfile=KEYFILE, certfile=CERTFILE)
    kvserv.serve_forever()

客户端可以使用 xmlrpc.client 模块连接服务器:

from xmlrpc.client import ServerProxy
s = ServerProxy('https://localhost:15000', allow_none=True)
s.set('foo', 'bar')
s.set('spam', [1, 2, 3])
print(s.keys())
print(s.get('foo'))
s.delete('spam')
print(s.exists('spam'))
10.3 SSL客户端验证

若服务器需要验证客户端,可以在客户端创建代理时提供客户端证书和密钥:

from xmlrpc.client import SafeTransport, ServerProxy
import ssl

class VerifyCertSafeTransport(SafeTransport):
    def __init__(self, cafile, certfile=None, keyfile=None):
        SafeTransport.__init__(self)
        self._ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
        self._ssl_context.load_verify_locations(cafile)
        if certfile:
            self._ssl_context.load_cert_chain(certfile, keyfile)
        self._ssl_context.verify_mode = ssl.CERT_REQUIRED

    def make_connection(self, host):
        s = super().make_connection((host, {'context': self._ssl_context}))
        return s

s = ServerProxy('https://localhost:15000', transport=VerifyCertSafeTransport('server_cert.pem', 'client_cert.pem', 'client_key.pem'), allow_none=True)
10.4 SSL配置注意事项

实现SSL服务需要进行初始的密钥、证书等组件的配置。每个SSL连接的端点通常有一个私钥和一个签名的证书文件,证书文件包含公钥,在每次连接时提供给远程对等方。对于公共服务器,证书通常由证书颁发机构签名,但为了实验目的,可以创建自签名证书。创建自签名证书的示例命令如下:

bash % openssl req -new -x509 -days 365 -nodes -out server_cert.pem -keyout server_key.pem
11. 进程间传递套接字文件描述符
11.1 问题与解决方案

若有多个Python解释器进程,想将一个解释器中的打开文件描述符传递给另一个解释器,可以使用 multiprocessing 模块。以下是一个示例:

import multiprocessing
from multiprocessing.reduction import recv_handle, send_handle
import socket

def worker(in_p, out_p):
    out_p.close()
    while True:
        fd = recv_handle(in_p)
        print('CHILD: GOT FD', fd)
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM, fileno=fd) as s:
            while True:
                msg = s.recv(1024)
                if not msg:
                    break
                print('CHILD: RECV {!r}'.format(msg))
                s.send(msg)

def server(address, in_p, out_p, worker_pid):
    in_p.close()
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    s.bind(address)
    s.listen(1)
    while True:
        client, addr = s.accept()
        print('SERVER: Got connection from', addr)
        send_handle(out_p, client.fileno(), worker_pid)
        client.close()

if __name__ == '__main__':
    c1, c2 = multiprocessing.Pipe()
    worker_p = multiprocessing.Process(target=worker, args=(c1,c2))
    worker_p.start()

    server_p = multiprocessing.Process(target=server, args=(('', 15000), c1, c2, worker_p.pid))
    server_p.start()

    c1.close()
    c2.close()
11.2 注意事项

这种传递文件描述符的方式可以用于构建可扩展的系统,例如在多核计算机上平衡不同解释器处理的客户端数量。但 send_handle() recv_handle() 函数仅适用于多进程连接,也可以使用其他方式连接解释器,只要使用Unix域套接字或Windows命名管道即可。

总结

本文详细介绍了网络编程和Web编程中的多个重要主题,包括TCP和UDP服务器的创建、IP地址范围的生成、REST接口的实现、远程过程调用、客户端认证、SSL添加以及进程间文件描述符的传递等。通过这些技术和方法,可以实现各种复杂的网络应用和分布式系统。在实际应用中,需要根据具体需求选择合适的技术,并注意安全性和性能等方面的问题。

流程图示例

graph TD;
    A[开始] --> B[创建TCP服务器];
    B --> C[等待客户端连接];
    C --> D{是否有客户端连接};
    D -- 是 --> E[处理客户端请求];
    E --> C;
    D -- 否 --> C;

表格示例

技术 优点 缺点
XML-RPC 简单易用,多语言支持 性能低,单线程
REST接口 灵活,数据传递方便 需遵循WSGI标准
基于 multiprocessing 的通信 消息不变,对象序列化方便 不适合公共服务

网络编程与Web编程技术详解(续)

12. 不同网络编程技术对比

为了更清晰地了解各种网络编程技术的特点,下面通过表格进行对比:
| 技术类型 | 优点 | 缺点 | 适用场景 |
| — | — | — | — |
| TCP服务器(socketserver) | 实现简单,可处理多客户端(通过多线程或多进程) | 单线程时只能处理一个客户端;多线程/进程有资源风险 | 对连接可靠性要求高,如文件传输、数据库连接等 |
| UDP服务器(socketserver) | 实现简单,无需建立连接 | 消息不可靠,可能丢失 | 对实时性要求高,对可靠性要求相对低,如视频流、音频流传输等 |
| REST接口 | 灵活,数据传递方便,支持多种数据格式,与现有框架兼容性好 | 需要遵循WSGI标准 | 构建API供不同客户端调用,如移动应用、网页应用的数据交互 |
| XML - RPC | 简单易用,多语言支持 | 性能低,单线程,支持数据类型有限 | 内部网络的分布式程序,对性能要求不高 |
| 基于 multiprocessing 的通信 | 消息不变,对象序列化方便 | 不适合公共服务 | 同一计算机上多个Python解释器之间的数据交换 |
| SSL加密通信 | 提供认证和加密,保障数据安全 | 配置复杂,性能有一定损耗 | 涉及敏感信息传输,如金融交易、用户登录等 |

13. 网络编程技术的综合应用示例

假设我们要构建一个简单的分布式文件存储系统,结合前面介绍的多种网络编程技术,以下是一个大致的实现思路和代码示例。

13.1 系统架构
  • 一个主服务器负责接收客户端的文件上传和下载请求,并将请求分发给多个存储节点。
  • 多个存储节点负责实际的文件存储和读取。
  • 客户端通过REST接口与主服务器进行交互。
13.2 主服务器代码(REST接口实现)
# main_server.py
import cgi
from wsgiref.simple_server import make_server

def notfound_404(environ, start_response):
    start_response('404 Not Found', [('Content - type', 'text/plain')])
    return [b'Not Found']

class PathDispatcher:
    def __init__(self):
        self.pathmap = {}

    def __call__(self, environ, start_response):
        path = environ['PATH_INFO']
        params = cgi.FieldStorage(environ['wsgi.input'], environ=environ)
        method = environ['REQUEST_METHOD'].lower()
        environ['params'] = {key: params.getvalue(key) for key in params}
        handler = self.pathmap.get((method, path), notfound_404)
        return handler(environ, start_response)

    def register(self, method, path, function):
        self.pathmap[method.lower(), path] = function
        return function

# 模拟存储节点列表
storage_nodes = [('node1.example.com', 8001), ('node2.example.com', 8002)]

def upload_file(environ, start_response):
    start_response('200 OK', [('Content - type', 'text/plain')])
    # 选择一个存储节点
    node = storage_nodes[0]
    # 这里可以添加将文件转发到存储节点的代码
    return [b'File uploaded successfully']

def download_file(environ, start_response):
    start_response('200 OK', [('Content - type', 'text/plain')])
    # 选择一个存储节点
    node = storage_nodes[0]
    # 这里可以添加从存储节点获取文件的代码
    return [b'File downloaded successfully']

if __name__ == '__main__':
    dispatcher = PathDispatcher()
    dispatcher.register('POST', '/upload', upload_file)
    dispatcher.register('GET', '/download', download_file)
    httpd = make_server('', 8080, dispatcher)
    print('Serving on port 8080...')
    httpd.serve_forever()
13.3 存储节点代码(TCP服务器实现)
# storage_node.py
from socketserver import BaseRequestHandler, TCPServer

class FileHandler(BaseRequestHandler):
    def handle(self):
        print('Got connection from', self.client_address)
        while True:
            msg = self.request.recv(8192)
            if not msg:
                break
            # 这里可以添加文件存储和读取的逻辑
            self.request.send(b'File operation completed')

if __name__ == '__main__':
    serv = TCPServer(('', 8001), FileHandler)
    serv.serve_forever()
13.4 客户端代码
# client.py
import requests

# 上传文件
response = requests.post('http://localhost:8080/upload')
print(response.text)

# 下载文件
response = requests.get('http://localhost:8080/download')
print(response.text)
14. 网络编程中的性能优化

在网络编程中,性能是一个重要的考虑因素。以下是一些常见的性能优化方法:

14.1 多线程/多进程优化
  • 对于TCP和UDP服务器,使用多线程或多进程可以提高并发处理能力。例如,在 socketserver 中使用 ThreadingTCPServer ThreadingUDPServer
from socketserver import ThreadingTCPServer

class EchoHandler(BaseRequestHandler):
    def handle(self):
        print('Got connection from', self.client_address)
        while True:
            msg = self.request.recv(8192)
            if not msg:
                break
            self.request.send(msg)

if __name__ == '__main__':
    serv = ThreadingTCPServer(('', 20000), EchoHandler)
    serv.serve_forever()
14.2 异步编程

使用异步编程模型可以在不创建大量线程或进程的情况下处理多个客户端连接。Python中的 asyncio 库可以实现异步网络编程。

import asyncio

async def handle_client(reader, writer):
    while True:
        data = await reader.read(8192)
        if not data:
            break
        writer.write(data)
        await writer.drain()
    writer.close()

async def main():
    server = await asyncio.start_server(handle_client, '127.0.0.1', 20000)
    async with server:
        await server.serve_forever()

asyncio.run(main())
14.3 缓存机制

在REST接口或远程过程调用中,使用缓存可以减少重复计算和数据传输。例如,使用Python的 functools.lru_cache 装饰器对函数结果进行缓存。

import functools

@functools.lru_cache(maxsize=128)
def expensive_function(arg):
    # 模拟耗时操作
    import time
    time.sleep(1)
    return arg * 2
15. 网络编程中的安全问题及解决方法

网络编程中安全问题至关重要,以下是一些常见的安全问题及解决方法:

15.1 客户端认证

使用 hmac 模块进行简单的客户端认证,防止非法客户端连接。

import hmac
import os

def client_authenticate(connection, secret_key):
    message = connection.recv(32)
    hash = hmac.new(secret_key, message)
    digest = hash.digest()
    connection.send(digest)

def server_authenticate(connection, secret_key):
    message = os.urandom(32)
    connection.send(message)
    hash = hmac.new(secret_key, message)
    digest = hash.digest()
    response = connection.recv(len(digest))
    return hmac.compare_digest(digest, response)
15.2 数据加密

使用SSL/TLS对数据进行加密传输,防止数据在传输过程中被窃取或篡改。

from socket import socket, AF_INET, SOCK_STREAM
import ssl

KEYFILE = 'server_key.pem'
CERTFILE = 'server_cert.pem'

def echo_server(address):
    s = socket(AF_INET, SOCK_STREAM)
    s.bind(address)
    s.listen(1)
    s_ssl = ssl.wrap_socket(s, keyfile=KEYFILE, certfile=CERTFILE, server_side=True)
    while True:
        try:
            c, a = s_ssl.accept()
            print('Got connection', c, a)
            while True:
                data = c.recv(8192)
                if data == b'':
                    break
                c.send(data)
            c.close()
        except Exception as e:
            print('{}: {}'.format(e.__class__.__name__, e))

if __name__ == '__main__':
    echo_server(('', 20000))
15.3 防止恶意攻击
  • 限制客户端连接数量,避免服务器被大量连接耗尽资源。
  • 对用户输入进行严格验证,防止SQL注入、XSS攻击等。
16. 未来网络编程趋势

随着技术的不断发展,网络编程也呈现出一些新的趋势:

16.1 微服务架构

将大型应用拆分成多个小型、自治的服务,通过REST接口进行通信。这种架构提高了系统的可扩展性和维护性。

16.2 容器化和编排

使用Docker等容器技术将应用打包成独立的容器,使用Kubernetes等编排工具进行容器的管理和调度,提高应用的部署效率和可靠性。

16.3 人工智能与网络编程的结合

利用人工智能技术对网络流量进行分析和预测,实现智能的网络优化和安全防护。

流程图示例

graph TD;
    A[客户端] --> B[REST接口请求];
    B --> C[主服务器];
    C --> D{选择存储节点};
    D --> E[存储节点];
    E --> F[文件操作];
    F --> G[返回结果];
    G --> C;
    C --> H[返回结果给客户端];

表格示例

性能优化方法 原理 适用场景
多线程/多进程 利用多个线程或进程同时处理多个客户端连接 高并发场景,对响应时间要求高
异步编程 利用异步I/O模型,在等待I/O操作时不阻塞线程 大量I/O操作的场景,如网络爬虫、文件读写等
缓存机制 缓存函数结果,避免重复计算 函数计算成本高,且输入相同结果相同的场景
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值