• Python开发——12.socket编程


    一、OSI七层

    1.物理层

    物理层的主要功能是基于电气特性发送高低电压(高代表1,低代表0)形成电信号,使计算机完成组网以达到接入Internet的目的

    2.数据链路层

    数据链路层是用来定义电信号的分组方式,使单纯的电信号0和1变得有意义

    (1)以太网协议

    以太网协议(ethernet)是统一的分组标准,以太网协议规定:a.一组电信号构成一个数据包,叫做“帧”;b。每一数据帧分成报头head和数据data两部分

    报头(head)固定为18个字节,发送者即源地址、接受者即目标地址和数据类型各占6个字节

    数据(data)最短为46字节,最长为1500字节,它包含了数据包的具体内容

    (2)mac地址和广播

    ethernet规定接入Internet的设备都必须具备网卡,发送端和接收端的地址即网卡地址,又称mac地址。

    ethernet采用广播的方式进行通信,一台主机通过ARP协议获取另一台主机的mac地址

    3.网络层

    网络层引入了一套新的地址即网络地址来区分不同的广播域

    (1)IP协议

    IP地址规定了网络地址由32位2进制表示,即从0.0.0.0-255.255.255.255,IP地址分为主机部分和网络部分

    (2)子网掩码

    子网掩码是描述子网络特征的参数,形式上与IP地址一致,但网络部分全部为1,主机部分全部为0,利用子网掩码与IP地址进行and运算(两个位数都为1运算结果为1,否则为0),结果相同表明在同一个子网络中。

    (3)ARP协议

    通过广播的方式发送数据包来获取目标主机的mac地址

    4.传输层

    传输层是用来建立端口到端口的通信,端口范围为0-65535,其中0-1023为系统占用端口

    (1)tcp协议和udp协议

    TCP协议为可靠传输,通常TCP的数据包的长度不会超过IP数据包的长度,理论上可以无限长;UDP协议为不可靠传输,总长度不超过65535个字节

    (2)tcp协议的三次握手和四次挥手

    5.应用层

    用来规定开发的应用程序的数据格式

    二、socket

    1.客户端/服务端架构

    即client/server(C/S)架构,socket就是为了实现C/S架构的开发,C/S架构的软件是基于网络进行通信的。

    2.socket层

    3.socket定义

    socket是应用层与TCP/IP协议通信的中间层,是一组封装了TCP/IP协议的接口,用户遵循socket规定编程即遵循tcp/udp协议

    socket主要有基于文件类型(AF_UNIX)和基于网络类型(AF_INET)两种家族。

    4.工作流程

    5.基于TCP的套接字

    tcp是基于链接的,必须先启动服务端

    服务端

    ss = socket() #创建服务器套接字
    ss.bind()      #把地址绑定到套接字
    ss.listen()      #监听链接
    inf_loop:      #服务器无限循环
        cs = ss.accept() #接受客户端链接
        comm_loop:         #通讯循环
            cs.recv()/cs.send() #对话(接收与发送)
        cs.close()    #关闭客户端套接字
    ss.close()        #关闭服务器套接字(可选)
    from socket import *
    import subprocess
    ip_port = ("10.10.27.37",8000)
    back_log = 5
    buffer_size = 1024
    
    ftp_server = socket(AF_INET,SOCK_STREAM)
    ftp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    ftp_server.bind(ip_port)
    ftp_server.listen(back_log)
    
    while True:
        conn,addr = ftp_server.accept()
        print("新的客户端链接",addr)
        while True:
            try:
                cmd = conn.recv(buffer_size)
                print("收到客户端的命令",cmd)
                if not cmd:
                    break
                res = subprocess.Popen(cmd.decode("utf-8"),shell = True,
                                       stderr=subprocess.PIPE,
                                       stdout=subprocess.PIPE,
                                       stdin=subprocess.PIPE)
                err = res.stderr.read()
                if err:
                    cmd_res = err
                else:
                    cmd_res = res.stdout.read()
                conn.send(cmd_res)
            except Exception:
                break
        conn.close()
    ftp_server

    客户端

    cs = socket()    # 创建客户套接字
    cs.connect()    # 尝试连接服务器
    comm_loop:        # 通讯循环
        cs.send()/cs.recv()    # 对话(发送/接收)
    cs.close()            # 关闭客户套接字
    from socket import *
    ip_port = ("10.10.27.37",8000)
    buffer_size = 1024
    
    ftp_client = socket(AF_INET,SOCK_STREAM)
    ftp_client.connect(ip_port)
    
    while True:
        cmd = input(">>>:")
        if not cmd :
            continue
        if cmd == "quit":
            break
        ftp_client.send(cmd.encode("utf-8"))
        res = ftp_client.recv(buffer_size)
        print(res.decode("gbk"))
    ftp_client.close()
    ftp_client

    6.基于udp的套接字

    udp是无连接的,可以先启动任意一端

    服务端

    ss = socket()   #创建一个服务器的套接字
    ss.bind()       #绑定服务器套接字
    inf_loop:       #服务器无限循环
        cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
    ss.close()                         # 关闭服务器套接字
    from socket import *
    ip_port = ("10.10.27.37",8001)
    buffer_size = 1024
    
    udp_server = socket(AF_INET,SOCK_DGRAM)
    udp_server.bind(ip_port)
    
    while True:
        data,addr =udp_sever.recvfrom(buffer_size)
        print(data,addr)
        udp_server.sendto(data.upper(),addr)
    udp_server

    客户端

    cs = socket()   # 创建客户套接字
    comm_loop:      # 通讯循环
        cs.sendto()/cs.recvfrom()   # 对话(发送/接收)
    cs.close()                      # 关闭客户套接字
    from socket import *
    udp_client = socket(AF_INET,SOCK_DGRAM)
    ip_port = ("10.10.27.37",8001)
    buffer_size = 1024
    
    while True:
        msg = input(">>>:")
        udp_client.sendto(msg.encode("utf-8"),ip_port)
        data,addr = udp_client.recvfrom(buffer_size)
        print(addr)
        print(data.decode("utf-8"))
    udp_client

    7.粘包

    (1)tcp和udp区别

    • TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
    • UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的

    (2)粘包现象

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

    • 正常情况

    • 发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)

    • 接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 

    (3)解决粘包的方法

    发送时:先发报头长度,再编码报头内容然后发送,最后发真实内容。

    接收时:先收报头长度,用struct取出来,根据取出的长度收取报头内容,然后解码,反序列化,从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容

    from socket import *
    import subprocess,struct
    ip_port = ("10.10.27.37",666)
    back_log = 5
    buffer_size = 1024
    
    tcp_server = socket(AF_INET,SOCK_STREAM)
    tcp_server.bind(ip_port)
    tcp_server.listen(back_log)
    
    while True:
        conn,addr = tcp_server.accept()
        print("新的客户端链接",addr)
        while True:
            #
            try:
                cmd = conn.recv(buffer_size)
                if not cmd :break
                print("收到客户端的命令",cmd)
    
                #执行命令,得到的运行结果cmd_res
                res = subprocess.Popen(cmd.decode("utf_8"),shell=True,
                                       stderr = subprocess.PIPE,
                                       stdout=subprocess.PIPE,
                                       stdin=subprocess.PIPE)
                err = res.stderr.read()
                if err:
                    cmd_res = err
                else:
                    cmd_res = res.stdout.read()
    
                #
                if not cmd_res:
                    cmd_res="执行成功".encode("gbk")
                #解决粘包
                length = len(cmd_res)
    
                data_length = struct.pack("i",length)#struct模块需要了解
                conn.send(data_length)
                conn.send(cmd_res)
            except Exception:
                break
    解决粘包server端
    from socket import *
    import struct
    from functools import partial
    ip_port = ("10.10.27.37",666)
    back_log = 5
    buffer_size = 1024
    
    tcp_client = socket(AF_INET,SOCK_STREAM)
    tcp_client.connect(ip_port)
    
    while True:
        cmd = input(">>>").strip()
        if not cmd:continue
        if cmd =="quit":break
    
        tcp_client.send(cmd.encode("utf-8"))
        #解决粘包
        length_data = tcp_client.recv(4)
        length = struct.unpack("i",length_data)[0]
    
        # recv_msg ="".join(iter(partial(tcp_client.recv,buffer_size),b""))
        recv_size = 0
        recv_data = "b"
        while recv_size<length:
            recv_data=tcp_client.recv(buffer_size)
            recv_size= len(recv_data)
        print("命令的执行结果是",recv_data.decode("gbk"))
    tcp_client.close
    解决粘包client端

    (4)TCP实现并发

    基于tcp的套接字,关键就是两个循环,一个链接循环,一个通信循环,利用socketserver模块中的两大类:server类(解决链接问题)和request类(解决通信问题)来实现并发

    import socketserver
    class MyServer(socketserver.BaseRequestHandler):
        def handle(self):
            print("conn in:",self.request)#conn
            print("addr is:",self.client_address)#addr
    
            while True:
                #收消息
                try:
                    data = self.request.recv(1024)
                    if not data:break
                    print("客户端收到的消息是:",data)
    
                    #发消息
                    self.request.sendall(data.upper())
                except Exception:
                    break
    if __name__ == "__main__":
        s = socketserver.ThreadingTCPServer(("10.10.27.37",8181),MyServer)#多进程
        # s = socketserver.ForkingTCPServer(("10.10.27.37",8181),MyServer)#多线程,系统开销高于多进程
    
        s.serve_forever()
    server端
    from socket import *
    ip_port = ("10.10.27.37",8181)
    back_log = 5
    buffer_size = 1024
    
    
    tcp_client = socket(AF_INET,SOCK_STREAM)
    tcp_client.connect(ip_port)
    
    while True:
        msg = input(">>>").strip()
        if not msg:continue
    
        tcp_client.send(msg.encode("utf-8"))
        data = tcp_client.recv(1024)
        print("收到服务端发来的消息",data.decode("utf-8"))
    
    tcp_client.close
    client端

     

  • 相关阅读:
    [置顶] Spring的自动装配
    BZOJ2831(小强的金字塔系列问题--区域整点数求法)
    国际跆拳道联盟
    Linux如何查找某个时间点后生成的空文件
    ORACLE中关于外键缺少索引的探讨和总结
    ORA-12514, TNS:listener does not currently know of service requested in connect descriptor案例2
    SQL Server 2005 sp_send_dbmail出现Internal error at FormatRowset (Reason: Not enough storage is available to complete this operation)
    SQL Server数据库邮件发送异常案例
    MySQL二进制日志总结
    OSWatcher使用过程中小问题解决方法
  • 原文地址:https://www.cnblogs.com/hechengwei/p/9141527.html
Copyright © 2020-2023  润新知