Python C/S 网络编程(四)之网络数据与网络错误

一、字符与字节

Python 中有两种表示字节的方法:

第一种是使用一个介于0~255之间的整数;
第二种是使用一个字节字符串(字符串的唯一内容就是该字节本身)。

字节字符串的实现通过bytes()实现:
在这里插入图片描述

首先说说最流行的ASCII编码方式:ASCII定义了0~127的字符代码,对应7位二进制位,所以最高位永远是0。

Python常用的时Unicode的编码方式,所以在Python中:
(1)encodeing:对字符进行编码,即将真正的Unicode字符串转化为字节字符串
(2)decodeing:对字节数据进行解码,即将字节字符串转化为真正的Unicode字符。

世界上的编码方式大致可以分为两类:

(1)单字节编码(最简单):最多可表示256个独立的字符,不过可以保证每个字符都能唯一映射到一个单独的字节。
(2)多字节编码:一些多字节编码会使用固定的字节数表示一个字符,比如UTF-32。优点是表示每个字符的字节数都是相同的。目前最常用的是UTF-8,和ASCII码差不多,唯一区别是,它包含的字符都是从大于127的代码开始的。

二、封帧与引用

  • 如果使用UDP数据报进行通信,那么协议本身就会使用独立的、可识别的模块进行数据传输。
  • 如果使用TCP进行通信,就要面对封帧问题。即如何分割消息,让接受方可以识别消息的开始与结束。recv()就必须运行多次才能收到完整的数据包。

关于封帧的六种模式:

  • 模式一:只涉及数据的发送,不关注响应。

发送方循环发送数据,直到所有数据都被传递给sendall()为止,然后使用close()关闭套接字。接收方只需要不断调用recv(),直到recv()最后返回一个空字符串(表示发送方已经关闭了套接字)为止。

#!/usr/bin/env python3
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter05/streamer.py
# Client that sends data then closes the socket, not expecting a reply.

import socket
from argparse import ArgumentParser

def server(address):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(address)
    sock.listen(1)
    print('Run this script in another window with "-c" to connect')
    print('Listening at', sock.getsockname())
    sc, sockname = sock.accept()
    print('Accepted connection from', sockname)
    sc.shutdown(socket.SHUT_WR)        #单向关闭写入
    message = b''
    while True:
        more = sc.recv(8192)  # arbitrary value of 8k
        if not more:  # socket has closed when recv() returns ''
            print('Received zero bytes - end of file')
            break
        print('Received {} bytes'.format(len(more)))
        message += more
    print('Message:\n')
    print(message.decode('ascii'))
    sc.close()
    sock.close()

def client(address):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(address)
    sock.shutdown(socket.SHUT_RD)        #单向关闭读取
    sock.sendall(b'Beautiful is better than ugly.\n')
    sock.sendall(b'Explicit is better than implicit.\n')
    sock.sendall(b'Simple is better than complex.\n')
    sock.close()

if __name__ == '__main__':
    parser = ArgumentParser(description='Transmit & receive a data stream')
    parser.add_argument('hostname', nargs='?', default='127.0.0.1',
                        help='IP address or hostname (default: %(default)s)')
    parser.add_argument('-c', action='store_true', help='run as the client')
    parser.add_argument('-p', type=int, metavar='port', default=1060,
                        help='TCP port number (default: %(default)s)')
    args = parser.parse_args()
    function = client if args.c else server
    function((args.hostname, args.p))

结果:
在这里插入图片描述
在这里插入图片描述

由于这个套接字并没有准备接受任何数据,因此,但C和S不再进行某一方向的通信时会立即关闭该方向的连接。这一做法避免了在另一方向上使用套接字。

  • 模式二:在两个方向上都通过流发送信息。

套接字最开始在两个方向上都是打开的。首先通过流在一个方向上发送信息,然后关闭该方向。接着在另一方向上通过流发送信息,最后关闭套接字。

注:一定要先完成一个方向上的数据传输,然后再反过来再另一个方向上通过流发送数据,否则容易死锁。

  • 模式三:使用定长消息

可以使用sendall()发送字节字符串,然后自己设计一个recvall()循环确保接收完整的消息。

def recvall(sock, length):
	data = ''
	while len(data) < length:
		more = sock.recv(length - len(data))
		if not more:
			raise EOFError('socket closed {} bytes into a {}-byte'
			               ' message'.format(len(data), length))
		data += more
return data
  • 模式四:使用特殊字符来划分消息的边界。

比方遇到 ‘.’ 判定消息发送结束。

  • 模式五:在每个消息前加上其长度作为前缀。

只要接收方读取并解码了长度,就能进入循环,重复调用recv(),知道整个消息都传达为止。

  • 模式六(模式五的拓展):因为实际上我们并非发送单个消息,而是多个数据块。所以,在每个数据块前加上数据块长度作为其前缀

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值