基于socket套接字的网络通讯

Socket工作原理

  Socket是一个抽象层:

先来理解什么是Socket:

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

也有人将socket说成ip+port,ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,而port是应用程序开启的,ip与port的绑定就标识了互联网中独一无二的一个应用程序

而程序的pid是同一台机器上不同进程或者线程的标识
扫盲

套接字的工作流程

一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理。

先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束

 简单的套接字通讯

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.bind(('127.0.0.1',6609))          ##将服务端绑定到本机IP和固定端口,#ip应该是服务端这个软件运行那台机器的ip地址,port(0-65535)
phone.listen(5)                         ##半连接池:控制的是同一时刻的链接请求数
connect,ip_addr = phone.accept()        ##(connect=套接字对象,ip_addr=存放有客户端的ip和端口的元组)

data = connect.recv(1024)               ##recv表示接收客户端发送的消息,1024单位是bytes,代表最大接收1024bytes
print('收到客户端的信息: ',data)         ##打印客户端发送的信息
connect.send(data.upper())              ##将客户端发来的信息转为大写发送给客户端

connect.close()                         ##关闭本次通讯
phone.close()                           ##关闭本socket
服务端
import socket           ##导入socket模块
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.connect(('127.0.0.1',6609))             ##链接服务器IP和端口
user_inp = input('>>> ').strip()
phone.send(user_inp.encode('utf-8'))            ##发送用户输入的信息
data = phone.recv(1024)                         ##接收服务端返回信息,1024代表本次最大接收1024bytes内容
print('收到服务端信息: ',data)                 ##打印接收到的内容
客户端

弊端:接收到一个客户端的指令答复后程序结束,之间的相互聊天通讯并不能实现循环

用户套接字通讯循环

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.bind(('127.0.0.1',6609))          ##将服务端绑定到本机IP和固定端口,#ip应该是服务端这个软件运行那台机器的ip地址,port(0-65535)
phone.listen(5)                         ##半连接池:控制的是同一时刻的链接请求数
connect,ip_addr = phone.accept()        ##(connect=套接字对象,ip_addr=存放有客户端的ip和端口的元组)
while True:
    data = connect.recv(1024)               ##recv表示接收客户端发送的消息,1024单位是bytes,代表最大接收1024bytes
    print('收到客户端的信息: ',data)         ##打印客户端发送的信息
    connect.send(data.upper())              ##将客户端发来的信息转为大写发送给客户端

connect.close()                         ##关闭本次通讯
phone.close()                           ##关闭本socket
通讯循环-服务端
import socket           ##导入socket模块
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.connect(('127.0.0.1',6609))             ##链接服务器IP和端口
while True:
    user_inp = input('>>> ').strip()
    phone.send(user_inp.encode('utf-8'))  ##发送用户输入的信息
    data = phone.recv(1024)  ##接收服务端返回信息,1024代表本次最大接收1024bytes内容
    print('收到服务端信息: ', data)  ##打印接收到的内容
通讯循环-客户端

弊端:当客户端主动关闭或强行关闭后,服务端也会随机崩溃,不同的系统表现不一样

  Windows:  抛出ConnectionResetError的异常,服务端崩溃

                       

  Unix:  unix下一直无限接收打印空消息,但是服务端不会崩溃

      

针对上面的情况,对服务端添加异常捕捉,使其服务端不会崩溃和接收空消息

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.bind(('127.0.0.1',6609))          ##将服务端绑定到本机IP和固定端口,#ip应该是服务端这个软件运行那台机器的ip地址,port(0-65535)
phone.listen(5)                         ##半连接池:控制的是同一时刻的链接请求数
connect,ip_addr = phone.accept()        ##(connect=套接字对象,ip_addr=存放有客户端的ip和端口的元组)
while True:
    try:
        data = connect.recv(1024)               ##recv表示接收客户端发送的消息,1024单位是bytes,代表最大接收1024bytes
        if len(data) == 0:break                 ##针对linux或者mac
        print('收到客户端的信息: ',data)         ##打印客户端发送的信息
        connect.send(data.upper())              ##将客户端发来的信息转为大写发送给客户端
    except ConnectionResetError:                ##针对Windows下的优化
        break

connect.close()                         ##关闭本次通讯
phone.close()                           ##关闭本socket
ConnectionResetError和空消息解决

