socket

socket 是工作在应用层和传输层之间的抽象层

tcp 循环发消息

# ### tcp 客户端
import socket

# 1.创建一个socket对象
sk = socket.socket()

# 2.连接服务端
sk.connect( ("127.0.0.1",9000) )

# 3.收发数据
"""
res = sk.recv(1024) # 一次接受的最大字节数是1024
print(res)
"""

while True:
    strvar = input("请输入您要发送的数据")
    sk.send(strvar.encode())

    res = sk.recv(1024)
    if res == b"q":
        break
    print(res.decode())


# 4.关闭连接
sk.close()
# ### tcp 服务端

import socket
# 1.创建一个socket对象
sk = socket.socket()

# 让当前端口重复绑定多个程序(仅仅在测试阶段用)
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

# 2.在网络中注册主机(绑定ip和端口号)
sk.bind( ("127.0.0.1",9000) )

# 3.监听端口
sk.listen()

# 4.三次握手
# conn,addr = sk.accept()

# 5.收发数据
'''
数据类型:二进制的字节流
b修饰的字符串 => 代表的是二进制的字节流
里面的字符必须是ascii编码中的字符.不能是中文,否则报错
'''
"""
conn.send(b"i love you")
"""

while True:
    conn,addr = sk.accept()
    while True:
        res = conn.recv(1024)
        print(res.decode())
        strvar = input("请输入服务端给客户端发送的消息:")
        conn.send(strvar.encode())
        # 退出
        if strvar == "q":
            break


# 6.四次挥手
conn.close()

# 7.退还端口
sk.close()

调用类对象中的成员使用 
对象.属性 对象.方法
类.属性 类.方法

三次握手,四次挥手后建立稳定的连接

 

 

 

 

TCP 收发数据

 

 

 

tcp粘包

 

解决粘包问题

import struct
"""
pack:
    把任意长度的数字转化成居右4个字节的固定长度的字节流

unpack:
    把4个字节值恢复成原本的数字,返回是元组

"""
# i => int 要转化的当前数据时整型
"""pack的数值范围不是无限的,上限大概在21个亿左右,不要超过这个值"""
res = struct.pack("i",999999999)
res = struct.pack("i",1234343433)
res = struct.pack("i",2100000011)
print(res)
print(len(res))

# i => 把对应的数据转换成int, 最后返回元祖
tup = struct.unpack("i",res)
print(tup[0]) # (2100000011,)

"""
#解决黏包场景:
    应用场景在实时通讯时,需要阅读此次发的消息是什么
#不需要解决黏包场景:
    下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓.
"""
class MyTCPClient:
    """客户端"""
    family = socket.AF_INET
    type = socket.SOCK_STREAM
    server_addr=(('127.0.0.1',9000))

    def __init__(self,self.server_addr,connect=True):
        self.socket = socket.socket(self.family,self.type)
        if connect:
            try:
                self.socket.connect(server_addr)
            except:
                self.socket.close()
                raise
    
    def handler_send(self,args):
        """
        发送字典,处理粘包
        args = {
            'cmd' : cmd,
            'user':user,
            'psd':psd,
            'filename': filename,
            'src_dir': src_dir,
            'dst_dir': dst_dir,
            'size': size,
        }
        """
        # 1.发送用户指令的字典
        # 1.1 args处理成二进制
        args_str = json.dumps(args)
        args_bytes = args_str.encode('utf8')
        # 1.2 先发长度
        args_len = len(args_bytes)
        self.socket.send(struct.pack('i',args_len))
        # 1.3 再发字典
        self.socket.send(args_bytes)
        # 1.4 如果字典里头有size关键字,再发文件
        if args.get('size'):
            size = args['size']
            src_dir = args['src_dir']
            with open(src_dir,'rb') as f:
                send_size = 0
                while send_size < size:
                    res = f.read(1024)
                    self.socket.send(res)
                    send_size += len(res)
                    self.view_bar(send_size,size)
            print('发送完毕!')
        # 2.接收执行结果的字典
        return self.handler_recv()

    def handler_recv(self):
        """
        接受指令,处理粘包,拿到字典
        res_head_dic = {
            'res' :'',
            'filename' :'',
            'size' :'',
        }
        """
        while True:
            try:
                # 先收报头长度
                head_struct = self.socket.recv(4)
                head_length = struct.unpack('i', head_struct)[0]
                # 收对应长度报头
                head_bytes = self.socket.recv(head_length)
                head_str = head_bytes.decode('utf8')
                head_dic = json.loads(head_str)
                print('\033[31mServer: %s\033[0m' % head_dic['res'])
                # 如果head_dic中有filename和size,就进一步接收文件
                if head_dic.get('size'):
                    size = head_dic['size']
                    filename = head_dic['filename']
                    dst_dir = head_dic['dst_dir']
                    file_hash = head_dic['file_hash']
                    if not os.path.exists(dst_dir): # 验证保存路径是否存在
                        os.makedirs(dst_dir)
                    file_dir = os.path.join(dst_dir,filename)
                    with open(file_dir,'wb') as f: # 写入文件
                        recv_size = 0
                        while recv_size < size:
                            res = self.socket.recv(1024)
                            f.write(res)
                            recv_size += len(res)
                            self.view_bar(recv_size,size) # 显示接收进度
                        print()
                    # 校验文件的一致性
                    dst_hash = self.get_md5_doc(file_dir, size)
                    if not file_hash == dst_hash:
                        print('文件接收不完整,请重发!')
                    else:
                        print('接收完毕!')
                return head_dic
            except Exception:
                break

    def get_cmd(self):
        """接受用户指令"""
        while True:
            self.help_info()
            cmd = input('【路径需加双引号】>>>:')
            if not cmd: continue
            cmd_li = cmd.split()
            order = cmd_li[0].strip()
            if hasattr(self,order):
                func = getattr(self,order)
                func(cmd)  # 调对应方法把用户要发送的信息打包成字典
            elif order == 'quit':
                self.close_connect(cmd)
                print('再见!')
                break
            else:
                print('程序暂不支持此操作!')  
