zoukankan      html  css  js  c++  java
  • Python--网络编程

    Python--网络编程

    socket模块

    套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。 

    基于文件类型的套接字家族

    套接字家族的名字:AF_UNIX

    unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

    基于网络类型的套接字家族

    套接字家族的名字:AF_INET

    (还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

    tcp协议和udp协议

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

    UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)

    基于TCP协议的socket

    server端

    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
    sk.listen()          #监听链接
    conn,addr = sk.accept() #接受客户端链接
    ret = conn.recv(1024)  #接收客户端信息
    print(ret)       #打印客户端信息
    conn.send(b'hi')        #向客户端发送信息
    conn.close()       #关闭客户端套接字
    sk.close()        #关闭服务器套接字(可选)

    client端

    import socket
    sk = socket.socket()           # 创建客户套接字
    sk.connect(('127.0.0.1',8898))    # 尝试连接服务器
    sk.send(b'hello!')
    ret = sk.recv(1024)         # 对话(发送/接收)
    print(ret)
    sk.close()            # 关闭客户套接字
    #加入一条socket配置,重用ip和端口
    import socket
    from socket import SOL_SOCKET,SO_REUSEADDR
    sk = socket.socket()
    sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
    sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
    sk.listen()          #监听链接
    conn,addr = sk.accept() #接受客户端链接
    ret = conn.recv(1024)   #接收客户端信息
    print(ret)              #打印客户端信息
    conn.send(b'hi')        #向客户端发送信息
    conn.close()       #关闭客户端套接字
    sk.close()        #关闭服务器套接字(可选)
    开启服务端时端口复用

    基于UDP协议的socket

    server端

    import socket
    udp_sk = socket.socket(type=socket.SOCK_DGRAM)   #创建一个服务器的套接字
    udp_sk.bind(('127.0.0.1',9000))        #绑定服务器套接字
    msg,addr = udp_sk.recvfrom(1024)
    print(msg)
    udp_sk.sendto(b'hi',addr)                 # 对话(接收与发送)
    udp_sk.close()                         # 关闭服务器套接字

    client端

    import socket
    ip_port=('127.0.0.1',9000)
    udp_sk=socket.socket(type=socket.SOCK_DGRAM)
    udp_sk.sendto(b'hello',ip_port)
    back_msg,addr=udp_sk.recvfrom(1024)
    print(back_msg.decode('utf-8'),addr)

    时间服务器

    # _*_coding:utf-8_*_
    from socket import *
    from time import strftime
    
    ip_port = ('127.0.0.1', 9000)
    bufsize = 1024
    
    tcp_server = socket(AF_INET, SOCK_DGRAM)
    tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    tcp_server.bind(ip_port)
    
    while True:
        msg, addr = tcp_server.recvfrom(bufsize)
        print('===>', msg)
    
        if not msg:
            time_fmt = '%Y-%m-%d %X'
        else:
            time_fmt = msg.decode('utf-8')
        back_msg = strftime(time_fmt)
    
        tcp_server.sendto(back_msg.encode('utf-8'), addr)
    
    tcp_server.close()
    server
    #_*_coding:utf-8_*_
    from socket import *
    ip_port=('127.0.0.1',9000)
    bufsize=1024
    
    tcp_client=socket(AF_INET,SOCK_DGRAM)
    
    
    
    while True:
        msg=input('请输入时间格式(例%Y %m %d)>>: ').strip()
        tcp_client.sendto(msg.encode('utf-8'),ip_port)
    
        data=tcp_client.recv(bufsize)
    client

    socket参数详解

    socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)
    family 地址系列应为AF_INET(默认值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。
    (AF_UNIX 域实际上是使用本地 socket 文件来通信)
    type 套接字类型应为SOCK_STREAM(默认值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。
    SOCK_STREAM 是基于TCP的,有保障的(即能保证数据正确传送到对方)面向连接的SOCKET,多用于资料传送。 
    SOCK_DGRAM 是基于UDP的,无保障的面向消息的socket,多用于在网络上发广播信息。
    proto 协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应为CAN_RAW或CAN_BCM之一。
    fileno 如果指定了fileno,则其他参数将被忽略,导致带有指定文件描述符的套接字返回。
    与socket.fromfd()不同,fileno将返回相同的套接字,而不是重复的。
    这可能有助于使用socket.close()关闭一个独立的插座。

    粘包现象

    黏包现象只发生在tcp协议中:

    1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。

    2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

    解决粘包现象

    刚刚的方法,问题在于我们我们在发送

    我们可以借助一个模块,这个模块可以把要发送的数据长度转换成固定长度的字节。这样客户端每次接收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,那么最终接受的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据了

    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)
    #_*_coding:utf-8_*_
    #http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html
    __author__ = 'Linhaifeng'
    import struct
    import binascii
    import ctypes
    
    values1 = (1, 'abc'.encode('utf-8'), 2.7)
    values2 = ('defg'.encode('utf-8'),101)
    s1 = struct.Struct('I3sf')
    s2 = struct.Struct('4sI')
    
    print(s1.size,s2.size)
    prebuffer=ctypes.create_string_buffer(s1.size+s2.size)
    print('Before : ',binascii.hexlify(prebuffer))
    # t=binascii.hexlify('asdfaf'.encode('utf-8'))
    # print(t)
    
    
    s1.pack_into(prebuffer,0,*values1)
    s2.pack_into(prebuffer,s1.size,*values2)
    
    print('After pack',binascii.hexlify(prebuffer))
    print(s1.unpack_from(prebuffer,0))
    print(s2.unpack_from(prebuffer,s1.size))
    
    s3=struct.Struct('ii')
    s3.pack_into(prebuffer,0,123,123)
    print('After pack',binascii.hexlify(prebuffer))
    print(s3.unpack_from(prebuffer,0))
    关于struct的详细用法

    自定义报头的解决

    import socket,struct,json
    import subprocess
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加
    
    phone.bind(('127.0.0.1',8080))
    
    phone.listen(5)
    
    while True:
        conn,addr=phone.accept()
        while True:
            cmd=conn.recv(1024)
            if not cmd:break
            print('cmd: %s' %cmd)
    
            res=subprocess.Popen(cmd.decode('utf-8'),
                                 shell=True,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE)
            err=res.stderr.read()
            print(err)
            if err:
                back_msg=err
            else:
                back_msg=res.stdout.read()
    
            headers={'data_size':len(back_msg)}
            head_json=json.dumps(headers)
            head_json_bytes=bytes(head_json,encoding='utf-8')
    
            conn.send(struct.pack('i',len(head_json_bytes))) #先发报头的长度
            conn.send(head_json_bytes) #再发报头
            conn.sendall(back_msg) #在发真实的内容
    
        conn.close()
    server
    from socket import *
    import struct,json
    
    ip_port=('127.0.0.1',8080)
    client=socket(AF_INET,SOCK_STREAM)
    client.connect(ip_port)
    
    while True:
        cmd=input('>>: ')
        if not cmd:continue
        client.send(bytes(cmd,encoding='utf-8'))
    
        head=client.recv(4)
        head_json_len=struct.unpack('i',head)[0]
        head_json=json.loads(client.recv(head_json_len).decode('utf-8'))
        data_len=head_json['data_size']
    
        recv_size=0
        recv_data=b''
        while recv_size < data_len:
            recv_data+=client.recv(1024)
            recv_size+=len(recv_data)
    
        print(recv_data.decode('utf-8'))
        #print(recv_data.decode('gbk')) #windows默认gbk编码
    client

    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的更多方法
    官方文档对socket模块下的socket.send()和socket.sendall()解释如下:
    
    socket.send(string[, flags])
    Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Returns the number of bytes sent. Applications are responsible for checking that all data has been sent; if only some of the data was transmitted, the application needs to attempt delivery of the remaining data.
    
    send()的返回值是发送的字节数量,这个数量值可能小于要发送的string的字节数,也就是说可能无法发送string中所有的数据。如果有错误则会抛出异常。
    
    –
    
    socket.sendall(string[, flags])
    Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Unlike send(), this method continues to send data from string until either all data has been sent or an error occurs. None is returned on success. On error, an exception is raised, and there is no way to determine how much data, if any, was successfully sent.
    
    尝试发送string的所有数据,成功则返回None,失败则抛出异常。
    
    故,下面两段代码是等价的:
    
    #sock.sendall('Hello world
    ')
    
    #buffer = 'Hello world
    '
    #while buffer:
    #    bytes = sock.send(buffer)
    #    buffer = buffer[bytes:]
    send和sendall方法

    socketserver

    socketserver模块帮我们实现了并发,用法相当简单

    import socketserver
    class Myserver(socketserver.BaseRequestHandler):
        def handle(self):
            self.data = self.request.recv(1024).strip()
            print("{} wrote:".format(self.client_address[0]))
            print(self.data)
            self.request.sendall(self.data.upper())
    
    if __name__ == "__main__":
        HOST, PORT = "127.0.0.1", 9999
    
        # 设置allow_reuse_address允许服务器重用地址
        socketserver.TCPServer.allow_reuse_address = True
        # 创建一个server, 将服务地址绑定到127.0.0.1:9999
        server = socketserver.TCPServer((HOST, PORT),Myserver)
        # 让server永远运行下去,除非强制停止程序
        server.serve_forever()
    server
    import socket
    
    HOST, PORT = "127.0.0.1", 9999
    data = "hello"
    
    # 创建一个socket链接,SOCK_STREAM代表使用TCP协议
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((HOST, PORT))          # 链接到客户端
        sock.sendall(bytes(data + "
    ", "utf-8")) # 向服务端发送数据
        received = str(sock.recv(1024), "utf-8")# 从服务端接收数据
    
    print("Sent:     {}".format(data))
    print("Received: {}".format(received))
    client

    源码解读学习链接解读socketserver源码 —— http://www.cnblogs.com/Eva-J/p/5081851.html 

  • 相关阅读:
    ASP.NET异步处理
    C# TPL学习
    canvas 动画库 CreateJs 之 EaselJS(上篇)
    kafka消息的可靠性
    流式处理框架storm浅析(下篇)
    流式处理框架storm浅析(上篇)
    网易严选后台系统前端规范化解决方案
    Question | 移动端虚拟机注册等作弊行为的破解之道
    Puppeteer入门初探
    ThreeJs 3D 全景项目开发总结
  • 原文地址:https://www.cnblogs.com/mr07lee/p/10097950.html
Copyright © 2011-2022 走看看