解决了上面的问题,发现还是有问题:

  1、不能同时给多个客户端提供服务

  2、客户端终止服务端依然会终止

实现连接循环+通讯循环

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.bind(('127.0.0.1',6609))          ##将服务端绑定到本机IP和固定端口,#ip应该是服务端这个软件运行那台机器的ip地址,port(0-65535)
phone.listen(5)                         ##半连接池:控制的是同一时刻的链接请求数
while True:
    connect,ip_addr = phone.accept()        ##(connect=套接字对象,ip_addr=存放有客户端的ip和端口的元组)
    while True:
        try:
            data = connect.recv(1024)               ##recv表示接收客户端发送的消息,1024单位是bytes,代表最大接收1024bytes
            if len(data) == 0:break                 ##针对linux或者mac
            print('收到客户端的信息: ',data)         ##打印客户端发送的信息
            connect.send(data.upper())              ##将客户端发来的信息转为大写发送给客户端
        except ConnectionResetError:                ##针对Windows下的优化
            break

    connect.close()                         ##关闭本次通讯
phone.close()                           ##关闭本socket
连接循环+通讯循环服务端
import socket           ##导入socket模块
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.connect(('127.0.0.1',6609))             ##链接服务器IP和端口
while True:
    user_inp = input('>>> ').strip()
    phone.send(user_inp.encode('utf-8'))  ##发送用户输入的信息
    data = phone.recv(1024)  ##接收服务端返回信息,1024代表本次最大接收1024bytes内容
    print('收到服务端信息: ', data)  ##打印接收到的内容
连接循环+通讯循环客户端1
import socket           ##导入socket模块
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.connect(('127.0.0.1',6609))             ##链接服务器IP和端口
while True:
    user_inp = input('>>> ').strip()
    phone.send(user_inp.encode('utf-8'))  ##发送用户输入的信息
    data = phone.recv(1024)  ##接收服务端返回信息,1024代表本次最大接收1024bytes内容
    print('收到服务端信息: ', data)  ##打印接收到的内容
连接循环+通讯循环客户端2

测试方法:服务端开启后,分别运行客户单1和客户端2,实现服务端先对

       客户端1发送信息立刻提供响应,客户端2发送信息没有响应阻塞在原地,当客户端1关闭后客户端2立刻被响应,如果请求客户端sync阻塞数目大于半连接池,Windows将会被直接拒绝,而Linux并不会被拒绝

为什么客户端2不能及时响应呢?

    

    我们很清晰的看到代码里面有两个While true,phone.accept()负责接收客户端的建立连接请求,而第二个后面实现了通讯循环,也就是说当第一个客户端接入的时候不出意外的话

    会直接进入到第二个while true,进行通讯循环,而后续接入的客户端全部被放入了半连接池,全部阻塞在phone.accept()这里,只有当第一个客户端结束了程序才有可能触发break

    从而回过头将客户端2的请求,从半链接池中提出出来,进入第二个while true来处理第二个客户端的请求,另外我的listen(5)半连接池设置为5,表示如果同时有7个客户端访问服务端,

    那么服务端将client1提供正常响应,client2-6这五个请求放入半连接池,一直阻塞,而client7则直接被拒绝,不能提供服务,当client1结束,client2则立刻被响应,client2结束,client3

    立刻被响应

结论:  

  链接循环+通讯循环,客户端主动关闭,服务端也不会关闭

  这种同时服务由于局限于单进程,并不能实现真正意义上的同时响应和服务,关于这个问题以后再去完善它   

  

制作一个简易的SSH命令执行工具