解决粘包 面向对象版本 client
class MyTCPServer:
    family = socket.AF_INET
    type = socket.SOCK_STREAM
    request_queue_size = 5
    user_info = {'user': None}
    dir_info = {'home': settings.DATA_DIR,'current_dir':settings.DATA_DIR}
   
    def __init__(self,server_addr,bind_and_activate = True):
        """创建socket对象,搭建服务器到监听状态"""
        self.socket = socket.socket(self.family,self.type)
        self.server_addr = server_addr
        if bind_and_activate:
            try:
                self.socket.bind(self.server_addr)
                self.socket.listen(self.request_queue_size)
            except:
                self.socket.close()
                raise

    def server_get_connect(self):
        """建立连接"""
        return self.socket.accept()

    def handler_recv(self,conn):
        """
        接收指令,处理粘包,拿到字典
        head_dic = {
            'cmd' :'',
            'user' :'',
            'psd' :'',
            'filename' :'',
            'src_dir': src_dir,
            'size' :'',
        }
        """
        while True:
            try:
                # 1.收到用户发来的指令字典
                # 1.1 先收报头长度
                head_struct = conn.recv(4)
                head_length = struct.unpack('i', head_struct)[0]
                # 1.2 收对应长度报头
                head_bytes = conn.recv(head_length)
                head_str = head_bytes.decode('utf8')
                head_dic = json.loads(head_str)
                # 1.3 如果head_dic中有filename和size,就进一步接收文件
                if head_dic.get('size'):
                    # 2.调用函数处理用户指令,得到处理结果的字典
                    res_head_dic = self.send_file(head_dic,conn)
                    # 3.发送结果字典
                    self.handler_send(res_head_dic, conn)
                else:
                    # 2.order_handler分配给对应函数处理用户指令,得到处理结果的字典
                    res_head_dic = self.order_handler(head_dic, conn)
                    # 3.发送结果字典
                    self.handler_send(res_head_dic,conn)
            except Exception:
                break

    def handler_send(self,args,conn):
        """
        发送字典,处理粘包
         args = {
            'res': res,
            'filename':filename,
            'src_dir':src_dir,
            'dst_dir':dst_dir,
            'size': size,
            }
        """
        # args处理成二进制
        args_str = json.dumps(args)
        args_bytes = args_str.encode('utf8')
        # 先发长度
        args_len = len(args_bytes)
        conn.send(struct.pack('i', args_len))
        # 再发字典
        conn.send(args_bytes)

        # 如果字典里头有size关键字,再发文件
        if args.get('size'):
            size = args['size']
            src_dir = args['src_dir']
            send_size = 0
            with open(src_dir,'rb') as f:
                while send_size < size:
                    res = f.read(1024)
                    conn.send(res)
                    send_size += len(res)

    def order_handler(self,head_dic,conn):
        """解析dict,拿到并处理用户指令"""

        cmd = head_dic['cmd']
        order = cmd.split()[0]
        if hasattr(self, order):
            func = getattr(self, order)
            return func(head_dic,conn)
        elif cmd == 'quit':
            self.close_connect(head_dic,conn)
