zoukankan      html  css  js  c++  java
  • 网络编程~socket

    理解Socket

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

    其实在我看来socket就是一个模块.我们通过模块中已经实现的方法建立两个进程之间的连接和通信.

    socket层

              

    TCP协议和UDP协议

    TCP:可靠的,面向连接的协议(eg:打电话),传输效率低全双工通信(发送缓存&接受缓存),面向字节流.使用TCP的应用:Web浏览器;电子邮件,文件传输程序.

    UDP:不可靠的,无连接的服务,传输效率高,一对一,一对多,多对一,多对多,面向报文,尽最大努力服务,无拥塞控制.使用UDP的应用:域名系统(DNS):视频流,IP语音.

        

    套接字(socket)初使用

    基于TCP协议的socket

    tcp是基于连接的,必须先启动服务端,然后启动客户端去链接服务端

    server端

    import socket
    sk = socket.socket()
    sk.bind(('192.168.13.118',8899))   #绑定地址和端口到套接字
    sk.listen()       #监听链接
    conn,addr = sk.accept()    #接受客户端链接
    ret = conn.recv(1024)     #接受客户端信息
    print(ret)     #打印客户端信息
    conn.close(b'hello')       #向客户端发送信息
    conn.close()         #关闭客户端套接字
    sk.close()          #关闭服务器套接字
    server端

    client端

    import socket
    sk = socket.socket()            #创建客户套接字
    sk.connect(('192.168.13.118',8899))      #尝试链接服务器
    sk.send(b'hello')
    ret = sk.recv(1024)          #对话(发送/接收)
    print(ret)
    sk.close()                #关闭客户端套接字
    client端

    基于UDP协议的socket

    udp 是无连接的,启动服务之后就可以直接接收消息,不需要提前建立连接

    #server端
    import socket
    sk = socket.socket(type = socket.SOCK_DGRAM)   #创建一个服务器套接字
    sk.bind(('192.168.13.118',8899))    #绑定服务器套接字
    conn,addr = sk.recvfrom(1024)
    print(conn)
    sk.sendto(b'hi',addr)       #对话
    sk.close()          #关闭服务器套接字
    #client端
    
    ip_port = ('192.168.13.118',8899)
    sk = socket.socket(type = socket.SOCK_DGRAM)
    sk.sendto(b'hello',ip_port)
    conn,addr = sk.recvfrom(1024)
    print(conn.decode('utf-8'),addr)

    黏包

    res = subprocess.Popen(cmd.decode('utf-8'),
    shell = True,
    stderr = subprocess.PIPE,
    stdout = subprocess.PIPE)
    
    
    编码是以当前所在系统为准的,如果Windows,那么res.stdout.read()读出的就是GBK编码,同时需要GBK解码
    
    
    且只能从管道里读取一次结果
    黏包现象

    同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令时又接收到执行的另一部分结果,这种现象就是黏包.

    注意:只有TCP有黏包现象,UDP永远不会黏包

    黏包成因:

    tcp协议的拆包机制

      当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去.

    MTU是Maximum Transmission Unit的缩写.意思是网络上传送的最大数据包.MTU的单位是字节.大部分网络设备的MTU都是1500.如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多的数据碎片,增加丢包率,降低网络速度.

    面向流的通信特点和Nagle算法

    TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。
    收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。
    这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
    对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。
    可靠黏包的tcp协议:tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

    会发生黏包的两种情况

    情况一,发送方的缓存机制

    from socket import *
    
    ip_port = ('127.0.0.1',8899)
    
    tcp_socket_server = socket(AF_INET,SOCK_STREAM)
    tcp_socket_server.bind(in_port)
    tcp_socket_server.listen(5)
    
    conn,addr = tcp_socket_server.accept()
    
    data1 = conn.recv(10)
    data2 = conn.recv(10)
    
    print('---->',data1.decode('utf8'))
    print('---->',data2.decode('utf8'))
    
    conn.close()
    服务端
    import socket
    BUFSIZE = 1024
    ip_port=('127.0.0.1',8899)
    
    sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    res = s.connect_ex(ip_port)
    
    sk.send('hello'.encode('utf-8'))
    sk.send('egg'.encode('utf-8'))
    客户端

    情况二,接收方的缓存机制

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

    from socket import *
    ip_port=('127.0.0.1',8080)
    
    tcp_socket_server=socket(AF_INET,SOCK_STREAM)
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(5)
    
    
    conn,addr=tcp_socket_server.accept()
    
    
    data1=conn.recv(2) #一次没有收完整
    data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的
    
    print('----->',data1.decode('utf-8'))
    print('----->',data2.decode('utf-8'))
    
    conn.close()
    服务端
    import socket
    BUFSIZE=1024
    ip_port=('127.0.0.1',8080)
    
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    res=s.connect_ex(ip_port)
    
    
    s.send('hello egg'.encode('utf-8'))
    客户端

    黏包的解决方案

          

    struct模块

    该模块可以吧一个类型,如数字,转换成固定长度的bytes

    >>> struct.pack('i',1111111111111)
    
    struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围

    import json,struct
    #假设通过客户端上传1T:1073741824000的文件a.txt
    
    #为避免粘包,必须自定制报头
    header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T数据,文件路径和md5值
    
    #为了该报头能传送,需要序列化并且转为bytes
    head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并转成bytes,用于传输
    
    #为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节
    head_len_bytes=struct.pack('i',len(head_bytes)) #这4个字节里只包含了一个数字,该数字是报头的长度
    
    #客户端开始发送
    conn.send(head_len_bytes) #先发报头的长度,4个bytes
    conn.send(head_bytes) #再发报头的字节格式
    conn.sendall(文件内容) #然后发真实内容的字节格式
    
    #服务端开始接收
    head_len_bytes=s.recv(4) #先收报头4个bytes,得到报头长度的字节格式
    x=struct.unpack('i',head_len_bytes)[0] #提取报头的长度
    
    head_bytes=s.recv(x) #按照报头长度x,收取报头的bytes格式
    header=json.loads(json.dumps(header)) #提取报头
    
    #最后根据报头的内容提取真实的数据,比如
    real_data_len=s.recv(header['file_size'])
    s.recv(real_data_len)

    Socket的更多方法

    服务端套接字函数
    s.bind()    绑定(主机,端口号)到套接字
    s.listen()  开始TCP监听
    s.accept()  被动接受TCP客户的连接,(阻塞式)等待连接的到来
    
    客户端套接字函数
    s.connect()     主动初始化TCP服务器连接
    s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
    
    公共用途的套接字函数
    s.recv()            接收TCP数据
    s.send()            发送TCP数据
    s.sendall()         发送TCP数据
    s.recvfrom()        接收UDP数据
    s.sendto()          发送UDP数据
    s.getpeername()     连接到当前套接字的远端的地址
    s.getsockname()     当前套接字的地址
    s.getsockopt()      返回指定套接字的参数
    s.setsockopt()      设置指定套接字的参数
    s.close()           关闭套接字
    
    面向锁的套接字方法
    s.setblocking()     设置套接字的阻塞与非阻塞模式
    s.settimeout()      设置阻塞套接字操作的超时时间
    s.gettimeout()      得到阻塞套接字操作的超时时间
    
    面向文件的套接字的函数
    s.fileno()          套接字的文件描述符
    s.makefile()        创建一个与该套接字相关的文件
    更多socket方法
  • 相关阅读:
    [daily][netcat] 在UNIX socket上使用netcat
    [emacs] emacs设置python code的indent
    [dev][python] 从python2进阶到python3你都需要了解什么
    [strongswan][autoconf][automake][cento] 在CentOS上编译strongswan git源码时遇到的autoconf问题
    [strongswan] strongswan是如何实现与xfrm之间的trap机制的
    对不可描述的软件安装sfbo插件
    [daily] 如何用emacs+xcscope阅读内核源码
    [daily] cscope
    [dev][ipsec] 什么是xfrm
    [dev][ipsec] netlink是什么
  • 原文地址:https://www.cnblogs.com/wangjun187197/p/9588039.html
Copyright © 2011-2022 走看看