import socket
import subprocess

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.bind(('127.0.0.1',6609))          ##将服务端绑定到本机IP和固定端口,#ip应该是服务端这个软件运行那台机器的ip地址,port(0-65535)
phone.listen(5)                         ##半连接池:控制的是同一时刻的链接请求数
while True:
    connect,ip_addr = phone.accept()        ##(connect=套接字对象,ip_addr=存放有客户端的ip和端口的元组)
    while True:
        try:
            data = connect.recv(1024)               ##recv表示接收客户端发送的消息,1024单位是bytes,代表最大接收1024bytes
            if len(data) == 0:break                 ##针对linux或者mac
            print('收到客户端的信息: ',data)         ##打印客户端发送的信息
            obj = subprocess.Popen(
                data.decode('utf-8'),
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            stdout=obj.stdout.read()
            stderr=obj.stderr.read()
            connect.send(stdout+stderr)                    ##将客户端发来的命令执行的到的正确输出返回
            # connect.send(stderr)                    ##将客户端发来的命令执行的到的错误输出返回
        except ConnectionResetError:                ##针对Windows下的优化
            break

    connect.close()                         ##关闭本次通讯
phone.close()                           ##关闭本socket
SSH服务端
import socket           ##导入socket模块
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.connect(('127.0.0.1',6609))             ##链接服务器IP和端口
while True:
    user_inp = input('>>> ').strip()
    phone.send(user_inp.encode('utf-8'))  ##发送用户输入的信息
    data = phone.recv(1024)  ##接收服务端返回信息,1024代表本次最大接收1024bytes内容
    print('收到服务端信息: ', data.decode('utf-8'))  ##打印接收到的内容
SSH客户端

弊端: 如果打印ls等简单的命令没什么问题,但是Linux下打ps -ef,就会发现一次只能接收我们客户端设置的1024个bytes,而你再打印ls发现打印的是上衣没有打印完的内容,也就是ls和上次为打印干净的ps -ef混在了一起,而这种现象我们称作粘包

粘包问题

发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略
udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠

tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

两种情况下会发生粘包。

发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 
粘包扫盲

为什么会粘包?

    粘包问题只会出现在TCP中,UDP不会出现

    根本问题在于接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据

为什么TCP是可靠的?UDP是不可靠的?

  tcp在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的

解决方案一:

既然我的客户端设置最多接收1024,那我给它调大解决

import socket           ##导入socket模块
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.connect(('127.0.0.1',6609))             ##链接服务器IP和端口
while True:
    user_inp = input('>>> ').strip()
    phone.send(user_inp.encode('utf-8'))  ##发送用户输入的信息
    data = phone.recv(1024000)  ##接收服务端返回信息,1024代表本次最大接收1024bytes内容
    print('收到服务端信息: ', data.decode('utf-8'))  ##打印接收到的内容
放大recv值-客户端修改

结论:虽然解决了这种粘包问题,但是没有根本解决问题,因为你根本不知道下次可能会有更大的数据量,依然无法满足过大消耗的是服务器本身的内存,所以最好的办法就是设置1024,然后循环去取,直到取干净本次的内容,然后一次性打印;所以recv调整不可取

解决方案二:

既然遵循尼桑算法(数据量小,时间间隔短,打包黏在一起发送,来减少IO),那么延长发送时间


解决方案三:

根本问题在于客户端不知道自己发出的指令得出的结果有多大,所以导致客户端的1024太小,那么我们能不能在客户端发出查询指令后,服务端查出结果后先提前告诉客户端这个结果有多大呢?看代码

import socket
import subprocess
import struct           ##用于将int转换为固定长度的bytes的模块,还支持很多



phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.bind(('127.0.0.1',6609))          ##将服务端绑定到本机IP和固定端口,#ip应该是服务端这个软件运行那台机器的ip地址,port(0-65535)
phone.listen(5)                         ##半连接池:控制的是同一时刻的链接请求数
while True:
    connect,ip_addr = phone.accept()        ##(connect=套接字对象,ip_addr=存放有客户端的ip和端口的元组)
    while True:
        try:
            data = connect.recv(1024)               ##recv表示接收客户端发送的消息,1024单位是bytes,代表最大接收1024bytes
            if len(data) == 0:break                 ##针对linux或者mac
            print('收到客户端的信息: ',data)         ##打印客户端发送的信息
            obj = subprocess.Popen(
                data.decode('utf-8'),
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            stdout=obj.stdout.read()
            stderr=obj.stderr.read()
            # 1、提前发送长度给客户端
            connect.send(struct.pack('i',len(stderr+stdout)))       ##将总长度转换为bytes提前发送给客户端,实际上是将int转换为bytes,而这个bytes的长度是4,是固定的
            # 2、发送数据
            connect.send(stdout+stderr)                    ##将客户端发来的命令执行的到的正确输出返回
            # connect.send(stderr)                    ##将客户端发来的命令执行的到的错误输出返回
        except ConnectionResetError:                ##针对Windows下的优化
            break

    connect.close()                         ##关闭本次通讯
phone.close()                           ##关闭本socket
自定义协议-固定长度报头-服务端
import socket           ##导入socket模块
import struct           ##用于将int转换为固定长度的bytes的模块,还支持很多

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.connect(('127.0.0.1',6609))             ##链接服务器IP和端口
while True:
    user_inp = input('>>> ').strip()
    if len(user_inp) == 0:continue
    phone.send(user_inp.encode('utf-8'))  ##发送用户输入的信息


    # 1、首先接收服务端下次传输的长度
    header = phone.recv(4)                  ##客户端先接收4个bytes
    total_size = struct.unpack('i',header)[0]       ##拿到4个bytes后,通过struct.unpack解出元组得到下次接收的总长度
    recv_size = 0                       ##用于记录一共接收了多少bytes
    res = b''
    # 2、循环取值
    while recv_size < total_size:
        recv_data = phone.recv(1024)        ##接收新数据
        res += recv_data                    ##把每次的接收数据拼接起来给res
        recv_size += len(recv_data)         ##记录每次循环实时一共接收了多少bytes

    print('收到服务端信息: ', res.decode('utf-8'))  ##打印接收到的内容
phone.close()
自定义协议-固定长度报头-客户端

结论: 解决了包黏在一起的问题

弊端:目前只是查询命令,所以可能暂时够用,如果你查询的结果是102400000000000000000000000000,struct可能就不能满足了,那么这种场景存在吗?比如FTP上传10G的电影,可能就尬了,所以不可取

解决方案四:

看完上面三个,我们现在只需要集中思路想一想看看怎么解决大文件传输的问题,说白了看看怎么标记102400000000000000000000000000这么大的数字

import socket
import subprocess
import struct           ##用于将int转换为固定长度的bytes的模块,还支持很多
import json



phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.bind(('127.0.0.1',6609))          ##将服务端绑定到本机IP和固定端口,#ip应该是服务端这个软件运行那台机器的ip地址,port(0-65535)
phone.listen(5)                         ##半连接池:控制的是同一时刻的链接请求数
while True:
    connect,ip_addr = phone.accept()        ##(connect=套接字对象,ip_addr=存放有客户端的ip和端口的元组)
    while True:
        try:
            data = connect.recv(1024)               ##recv表示接收客户端发送的消息,1024单位是bytes,代表最大接收1024bytes
            if len(data) == 0:break                 ##针对linux或者mac
            print('收到客户端的信息: ',data)         ##打印客户端发送的信息
            obj = subprocess.Popen(
                data.decode('utf-8'),
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            stdout=obj.stdout.read()
            stderr=obj.stderr.read()

            # 1、制作报头
            header_dic = {
                'filename':'xxxxx',
                'total_size':len(stderr+stdout),        ##此处是传输数据的总大小
                'md5':'xxxxxxxxxxxxxxxxxxxx',
            }
            header_str = json.dumps(header_dic)            ##为了传输到客户端后能得到字典,使用json,得到header的str类型
            heaer_bytes = header_str.encode('utf-8')        ##解码得到header的bytes类型,而现在的bytes长度是非常小了

            # 2、发送报头(header_bytes)长度
            connect.send(struct.pack('i',len(heaer_bytes)))      ##这里发送的是将header_dic转换为bytes后的长度的元组,但是客户端接收4bytes即可得到

            # 3、发送报头(header_bytes)
            connect.send(heaer_bytes)

            # 4、发送数据
            connect.send(stdout+stderr)

        except ConnectionResetError:                ##针对Windows下的优化
            break

    connect.close()                         ##关闭本次通讯
phone.close()                           ##关闭本socket
自定义报文-终极版-服务端
import socket           ##导入socket模块
import struct           ##用于将int转换为固定长度的bytes的模块,还支持很多
import json

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)        ##AF_INET = internet,SOCK_STREAM表示流式协议,也就是TCP
phone.connect(('127.0.0.1',6609))             ##链接服务器IP和端口
while True:
    user_inp = input('>>> ').strip()
    if len(user_inp) == 0:continue
    phone.send(user_inp.encode('utf-8'))  ##发送用户输入的信息

    # 1、接收报文的长度
    header_size = struct.unpack('i',phone.recv(4))[0]       #从元祖中拿到报文的长度

    # 2、接收报文
    header_bytes = phone.recv(header_size)                  #指定上面得到的报文长度接收json的bytes内容
    header_json = header_bytes.decode('utf-8')              #得到json的bytes内容后转成json文件
    header_dic = json.loads(header_json)                    #得到字典
    # 3、提取总大小
    total_size = header_dic['total_size']                   #根据字典取出查询结果的总大小

    recv_size = 0                       ##用于记录一共接收了多少bytes
    res = b''
    # 2、循环取值
    while recv_size < total_size:
        recv_data = phone.recv(1024)        ##接收新数据
        res += recv_data                    ##把每次的接收数据拼接起来给res
        recv_size += len(recv_data)         ##记录每次循环实时一共接收了多少bytes

    print('收到服务端信息: ', res.decode('utf-8'))  ##打印接收到的内容
phone.close()
自定义报文-终极版-客户端

结论:我们通过这个方法实现了真正的粘包解决方案,而这种的重点在哪呢?重点在于之前struct直接int转bytes,会受int大小的限制,而通过先将int大小添加到字典在转成字符串就解决了这个问题

    方法一:  10000    表达方式    10000    直接int表达

    方法二:  10000    表达方式    5       使用dic表达,再转str

   很显然对于struct来说第二种方法更方便一些,能表达的值更大一些 

socket并发实现

socket并发实现前,首先要明白为什么上面的方案无法实现并发,是因为实现通讯为两个步骤一个是“循环建立连接”,一个是“循环通讯”,但是一个请求的完整流程意味着进入通讯阶段,就无法在建立连接,只有等本次通讯结束,那么

socket的并发就是通过多线程技术来实现“循环建立连接”和“循环通讯”分离开来,来真正实现各自的循环

import socketserver
##之所以无法实现并发是因为TCP有两种活一个是链接循环和通讯循环,把他们分开就好了


# 通讯循环
class MyTCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        print(self.request)     #self.request == conn
        while True:
            try:
                data = self.request.recv(1024)
                if not data:break
                print('收到客户端消息: %s' %data)
                self.request.send(data.upper())
            except ConnectionResetError:
                break
        self.request.close()


# 链接循环
if __name__ == '__main__':
    ## 造线程
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8081),MyTCPHandler)
    server.serve_forever()          ##永远循环,接请求