解决粘包 面向对象版本 server

udp 用于即时交互

# ### 客户端
import socket
# type=socket.SOCK_DGRAM => 返回udp协议对象
# 1.创建udp对象
sk = socket.socket(type=socket.SOCK_DGRAM)

# 2.发送数据
msg = "大妹子,你好呀?"
# sendto(二进制字节流, (ip,端口) )
sk.sendto(msg.encode() , ("127.0.0.1",9000) )

# 客户端接受服务端发过来的数据
msg,ser_addr = sk.recvfrom(1024)
print(msg.decode())
print(ser_addr)


# 3.关闭连接
sk.close()

---------------------------------------------------------------

# ### 服务端
import socket
# type=socket.SOCK_DGRAM => 返回udp协议对象
# 1.创建udp对象
sk = socket.socket(type=socket.SOCK_DGRAM)

# 2.绑定地址端口号
sk.bind( ("127.0.0.1",9000) )

# 3.接受消息(udp作为服务端的时候,第一次一定是接受消息)
msg,cli_addr = sk.recvfrom(1024)
print(msg.decode())
print(cli_addr) # ('127.0.0.1', 56184)

# 服务端给客户端发消息
msg = "我是老爷们,我不好!"
sk.sendto(msg.encode(), cli_addr )


# 4.关闭连接
sk.close()

tcp 和 udp对比

TCP/UDP协议:

TCP(Transmission Control Protocol)一种面向连接的、可靠的、传输层通信协议(比如:打电话)
优点:可靠,稳定,传输完整稳定,不限制数据大小
缺点:慢,效率低,占用系统资源高,一发一收都需要对方确认
应用:Web浏览器,电子邮件,文件传输,大量数据传输的场景

UDP(User Datagram Protocol)一种无连接的,不可靠的传输层通信协议(比如:发短信)
优点:速度快,可以多人同时聊天,耗费资源少,不需要建立连接
缺点:不稳定,不能保证每次数据都能接收到
应用:IP电话,实时视频会议,聊天软件,少量数据传输的场景

客户端和服务端在建立连接时: 三次握手
客户端和服务端在断开连接时: 四次挥手
SYN 创建连接
ACK 确认响应
FIN 断开连接

socket

socket的意义:通络通信过程中,信息拼接的工具(中文:套接字)
# 开发中,一个端口只对一个程序生效,在测试时,允许端口重复捆绑 (开发时删掉)
# 在bind方法之前加上这句话,可以让一个端口重复使用
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

黏包

# tcp协议在发送数据时,会出现黏包现象.    
    (1)数据粘包是因为在客户端/服务器端都会有一个数据缓冲区,
    缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。
    (2)在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度
    导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包
黏包出现的两种情况

#黏包现象一:
    在发送端,由于两个数据短,发送的时间隔较短,所以在发送端形成黏包
#黏包现象二:
    在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包
#总结:
    发送端,包之间时间间隔短 或者 接收端,接受不及时, 就会黏包
    核心是因为tcp对数据无边界截取,不会按照发送的顺序判断
黏包对比:tcp和udp

#tcp协议:
缺点:接收时数据之间无边界,有可能粘合几条数据成一条数据,造成黏包 
优点:不限制数据包的大小,稳定传输不丢包

#udp协议:
优点:接收时候数据之间有边界,传输速度快,不黏包
缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包

#tcp和udp对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送
但是tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应响应完毕为止
而udp一旦发送失败,是不会询问对方是否有响应的,如果数据量过大,易丢包
解决黏包问题

#解决黏包场景:
    应用场景在实时通讯时,需要阅读此次发的消息是什么
#不需要解决黏包场景:
    下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓.
模块 socketserver

#网络协议的最底层就是socket,基于原有socket模块,又封装了一层,就是socketserver
socketserver 为了实现tcp协议,server端的并发.

 

posted @ 2020-09-14 10:15  正在学Python  阅读(135)  评论(0编辑  收藏  举报