一步一步学习和开发 Python 异步 Socket 程序(二)

第一步:分离服务端的发送功能

        根据前面所述的需求,这里的 Socket 程序模块实现的不是直接返回,而是在客户端和内部的处理程序之间进行数据的转发,因而就不能直接在接收循环中收到数据以后直接发回去,而是要送内部程序处理以后再发回客户端。为此,首先需要将发送程序从服务端的接收函数中分离开来,这是我们做的第一步修改。
        这一步的修改不需改动客户端代码,相应的服务端代码改为:

import asyncio


async def handle_echo(reader, writer):
    data = await reader.read(100)
    message = data.decode()
    addr = writer.get_extra_info('peername')

    print(f"Received {message!r} from {addr!r}")

    print(f"Send: {message!r}")
    # writer.write(data)

    await send_message(writer, data)
    print("Close the connection")
    writer.close()


async def send_message(writer, message):
    writer.write(message)
    await writer.drain()


async def main():
    server = await asyncio.start_server(
        handle_echo, '127.0.0.1', 8888)

    addr = server.sockets[0].getsockname()
    print(f'Serving on {addr}')

    async with server:
        await server.serve_forever()


asyncio.run(main())

        这里,增加了一个send_message异步函数,将handle_message中的 writer.write(data) 和 await writer.drain()两句移到其中,完成向客户端发送消息的功能。与此同时,在 handle_echo 函数中相应的位置改为调用send_message 函数,传入的参数分别是 writer 句柄和收到的消息。修改以后,程序的功能和前面官方的例子完全相同,服务端和客户端的运行输出也与官方例子的输出相同。

第二步:修改客户端和服务端,持续发送和接收消息

        为了测试服务端多客户端连接和收发功能,修改了客户端和服务端程序。
       其中客户端程序改变和增加了以下功能:
        1. 客户端无法连接服务端时(服务端尚未启动),每隔两秒钟重试连接;
        2. 连接成功以后,进入死循环,重复发送操作,不再关闭连接,重复间隔为两秒钟,以持续测试多个客户端连接和收发情况。
        修改后的客户端程序如下:

import asyncio
import time

connected = False
reader = None
writer = None


async def connect():
    global connected, reader, writer
    while not connected:
        print('try to connect to server: 127.0.0.1:8888')
        try:
            reader, writer = await asyncio.open_connection('127.0.0.1', 8888)
            connected = True
        except Exception as e:
            print(e)
            connected = False
        await asyncio.sleep(2)


async def tcp_echo_client(message):
    global connected, reader, writer
    await connect()
    n = 0
    while connected:
        print(f'Send: {message!r}')
        s_message = message + str(n)
        writer.write(s_message.encode())
        await writer.drain()

        data = await reader.read(100)
        print(f'Received: {data.decode()!r}')

        n += 1
        # print('Close the connection')
        # writer.close()
        await asyncio.sleep(2)


asyncio.run(tcp_echo_client(f'Hello World! '))

        服务端则进行了以下修改:
        1. 将 handle_echo 的读写操作改为循环进行;
        2. 收发后不再关闭连接。
        修改后的服务端程序如下:

import asyncio


async def handle_echo(reader, writer):
	# 循环接收,直至客户端推出
    while True:
        data = await reader.read(100)
        # 如果收到长度为0的消息,表示客户端已经断开
        if len(data) == 0:
	        addr = writer.get_extra_info('peername')
    	    print(f"client {addr!r} is closed")
            writer.close()
            break
        message = data.decode()
        addr = writer.get_extra_info('peername')

        print(f"Received {message!r} from {addr!r}")

        print(f"Send: {message!r}")
        # writer.write(data)

        await send_message(writer, data)
        await asyncio.sleep(0.1)
    # writer.close()


async def send_message(writer, message):
    writer.write(message)
    await writer.drain()


async def main():
    server = await asyncio.start_server(
        handle_echo, '127.0.0.1', 8888)

    addr = server.sockets[0].getsockname()
    print(f'Serving on {addr}')

    async with server:
        await server.serve_forever()


asyncio.run(main())

        修改以后,客户端和服务端均进入死循环进行数据收发。下面是客户端执行结果显示的片段,服务端与其类似,不再赘述。显示中,Hello World!后面的数字是客户端发送的循环次数。

Received: 'Hello World! 8606'
Send: 'Hello World! '
Received: 'Hello World! 8607'
Send: 'Hello World! '
Received: 'Hello World! 8608'
Send: 'Hello World! '
Received: 'Hello World! 8609'
Send: 'Hello World! '
Received: 'Hello World! 8610'
Send: 'Hello World! '
Received: 'Hello World! 8611'

        修改后的程序支持持续的多连接测试。为了进行这种测试,简单生成了三个同样的客户端程序并修改了最后一句中的打印内容,分别改为:“instance_01 : Hello World!”、“instance_02 …” 和 “instance_03 …”,如:

asyncio.run(tcp_echo_client(f'instance_02: Hello World! '))

        当然也可以将打印内容作为参数传递,用同一个程序生成三个进程执行。
        运行服务端程序和三个客户端程序,我们可以看出:服务端程序可以接收来自三个客户端的数据,并按照来路返回客户端;客户端可以收到自己发送的数据,不会错收。
       下面是服务端执行的情况(片段),从中可以看到其收到来自(instance_00 - instance_02)三个客户端的数据消息。

Received 'instance_01: Hello World ! 3030' from ('127.0.0.1', 50811)
Send: 'instance_01: Hello World ! 3030'
Received 'instance_02: Hello World! 3016' from ('127.0.0.1', 50812)
Send: 'instance_02: Hello World! 3016'
Received 'instance_00: Hello World! 3053' from ('127.0.0.1', 50807)
Send: 'instance_00: Hello World! 3053'
Received 'instance_02: Hello World! 3017' from ('127.0.0.1', 50812)
Send: 'instance_02: Hello World! 3017'
Received 'instance_01: Hello World ! 3031' from ('127.0.0.1', 50811)
Send: 'instance_01: Hello World ! 3031'

       下面是instance_01的客户端执行情况片段,从中可以看出,它只收到自己发送到服务端的数据消息:

end: 'instance_01: Hello World ! '
Received: 'instance_01: Hello World ! 3134'
Send: 'instance_01: Hello World ! '
Received: 'instance_01: Hello World ! 3135'
Send: 'instance_01: Hello World ! '
Received: 'instance_01: Hello World ! 3136'
Send: 'instance_01: Hello World ! '

       接下来的将描述程序的修改步骤,用以实现异步socket程序和内部处理程序的交互,最终完成能够满足本文(一)中所述需求的原型程序。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值