socket并发编程-服务端
import socket


client  = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8081))
while True:
    user_inp = input('>>> ').strip()
    client.send(user_inp.encode('utf-8'))  ##发送用户输入的信息
    data = client.recv(1024)  ##接收服务端返回信息,1024代表本次最大接收1024bytes内容
    print('收到服务端信息: ', data)  ##打印接收到的内容
socket并发编程-客户端

基于UDP的套接字通讯

from socket import *            ##将udp的所有模块导入服务端(名称空间)


server=socket(AF_INET,SOCK_DGRAM) # 数据报协议UDP
#1、基于udp协议每发送的一条数据都自带边界,即udp协议没有粘包问题
#2、基于udp协议的通信,一定是一发对应一收

server.bind(('127.0.0.1',8080))

while True:
    msg,client_addr=server.recvfrom(1024)       ##若接收内容为大于1024bytes,那么大于的部分将被丢弃
    print('收到客户端信息:',msg)
    server.sendto(msg.upper(),client_addr) ##注意这里发送信息和TCP有所不同,必须指定收件人信息,因为UDP不是基于三次握手通讯
基于UDP-服务端
from socket import *

client=socket(AF_INET,SOCK_DGRAM)       ##指定数据报协议SOCK_DGRAM

while True:
    msg = input('请输入发送的信息: ').strip()
    client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))       ##指定服务端地址
    res,server_addr=client.recvfrom(1024)           ##若接收内容为大于1024bytes,那么大于的部分将被丢弃
    print('收到服务端信息',res)
基于UDP-客户端

结论:  UDP和TCP不同,是不可靠协议,不会进行三次握手建立连接,若服务端/客户端不启动,或网络有问题,客户端/服务端发送也不会抛出异常,发送的数据丢失

        UDP的有效可靠传输512bytes,大于512bytes将不可靠

     UDP没有粘包问题,只有收到或丢失的情况

        涉及到查询,查询的内容小于512建议使用UDP,涉及到重要保存或下载使用TCP

 

 

原文地址:https://www.cnblogs.com/swiki/p/9562035.html