python网络编程linux_linux编程python - CSDN
精华内容
参与话题
  • python网络编程

    2019-08-14 11:16:18
    所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。 套接字分类 基于文件类型的套接字家族:AF_UNIX 基于网络类型的套...

    socket

    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

    套接字分类

    • 基于文件类型的套接字家族:AF_UNIX
    • 基于网络类型的套接字家族:AF_INET

    套接字工作流程

    套接字函数

    一、服务端套接字函数

    • s.bind() 绑定(主机,端口号)到套接字
    • s.listen() 开始TCP监听
    • s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来

    二、客户端套接字函数

    • s.connect() 主动初始化TCP服务器连接
    • s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

    三、公共用途的套接字函数

    • s.recv() 接收TCP数据
    • s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
    • s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
    • s.recvfrom() 接收UDP数据
    • s.sendto() 发送UDP数据
    • s.getpeername() 连接到当前套接字的远端的地址
    • s.getsockname() 当前套接字的地址
    • s.getsockopt() 返回指定套接字的参数
    • s.setsockopt() 设置指定套接字的参数
    • s.close() 关闭套接字

    四、面向锁的套接字方法

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

    五、面向文件的套接字的函数

    • s.fileno() 套接字的文件描述符
    • s.makefile() 创建一个与该套接字相关的文件

    基于TCP的套接字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    """
    服务端:
    """
    import socket
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建服务器套接字
    s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #重启服务端时遇上Address already in use 时加上,在bind前加
    s.bind(('127.0.0.1',8083)) #把地址绑定到套接字
    s.listen(5) #监听链接
     
    print('starting...')
    while True: # 链接循环
        conn,client_addr=s.accept() #接受客户端链接
        print(client_addr)
        while True: #通信循环
            try:
                data=conn.recv(1024)
                if not data:break #适用于linux操作系统 正在链接的客户端突然断开,recv便不再阻塞,死循环发生
                print('客户端的数据',data)
                conn.send(data.upper())
            except ConnectionResetError: #适用于windows操作系统
                break
        conn.close() #关闭客户端套接字
    s.close() #关闭服务器套接字(可选)
     
     
    """
    客户端
    """
    import socket
    c=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 创建客户套接字
    c.connect(('127.0.0.1',8083)) # 尝试连接服务器
    while True# 通讯循环
        msg=input('>>: ').strip() #msg=''
        if not msg:continue
        c.send(msg.encode('utf-8')) #c.send(b'')
        data=c.recv(1024)
        print(data.decode('utf-8'))
    c.close() # 关闭客户套接字

    基于UDP的套接字

    udp是无链接的,先启动哪一端都不会报错

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    """
    服务端:
    """
    from socket import *
    server=socket(AF_INET,SOCK_DGRAM)
    server.bind(('127.0.0.1',8080))
     
    while True:
        data,client_addr=server.recvfrom(1024)
        print(data)
        server.sendto(data.upper(),client_addr)
    server.close()
     
    """
    客户端
    """
    from socket import *
    client = socket(AF_INET, SOCK_DGRAM)
    while True:
        msg=input('>>: ').strip()
        client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
     
        data,server_addr=client.recvfrom(1024)
        print(data,server_addr)
    client.close()

    粘包现象

    一、产生原因

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

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

    二、补充说明

    • TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。
    • UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。
    • 只有TCP有粘包现象,UDP永远不会粘包

    三、解决粘包的方法

    把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节

    1.发送时:

    • 先发报头长度
    • 再编码报头内容然后发送
    • 最后发真实内容

    2.接收时:

    • 先收报头长度,用struct取出来
    • 根据取出的长度收取报头内容,然后解码,反序列化
    • 从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    """
    服务端
    """
    import socket
    import subprocess
    import struct
    import json
     
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    phone.bind(('127.0.0.1',9909))
    phone.listen(5)
     
    print('starting...')
    while True: # 链接循环
        conn,client_addr=phone.accept()
        print(client_addr)
     
        while True: #通信循环
            try:
                #1、收命令
                cmd=conn.recv(8096)
                if not cmd:break #适用于linux操作系统
     
                #2、执行命令,拿到结果
                obj = subprocess.Popen(cmd.decode('utf-8'), shell=True,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
     
                stdout=obj.stdout.read()
                stderr=obj.stderr.read()
     
                #3、把命令的结果返回给客户端
                #第3.1步:制作固定长度的报头
                header_dic={
                    'filename':'a.txt',
                    'md5':'xxdxxx',
                    'total_size': len(stdout) + len(stderr)
                }
     
                header_json=json.dumps(header_dic)
     
                header_bytes=header_json.encode('utf-8')
     
                #第3.2步:先发送报头的长度
                conn.send(struct.pack('i',len(header_bytes)))
     
                #第3.3步:再发报头
                conn.send(header_bytes)
     
                #第3.4步:再发送真实的数据
                conn.send(stdout)
                conn.send(stderr)
     
            except ConnectionResetError: #适用于windows操作系统
                break
        conn.close()
     
    phone.close()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    """
    客户端
    """
    import socket
    import struct
    import json
     
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     
    phone.connect(('127.0.0.1',9909))
     
    while True:
        #1、发命令
        cmd=input('>>: ').strip() #ls /etc
        if not cmd:continue
        phone.send(cmd.encode('utf-8'))
     
        #2、拿命令的结果,并打印
     
        #第2.1步:先收报头的长度
        obj=phone.recv(4)
        header_size=struct.unpack('i',obj)[0]
     
        #第2.2步:再收报头
        header_bytes=phone.recv(header_size)
     
        #第2.3步:从报头中解析出对真实数据的描述信息
        header_json=header_bytes.decode('utf-8')
        header_dic=json.loads(header_json)
        print(header_dic)
        total_size=header_dic['total_size']
     
        #第2.4步:接收真实的数据
        recv_size=0
        recv_data=b''
        while recv_size < total_size:
            res=phone.recv(1024) #1024是一个坑
            recv_data+=res
            recv_size+=len(res)
     
        print(recv_data.decode('utf-8'))
     
    phone.close()
    """
    内网ip
    192.168.0.0-192.168.255.255
    172.16.0.0-172-172.31.255.255
    10.0.0.0-10.255.255.255
    
    网卡 mac地址 全球唯一
    交换机进行局域网内的通讯
    交换机:只认识mac地址,广播,单播,组播
    arp协议:地址解析协议,已知一个机器的IP地址,获取这台机器的mac地址(广播-单播),由交换机完成的(广播,单播)
    
    路由器 局域网与局域网的通讯,识别IP地址
    网关IP,访问局域网外部服务的一个出口IP,访问局域网之外的区域都需要经过路由器和网关
    网段指的是一个地址段
    ip地址在网络上定位一台机器 ipv4  ipv6
    端口port能够在网络上定位一台机器的一个服务 0-65535
    子网掩码判断两台机器是否在同一个网段内
    
    阻塞io模型
    非阻塞io模型
    事件驱动io
    io多路复用
    异步io模型
    
    非阻塞io模型+io多路复用
    虽然非阻塞,提高了cpu的利用率,但是耗费了很多无用功
    
    import os
    print(os.urandom(32))
    
    server:接受链接,发送随机字符串,使用密钥和随机字符串计算,接受字符串,检测接受到的结果和自己计算的是否一致
    client:发送连接请求,接受随机字符串,使用密钥和随机字符串计算,发送计算结果
    """
    import os
    import socket
    import hashlib
    
    def get_md5(secret_key,randseq):
        md5 = hashlib.md5(secret_key)
        md5.update(randseq)
        return md5.hexdigest()
    
    sk=socket.socket()
    sk.bind(('127.0.0.1',9000))
    sk.listen()
    conn,addr=sk.accept()
    
    secret_key=b'qweqweqwe'
    randseq=os.urandom(32)
    conn.send(randseq)
    
    md5_code=get_md5(secret_key,randseq)
    ret=conn.recv(32).decode('utf-8')
    print(ret)
    if ret==md5_code:
        print('合法')
    else:
        print('非法')
    
    conn.close()
    sk.close()
    
    
    ######################客户端
    import socket
    import hashlib
    
    def get_md5(secret_key,randseq):
        md5 = hashlib.md5(secret_key)
        md5.update(randseq)
        return md5.hexdigest()
    
    sk=socket.socket()
    sk.connect(('127.0.0.1',9000))
    randseq=sk.recv(32)
    
    secret_key=b'qweqweqwe'
    md5_code=get_md5(secret_key,randseq)
    
    sk.send(md5_code.encode('utf-8'))
    sk.close()
    
    
    ######################
    import os
    import hmac
    secret_key=b'qweqweqwe'
    randseq=os.urandom(32)
    hmac=hmac.new(secret_key,randseq)
    ret=hmac.digest()
    print(ret) #字节
    print(len(ret)) #16
    #######################
    合法性验证

    socketserver实现并发

    • 基于tcp的套接字,关键就是两个循环,一个链接循环,一个通信循环
    • socketserver模块中分两大类:server类(解决链接问题)和request类(解决通信问题)
    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 + "\n", "utf-8")) # 向服务端发送数据
        received = str(sock.recv(1024), "utf-8")# 从服务端接收数据
    
    print("Sent:     {}".format(data))
    print("Received: {}".format(received))
    client
    """
    1.功能类
    class Myserver(socketserver.BaseRequestHandler):
        def handle(self):  #放入要并发的逻辑  self.request 就是之前的conn
            pass       
    2.server=socketserver.ThreadingTCPServer(("127.0.0.1",8881),Myserver)
    3.server.serve_forever()
    """
    import socketserver
    class Myserver(socketserver.BaseRequestHandler):
        def handle(self):  #放入要并发的逻辑  self.request 就是之前的conn
                while True:
                    try:
                        client_data=self.request.recv(1024)
                        if len(client_data)==0:
                            break  # 适用于linux操作系统 正在链接的客户端突然断开,recv便不再阻塞,死循环发生
                        print ("客户端的数据 >>>",str(client_data,"utf8"))
                        server_data=input("服务端的数据 >>>").strip()
                        self.request.send(bytes(server_data,"utf8"))
                    except ConnectionResetError:  # 适用于windows操作系统
                        break
                self.request.close()
    server=socketserver.ThreadingTCPServer(("127.0.0.1",8881),Myserver)
    server.serve_forever()
    ######################################################################
    import socket
    c=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 创建客户套接字
    c.connect(('127.0.0.1',8881)) # 尝试连接服务器
    while True:  # 通讯循环
        msg=input('>>: ').strip() #msg=''
        if not msg:continue
        c.send(msg.encode('utf-8')) #c.send(b'')
        data=c.recv(1024)
        print(data.decode('utf-8'))
    c.close() # 关闭客户套接字
    View Code

    四、分析socketserver源码:

    • server=socketserver.ThreadingTCPServer(("127.0.0.1",8881),Myserver)
    • server.serve_forever()
    • 查找属性的顺序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer
    • 实例化得到server,先找类ThreadingTCPServer的__init__,在TCPServer中找到,进而执行server_bind,server_active
    • 找server下的serve_forever,在BaseServer中找到,进而执行self._handle_request_noblock(),该方法同样是在BaseServer中 执行self._handle_request_noblock()进而执行request,client_address = self.get_request()(就是TCPServer中的self.socket.accept()),然后执行self.process_request(request, client_address)
    • 在ThreadingMixIn中找到process_request,开启多线程应对并发,进而执行process_request_thread,执行self.finish_request(request, client_address) 上述完成了链接循环.
    • 开始进入通讯部分,在BaseServer中找到finish_request,触发我们自己定义的类的实例化,去找__init__方法,而我们自己定义的类没有该方法,则去它的父类也就是BaseRequestHandler中找__init__方法....

    #_*_coding:utf-8_*_
    
    """
    尽管Son继承了Base类,父子类中都有同样的方法,
    但是由于我们实例化了子类的对象,所以这个在初始化方法里的self.Testfunc,
    self指的是子类的对象,当然也就先调用子类中的方法啦。
    所以尽管初始化方法在父类执行,但是还是改变不了它是子类对象的本质,
    当使用self去调用Testfunc方法时,始终是先调用子类的方法。
    """
    
    class Base(object):
        def __init__(self,name):
            self.name = name
            self.Testfunc()
        def Testfunc(self):
            print ('do Base Testfunc')
    class Son(Base):
        def Testfunc(self):
            print ('do Son Testfunc')
    
    sonobj = Son('sonobj')  # do Son Testfunc
    
    
    
    """
    尽管这三个类中都有同样的Testfunc方法,但是,由于计算机在找方法的时候,
    遵循的顺序是:Base2,Son,Base,所以它会先找到Base2类,
    而这个类中刚好有它要找的方法,它也就拿去执行啦!
    """
    
    class Base(object):
        def Testfunc(self):
            print ('do Base Testfunc')
    class Son(Base):
        def __init__(self,name):
            self.name = name
            self.Testfunc()
        def Testfunc(self):
            print ('do Son Testfunc')
    class Base2(object):
        def Testfunc(self):
            print ('do Base2 Testfunc')
    class GrandSon(Base2,Son):
        pass
    sonobj = Son('sonobj')       #do Son Testfunc
    sonobj = GrandSon('sonobj')  #do Base2 Testfunc
    
    
    
    class A:
        def func(self):
            super().func() #B里的func   此处的super是找mro中的下一个
            print('AAA')
    class B:
        def func(self):
            print('BBB')
    
    class C(A,B):
        pass
    c=C()
    c.func()  #BBB AAA
    print(C.mro()) #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
    继承复习
    """
    MRO方法路径顺序:
    py2
        1.使用经典类(写继承关系的时候,基类不继承object)
        2.新式类(继承关系的根,是)object
    py3
        只有新式类
    
    经典类的MRO使用的是深度优先遍历
    新式类中摒弃了(部分)旧的深度优先算法,使用C3算法
    如果你的继承关系中没有菱形继承(深度优选就够了)
    如果有菱形:使用C3算法来计算MRO
    假设C3算法:是L(x)表示x的继承关系
    先拆分,拆到你能看出结果为止,反着进行merge()运算
    合并
    merge(元祖,元祖,元祖...)
    (GECAMNO),(FDBECA)
    
    
    (ECAMNO),(FDBECA)
    G
    
    (ECAMNO),(DBECA)
    GF
    
    (ECAMNO),(BECA)
    GFD
    
    (ECAMNO),(ECA)
    GFDB
    
    (CAMNO),(CA)
    GFDBE
    
    (AMNO),(A)
    GFDBECAMNO
    ...
    
    GFDBECAMNO
    
    
    摘头
    头和尾在比对,如果下一个尾没有这个头,头就出现,如果头在后面的尾出现,跳过该元祖,继续下一个头,直到最后一个元素跟自己去匹配
    
    --super--可以访问MRO列表中的下一个类中的内容,找父类。
    不管super()写在那,在那执行,一定先找到MRO列表,根据MRO列表的顺序往下找,否则一切都是错的。
    """
    MRO

    1:threadingTCPServer的相关类:

    2:初始化相关过程:

    3:执行serve_forever的相关代码:

     

    五、源码分析总结:

    1、基于tcp的socketserver我们自己定义的类中的

    • self.server即套接字对象
    • self.request即一个链接
    • self.client_address即客户端地址

    2、基于udp的socketserver我们自己定义的类中的

    • self.request是一个元组(第一个元素是客户端发来的数据,第二部分是服务端的udp套接字对象),如(b'adsf', )
    • self.client_address即客户端地址

    转载于:https://www.cnblogs.com/bubu99/p/10171627.html

    展开全文
  • python 网络编程linux 环境下面)

    千次阅读 2015-03-15 09:30:54
    首先, 一般在每个python脚本的最开始, 添加如下语句: #!usr/bin/env python加上这一句, 我们就可以在terminal 下面, 写换到相关文件的目录下面, 直接执行: ./filename.py脚本中的这一句话的目的是告诉操作系统...

    这里主要使用socket模块。

    首先, 一般在每个python脚本的最开始, 添加如下语句:

    #!usr/bin/env python
    加上这一句, 我们就可以在terminal 下面, 写换到相关文件的目录下面, 直接执行:

    ./filename.py
    脚本中的这一句话的目的是告诉操作系统这个脚本的解释执行程序的 。 正是因为有了这一句, 我们才能够直接执行./命令。 (否则, 没有上述的那一句话, 我们就只能按照如下命令执行:

    python filename.py

    另外, 执行的时候, 如果遇见permission denied等相关错误, 那是因为我们的文件不是可执行的。 我们需要执行如下命令改变未见得mode:

    chmod +x filename.py 

    接下来,就可以直接运行:

    ./filename.py
    好了言归正传, 说说网络编程。

    所谓的networking, 就是两个进程通过network进行通信。 无论是client to client, 还是client to server, 甚至是client to itself。

    客户端/服务器模型是网络编程的基本模型。 所谓的客户端, 就是面对human 的终端器件, 所谓服务器, 就是为客户端提供service 的一个device。

    客户端通过连接到server获得相关的资讯。 举个例子, 网络浏览器(client)连接到google网站(server)就是属于Client/server模型。 C/S模型是最基本的网络编程模型。 

    第二个模型是Peer to peer model。  例如skype。 客户端同其他的客户端连接起来, 而没有使用central server. 

    网络编程的相关术语:

    Address(地址): 指代的是IP地址, 例如, ”127.0.0.1“, 也就是32位的 

    Port(端口号): 表示端口号, 例如5000

      根据协议, 1——1024这些端口被保留作为核心的协议。作为固定分配给一些服务。 例如比如21端口分配给FTP(文件传输协议)服务,25端口分配给SMTP(简单邮件传输协议)服务,80端口分配给HTTP服务,135端口分配给RPC(远程过程调用)服务等等。 所以我们在套接字(socket)编程的时候, 选择的端口号必须要大于1024, 但是还要小于65535。

    Socket(套接字): 套接字是支持TCP/IP 网络通信的基本操作单元, 是网络编程接口。  是一种介于应用层和运输层的编程接口抽象层。 如下图:


    Socket就是一组接口, 他把TCP/IP协议簇隐藏在Socket接口后面。 对用户来说, 一组简单的接口就是全部。让Socket去组织数据, 我们不用去关心那些复杂的协议了, 大大方便我们进行网络编程开发。 

    Socket的相关接口函数(Python):

    socket(socket_family,  socket_type);

    解释: 这是一个建构子, 创建一个新的socket。 socket_family参数, 默认值为AF_INET,  AF表示address family, 为AF_INET就代表使用IP。 也可以是AF_UNIX, AF_UNIX家族用于同一台机器上的进程间通信。 socket_type表示套接字的类型。 可以为SOCK_STREAM(流套接字), 表示使用TCP传输控制协议,  是双向可靠数据流。 也可以为SOCK_DGRAM(数据包套接字), 表示使用使用用户数据包UDP协议, 是双向不可靠数据报。

    服务器的套接字的函数:

    bind((hostname, port))
    解释: bind 函数将有(主机IP地址, 端口号)组成的元组(tuple)绑定到套接字上。

    listen()
    解释: 这个方法是的该主机等待TCP链接请求, 即TCP监听。

    accept()
    解释: 这个函数表示接受一个链接请求, 该函数返回一个新的socket。

    在client side:

    connect((hostname,  port))

    解释: 这个函数请求与一个listening server进行连接。

    公共用途的套接字函数:

    recv(buffer)
    解释: 用于从一个TCP的连接中抓取数据。 buffer表示缓冲区的大小, 通常取1024。

    send(bytes)
    解释: 试图传送上层传递下来的数据bytes发送出去。

    close()
    关闭一个套接字(socket)/连接, 释放port端口。

    什么是TCP:

    TCP, 全称transmission control protocol。 是基于可靠连接的协议。 具有简单的差错检测(checksum)。 

    TCP协议被网络浏览器, Email, SSH, FTP等等应用使用。

    TCP比UDP慢。

    提示: 在运行网络程序的时候, 最好在不同的电脑上执行服务器和客户端程序。 这里, 由于条件限制, 我在同一台电脑上运行。   

    相关程序如下.

    tcpClient.py:

    #!/usr/bin/env python
    import socket
    
    def Main():
            host = "127.0.0.1"
            port = 5000
    
            s = socket.socket()
            s.connect((host, port))
    
            message = raw_input("->")
            while message != 'q':
                    s.send(message)
                    data = s.recv(1024)
                    print "recieved from server:" + str(data)
                    message = raw_input("->")
            s.close()
    
    if __name__ == "__main__":
            Main()
                                                                                         
    tcpServer.py:

    #!/usr/bin/env python
    import socket
    
    def Main():
            host = "127.0.0.1"
            port = 5000
    
            s = socket.socket()
            s.bind((host, port))
    
            s.listen(1)
            c, addr = s.accept()
            print "connection from:" + str(addr)
            while True:
                    data = c.recv(1024)
                    if not data:
                            break
                    print "form connected user: " + str(data)
                    data = str(data).upper()
                    print "sending: " + str(data)
                    c.send(data)
            c.close()
    
    if __name__ == "__main__":
            Main()
    
    
    
    首先执行tcpServer.py

    然后Ctrl + ALT + T 在开一个terminal: 执行tcpClient.py:



    展开全文
  • 摘要:使用 wxFormBuilder 搭建软件框架,Python 3.6 编程实现了Linux及Windows系统时间同步软件设计,可用于系统内上位机、板卡及计算机的系统时间同步。       1 引言     时间同步是计算机应用系统一...

      
    摘要:使用 wxFormBuilder 搭建软件框架,Python 3.6 编程实现了Linux及Windows系统时间同步软件设计,可用于系统内上位机、板卡及计算机的系统时间同步。
      
      

    1 引言

        时间同步是计算机应用系统一个最基本的要求,集中式系统时间无二义性,而分布式系统本身没有标准的时间统一,所以必须建立分布式系统的时间统一系统。时间同步分为系统级时间同步和装置级时间同步,装置级时间同步较为简单,只要把原子钟产生的同步脉冲信号1PPS输入到相应的设备即可,系统级时间同步主要是针对系统内的上位机及计算机系统时间的同步。

    2 软件设计原理

                             1

    图1 系统级时间同步设计原理


        系统级时间同步设计原理如图1所示。基于NTP/SNTP协议的网络时间服务器,可以从GPS卫星上获取标准时钟信号,并将这些信息在网络中传输。NTP/SNTP协议除了可以估算数据包在网络上的往返延迟外,还可独立地估算本地计算机时钟偏差,从而实现在网络上的高精度计算机时间校时。NTP/SNTP协议是一种TCP/IP协议,其本身的传输基于UDP,保留端口号123。同步精度在ms量级。
        设计原理如下,首先创建网络时间客户端,客户端调用socket创建UDP套接字,用于连接网络时间服务器。然后,客户端向网络时间服务器发送请求,服务器返回包含时间信息的数据包,客户端对收到的数据包进行解析处理,计算得到数据包在网络上的往返传播延时和本地时钟补偿延时等参数,从而得到精确的本地时间,最后调用相关命令对计算机系统(Windows和Linux)的日期和时间进行修改,以实现时间同步。对于Linux系统的时间同步,需要通过SSH连接登陆到系统,然后执行同步命令。
        系统时间同步软件采用Python 3.6语言设计完成,使用wxPython GUI图形库搭建软件框架, socket库完成UDP通信,paramiko库完成SSH连接Linux系统。运行框图如图2所示。
                13

    图2 软件运行界面


        软件操作使用说明如下:
            1) 首先输入网络时间服务器地址、协议及端口号;
            2) Windows系统时间同步,点击开始同步按钮,可完成计算机与网络时间服务器的单次时间同步。若需要计算机按一定时间间隔同步网络时间服务器,可勾选定时同步功能,在定时文本框中输入时间间隔,注意是毫秒,然后点击开始定时同步按钮可进行定时同步,点击暂停同步按钮可以暂停定时同步。
            3) Linux系统时间同步,输入Linux系统的IP地址、用户名、密码。点击开始同步按钮,自动登录Linux系统并完成Linux系统与网络时间服务器的单次时间同步。若需要定时同步功能,操作步骤与步骤2一样。
            4) 单次同步和定时同步的同步状态信息在下方同步结果显示文本框中可以查看,同步状态信息包括数据包在网络上的往返传播延时、本地时钟补偿延时、当前精确的系统时间以及是否成功修改本地计算机系统时间。

    3 Python编码


        使用 wxFormBuilder生成wxPython GUI图形界面。下图是wxFormBuilder软件运行界面。
    wxFormBuilder
    生成的图形界面代码如下:
    文件:system_time_sync_frame.py

    # -*- coding: utf-8 -*- 
    
    ###########################################################################
    ## Python code generated with wxFormBuilder (version Jun 17 2015)
    ## http://www.wxformbuilder.org/
    ##
    ## PLEASE DO "NOT" EDIT THIS FILE!
    ###########################################################################
    
    import wx
    import wx.xrc
    
    ###########################################################################
    ## Class system_time_sync_frame
    ###########################################################################
    
    class system_time_sync_frame ( wx.Frame ):
    	
    	def __init__( self, parent ):
    		wx.Frame.__init__ ( self, parent = None, id = wx.ID_ANY, title = u"系统时间同步软件v1.0", pos = wx.DefaultPosition, size = wx.Size( 594,477 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
            
    #        # 设置窗口标题的图标
    #        self.icon = wx.Icon('time.ico', wx.BITMAP_TYPE_ICO)
    #        self.SetIcon(self.icon) 
            
    		self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize )
    		self.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 92, False, wx.EmptyString ) )
    		self.SetForegroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_INACTIVECAPTIONTEXT ) )
    		self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_MENU ) )
            
    		
    		bSizer1 = wx.BoxSizer( wx.VERTICAL )
    		
    		sbSizer1 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"【网络时间服务器配置】" ), wx.HORIZONTAL )
    		
    		sbSizer1.SetMinSize( wx.Size( 100,10 ) ) 
    		bSizer15 = wx.BoxSizer( wx.HORIZONTAL )
    		
    		bSizer5 = wx.BoxSizer( wx.HORIZONTAL )
    		
    		self.m_staticText3 = wx.StaticText( sbSizer1.GetStaticBox(), wx.ID_ANY, u"时间服务器IP地址:", wx.DefaultPosition, wx.DefaultSize, 0 )
    		self.m_staticText3.Wrap( -1 )
    		self.m_staticText3.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString ) )
    		
    		bSizer5.Add( self.m_staticText3, 0, wx.ALL, 5 )
    		
    		self.m_textCtrl_timeserver = wx.TextCtrl( sbSizer1.GetStaticBox(), wx.ID_ANY, u"182.92.12.11", wx.DefaultPosition, wx.DefaultSize, 0 )
    		bSizer5.Add( self.m_textCtrl_timeserver, 0, wx.ALL, 5 )
    		
    		
    		bSizer15.Add( bSizer5, 1, 0, 5 )
    		
    		bSizer6 = wx.BoxSizer( wx.HORIZONTAL )
    		
    		self.m_staticText4 = wx.StaticText( sbSizer1.GetStaticBox(), wx.ID_ANY, u"端口号:", wx.DefaultPosition, wx.DefaultSize, 0 )
    		self.m_staticText4.Wrap( -1 )
    		self.m_staticText4.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString ) )
    		
    		bSizer6.Add( self.m_staticText4, 0, wx.ALL, 5 )
    		
    		self.m_textCtrl_port = wx.TextCtrl( sbSizer1.GetStaticBox(), wx.ID_ANY, u"123", wx.DefaultPosition, wx.DefaultSize, 0 )
    		bSizer6.Add( self.m_textCtrl_port, 0, wx.ALL, 5 )
    		
    		
    		bSizer15.Add( bSizer6, 1, 0, 5 )
    		
    		bSizer7 = wx.BoxSizer( wx.HORIZONTAL )
    		
    		self.m_staticText5 = wx.StaticText( sbSizer1.GetStaticBox(), wx.ID_ANY, u"协议:", wx.DefaultPosition, wx.DefaultSize, 0 )
    		self.m_staticText5.Wrap( -1 )
    		self.m_staticText5.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString ) )
    		
    		bSizer7.Add( self.m_staticText5, 0, wx.ALL, 5 )
    		
    		self.m_textCtrl_protocol = wx.TextCtrl( sbSizer1.GetStaticBox(), wx.ID_ANY, u"SNTP", wx.DefaultPosition, wx.DefaultSize, 0 )
    		bSizer7.Add( self.m_textCtrl_protocol, 0, wx.ALL, 5 )
    		
    		
    		bSizer15.Add( bSizer7, 1, 0, 5 )
    		
    		
    		sbSizer1.Add( bSizer15, 0, wx.EXPAND, 5 )
    		
    		
    		bSizer1.Add( sbSizer1, 1, wx.EXPAND|wx.TOP|wx.RIGHT|wx.LEFT, 5 )
    		
    		sbSizer2 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"【Windows系统时间同步】" ), wx.HORIZONTAL )
    		
    		bSizer10 = wx.BoxSizer( wx.VERTICAL )
    		
    		bSizer51 = wx.BoxSizer( wx.HORIZONTAL )
    		
    		self.m_checkBox_win = wx.CheckBox( sbSizer2.GetStaticBox(), wx.ID_ANY, u"定时同步(ms):", wx.DefaultPosition, wx.DefaultSize, 0 )
    		bSizer51.Add( self.m_checkBox_win, 0, wx.ALL, 5 )
    		
    		self.m_textCtrl_timer_1 = wx.TextCtrl( sbSizer2.GetStaticBox(), wx.ID_ANY, u"1000", wx.DefaultPosition, wx.Size( -1,-1 ), 0 )
    		bSizer51.Add( self.m_textCtrl_timer_1, 0, wx.ALL, 5 )
    		
    		
    		bSizer10.Add( bSizer51, 0, 0, 5 )
    		
    		
    		sbSizer2.Add( bSizer10, 1, wx.EXPAND|wx.ALIGN_RIGHT, 5 )
    		
    		bSizer12 = wx.BoxSizer( wx.HORIZONTAL )
    		
    		self.m_button_win_sync_start = wx.Button( sbSizer2.GetStaticBox(), wx.ID_ANY, u"开始同步(B)", wx.Point( -1,-1 ), wx.DefaultSize, 0 )
    		self.m_button_win_sync_start.SetFont( wx.Font( 11, 72, 90, 92, False, "Times New Roman" ) )
    		
    		bSizer12.Add( self.m_button_win_sync_start, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 5 )
    		
    		self.m_button_win_sync_stop = wx.Button( sbSizer2.GetStaticBox(), wx.ID_ANY, u"暂停同步(B)", wx.Point( -1,-1 ), wx.DefaultSize, 0 )
    		self.m_button_win_sync_stop.SetFont( wx.Font( 11, 72, 90, 92, False, "Times New Roman" ) )
    		
    		bSizer12.Add( self.m_button_win_sync_stop, 0, wx.ALL, 5 )
    		
    		
    		sbSizer2.Add( bSizer12, 1, wx.ALIGN_CENTER|wx.RIGHT|wx.LEFT|wx.EXPAND, 5 )
    		
    		
    		bSizer1.Add( sbSizer2, 1, wx.EXPAND|wx.BOTTOM, 5 )
    		
    		sbSizer3 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"【Linux系统时间同步】" ), wx.VERTICAL )
    		
    		bSizer151 = wx.BoxSizer( wx.HORIZONTAL )
    		
    		bSizer52 = wx.BoxSizer( wx.HORIZONTAL )
    		
    		self.m_staticText31 = wx.StaticText( sbSizer3.GetStaticBox(), wx.ID_ANY, u"IP地址:", wx.DefaultPosition, wx.DefaultSize, 0 )
    		self.m_staticText31.Wrap( -1 )
    		self.m_staticText31.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString ) )
    		
    		bSizer52.Add( self.m_staticText31, 0, wx.ALL, 5 )
    		
    		self.m_textCtrl_linuxserver = wx.TextCtrl( sbSizer3.GetStaticBox(), wx.ID_ANY, u"192.168.1.205", wx.DefaultPosition, wx.DefaultSize, 0 )
    		bSizer52.Add( self.m_textCtrl_linuxserver, 0, wx.ALL, 5 )
    		
    		
    		bSizer151.Add( bSizer52, 1, wx.EXPAND, 5 )
    		
    		bSizer61 = wx.BoxSizer( wx.HORIZONTAL )
    		
    		self.m_staticText41 = wx.StaticText( sbSizer3.GetStaticBox(), wx.ID_ANY, u"用户名:", wx.DefaultPosition, wx.DefaultSize, 0 )
    		self.m_staticText41.Wrap( -1 )
    		self.m_staticText41.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString ) )
    		
    		bSizer61.Add( self.m_staticText41, 0, wx.ALL, 5 )
    		
    		self.m_textCtrl_username = wx.TextCtrl( sbSizer3.GetStaticBox(), wx.ID_ANY, u"root", wx.DefaultPosition, wx.DefaultSize, 0 )
    		bSizer61.Add( self.m_textCtrl_username, 0, wx.ALL, 5 )
    		
    		
    		bSizer151.Add( bSizer61, 1, wx.EXPAND, 5 )
    		
    		bSizer71 = wx.BoxSizer( wx.HORIZONTAL )
    		
    		self.m_staticText51 = wx.StaticText( sbSizer3.GetStaticBox(), wx.ID_ANY, u"密码:", wx.DefaultPosition, wx.DefaultSize, 0 )
    		self.m_staticText51.Wrap( -1 )
    		self.m_staticText51.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString ) )
    		
    		bSizer71.Add( self.m_staticText51, 0, wx.ALL, 5 )
    		
    		self.m_textCtrl_passwd = wx.TextCtrl( sbSizer3.GetStaticBox(), wx.ID_ANY, u"fatri123", wx.DefaultPosition, wx.DefaultSize, 0 )
    		bSizer71.Add( self.m_textCtrl_passwd, 0, wx.ALL, 5 )
    		
    		
    		bSizer151.Add( bSizer71, 1, wx.EXPAND, 5 )
    		
    		
    		sbSizer3.Add( bSizer151, 1, 0, 5 )
    		
    		bSizer14 = wx.BoxSizer( wx.HORIZONTAL )
    		
    		bSizer511 = wx.BoxSizer( wx.HORIZONTAL )
    		
    		self.m_checkBox_linux = wx.CheckBox( sbSizer3.GetStaticBox(), wx.ID_ANY, u"定时同步(ms):", wx.DefaultPosition, wx.DefaultSize, 0 )
    		bSizer511.Add( self.m_checkBox_linux, 0, wx.ALL, 5 )
    		
    		self.m_textCtrl_timer_2 = wx.TextCtrl( sbSizer3.GetStaticBox(), wx.ID_ANY, u"1000", wx.DefaultPosition, wx.Size( -1,-1 ), 0 )
    		bSizer511.Add( self.m_textCtrl_timer_2, 0, wx.ALL, 5 )
    		
    		
    		bSizer14.Add( bSizer511, 1, wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL|wx.TOP|wx.BOTTOM, 5 )
    		
    		bSizer121 = wx.BoxSizer( wx.HORIZONTAL )
    		
    		self.m_button_linux_sync = wx.Button( sbSizer3.GetStaticBox(), wx.ID_ANY, u"开始同步(B)", wx.Point( -1,-1 ), wx.DefaultSize, 0 )
    		self.m_button_linux_sync.SetFont( wx.Font( 11, 72, 90, 92, False, "Times New Roman" ) )
    		self.m_button_linux_sync.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_3DLIGHT ) )
    		
    		bSizer121.Add( self.m_button_linux_sync, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 5 )
    		
    		self.m_button_linux_sync_stop = wx.Button( sbSizer3.GetStaticBox(), wx.ID_ANY, u"暂停同步(B)", wx.Point( -1,-1 ), wx.DefaultSize, 0 )
    		self.m_button_linux_sync_stop.SetFont( wx.Font( 11, 72, 90, 92, False, "Times New Roman" ) )
    		
    		bSizer121.Add( self.m_button_linux_sync_stop, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 5 )
    		
    		
    		bSizer14.Add( bSizer121, 1, wx.EXPAND, 5 )
    		
    		
    		sbSizer3.Add( bSizer14, 1, wx.EXPAND, 5 )
    		
    		
    		bSizer1.Add( sbSizer3, 2, wx.EXPAND|wx.BOTTOM, 5 )
    		
    		sbSizer4 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, u"同步结果显示:" ), wx.HORIZONTAL )
    		
    		self.m_textCtrl_sync_result = wx.TextCtrl( sbSizer4.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( 500,200 ), wx.HSCROLL|wx.TE_MULTILINE )
    		sbSizer4.Add( self.m_textCtrl_sync_result, 0, wx.ALL, 5 )
    		
    		
    		bSizer1.Add( sbSizer4, 4, wx.EXPAND, 5 )
    		
    		
    		self.SetSizer( bSizer1 )
    		self.Layout()
    		
    		self.Centre( wx.BOTH )
    		
    		# Connect Events
    		self.m_checkBox_win.Bind( wx.EVT_CHECKBOX, self.m_checkBox_win_OnCheck )
    		self.m_button_win_sync_start.Bind( wx.EVT_BUTTON, self.m_button_win_sync_OnButtonClick )
    		self.m_button_win_sync_stop.Bind( wx.EVT_BUTTON, self.m_button_win_sync_stop_OnButtonClick )
    		self.m_checkBox_linux.Bind( wx.EVT_CHECKBOX, self.m_checkBox_linux_OnCheck )
    		self.m_button_linux_sync.Bind( wx.EVT_BUTTON, self.m_button_linux_sync_OnButtonClick )
    		self.m_button_linux_sync_stop.Bind( wx.EVT_BUTTON, self.m_button_linux_sync_stop_OnButtonClick )
        
        
    	
    	def __del__( self ):
    		pass
    	
    	
    	# Virtual event handlers, overide them in your derived class
    	def m_checkBox_win_OnCheck( self, event ):
    		event.Skip()
    	
    	def m_button_win_sync_OnButtonClick( self, event ):
    		event.Skip()
    	
    	def m_button_win_sync_stop_OnButtonClick( self, event ):
    		event.Skip()
    	
    	def m_checkBox_linux_OnCheck( self, event ):
    		event.Skip()
    	
    	def m_button_linux_sync_OnButtonClick( self, event ):
    		event.Skip()
    	
    	def m_button_linux_sync_stop_OnButtonClick( self, event ):
    		event.Skip()
    	
    


    主程序:time_sync_main.py

    # -*- coding: utf-8 -*-
    """
    Date: Wed Dec 12 13:28:23 2018
    
    Author: heat.huang
    """
    
    import paramiko
    import system_time_sync_frame
    
    import wx
    import time
    import socket  
    import struct  
    import win32api
    
    ###########  MainWindow  ###################
    class MainWindow(system_time_sync_frame.system_time_sync_frame):
        def __init__(self, parent):
            super().__init__(self)
            
            # 设置窗口标题的图标
            self.icon = wx.Icon('time.ico', wx.BITMAP_TYPE_ICO)
            self.SetIcon(self.icon)   
            
            #初始化复选框的定时间隔,用于判断复选框的勾选
            self.timer_val_win = 0
            self.timer_val_linux = 0       
            
            # 创建 win_sync 定时器
            self.timer_win = wx.Timer(self)
            self.Bind(wx.EVT_TIMER, self.win_time_sync, self.timer_win)
            # 创建 linux_sync 定时器
            self.timer_linux = wx.Timer(self)
            self.Bind(wx.EVT_TIMER, self.linux_time_sync, self.timer_linux)
            
        ######### 从网络时间服务器获取时间 ###############    
        def get_time_from_ntpserver(self):
            # 网络时间服务器地址 和端口号
            ntp_server = self.m_textCtrl_timeserver.GetValue()
            ntp_port = int(self.m_textCtrl_port.GetValue())
    	    
            # 使用socket创建一个IPv4的UDP连接
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            # 1900年1月1日00:00:00~1970年1月1日00:00:00的总秒数,2208988800s
            #  0x83aa7e80 是1900 到 1970年的总秒数,以十六进制表示
            time1990_1970 = 0x83aa7e80
            # 起始源时间戳--请求信息离开客户端的时间
    		# time.time()返回当前时间的时间戳(1970纪元后经过的浮点秒数)
            t1 = time.time()
            # 使用struct.pack()打包数据,B表示8bit,即一个字节宽度,
    		# I表示32bit,Q表示64bit
            # <<是左移,它后面跟的数字表示移动的位数
            # | 表示按位或运算符:只要对应的两个二进位有一个为1时,结果位就为1        
            ntppack = struct.pack("!BBBBIIIQQQQ",3<<6 | 3<<3 | 3,1,10,1,0,0,0,0,0,0,0)
            # 客户端向网络时间服务器发送请求
            sock.sendto(ntppack, (ntp_server,ntp_port))
            # 客户端接收到时间服务器返回的包
            resp, addr = sock.recvfrom(1024)
            # 目的地时间戳--信息包到达客户端的时间
            t4 = time.time()
            # 客户端使用struct.unpack()解包;
    		# 这里LI VN Mode Stratum Poll Precision放到一个64bit里面
            # struct.unpack(fmt,string)返回一个由解包数据(string)得到的一个元组(tuple)
            # ‘!’表示network(=big-endian 大端)        
            (vi, root, refeTime, oriTime, receTime, tranTime) = struct.unpack("!QQQQQQ", resp)
            # print(struct.unpack("!QQQQQQ",resp))
            # Mode=4 表示服务器返回标志,判断是否是时间服务器返回
            # 十进制4用二进制表示 0100,在64bit中,0100左移56位,
    		# 随后与64bit的vi进行或运算,以此判断...
            # ...Mode的值是否为4    
            if vi == vi | 4<<56:
                # 对解包得到的64bit总秒数取高32位,即获得整秒数,
    			# 并减去1900-1970年之间的秒数   
    			# 接收时间戳--服务器收到查询请求时间        
                receTime = (receTime/(2**32) - time1990_1970)  
    			# 传输时间戳--服务器回复时间信息包时间                                        
                tranTime = (tranTime/(2**32) - time1990_1970)                                          
                # 计算本地时钟补偿compensation time                                                    
                comTime = ((receTime - t1) + (tranTime - t4)) /2                                      
                # 计算往返传播延迟 propagation delay                                                   
                propaTime = (t4 - t1) + (tranTime - receTime)                                          
                # 将往返传播延时(ms)和本地时钟补偿(ms)信息添加到文本框                                                                              
                self.m_textCtrl_sync_result.AppendText('\n\n=====同步结果=====\n往返传播延时(ms):{0:3f} \n本地时钟补偿(ms):{1:3f}'.format(propaTime*1000,comTime*1000))       
                # 目的地时间戳加本地时钟补偿得到准确的本地时间precise local time                                 
                preTime = t4 + comTime                                                                                                                                               
                # time.strftime():
    			# Convert a time tuple to a string according to a format specification 
    			# 将精确的本地时间信息添加到文本框
                self.m_textCtrl_sync_result.AppendText('\n' + '' + time.strftime("Local Time: %Y-%m-%d %H:%M:%S", time.localtime(preTime))) 
            sock.close()  
            
            return preTime
        
        ######### Windows 系统时间同步 ###############  
        def win_time_sync(self, event):
            
            preTime = self.get_time_from_ntpserver()
            # 修改系统时间
            # gmtime([seconds]):
    		# Convert seconds since the Epoch to a time tuple expressing UTC (a.k.a.GMT)
            (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst)= time.gmtime(preTime) 
            
            # 调用win32api.SetSystemTime()修改系统时间
            win32api.SetSystemTime(tm_year, tm_mon, tm_wday, tm_mday, tm_hour, tm_min, tm_sec, 0)
            # 将成功修改系统时间信息添加到文本框
            self.m_textCtrl_sync_result.AppendText('\n' + 5*'*' + ' \nWindows系统时间修改成功 ' + '\n') 
        
        ######### Linux 系统时间同步 ###############  
        def linux_time_sync(self, event):
            try:
                ######## 创建SSH对象
                ssh = paramiko.SSHClient()
                ######## 允许连接不在know_hosts文件中的主机
                ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                
                host_name = self.m_textCtrl_linuxserver.GetValue()
                user_name =  self.m_textCtrl_username.GetValue()
                pass_word = self.m_textCtrl_passwd.GetValue()
                
        #        print("host_name: ", host_name)
        #        print("user_name: ", user_name)
        #        print("pass_word: ", pass_word)
                
                ####### 连接主控板
                ssh.connect(host_name, 22, user_name, pass_word)
                    
                ######## 执行命令
                
                # 得到精确的本地时间
                preTime = self.get_time_from_ntpserver()
                preDate = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(preTime))
                cmd = "date -s " + "\"" + preDate + "\""
                #print("cmd: ", cmd)
                
                stdin, stdout, stderr = ssh.exec_command(cmd)
                result = stdout.read()
                result = str(result, encoding = "utf-8")
                result = '\n' + 5*'*' + '\nLinux系统时间修改为:' + result + '\n' 
                self.m_textCtrl_sync_result.AppendText(result)
        #       print(result)
                
                ####### 关闭连接
                ssh.close()   
            except:
                Show_Message_Box("Linux系统时间同步失败!")
                
        
        ###############  定时同步设置  ################### 
        
        ### 定义函数--开始定时同步
        def OnStart_Win(self, event):
            # 时间间隔为毫秒量级,启动定时器
            self.timer_win.Start(int(self.m_textCtrl_timer_1.GetValue()))
        def OnStart_Linux(self, event):
            # 时间间隔为毫秒量级,启动定时器
            self.timer_linux.Start(int(self.m_textCtrl_timer_2.GetValue()))
        
        #### 定义函数--暂停定时同步
        def OnStop_Win(self, event):
            self.timer_win.Stop()  
            self.timer_val_win = 0
        def OnStop_Linux(self, event):
            self.timer_linux.Stop()
            self.timer_val_linux = 0        
    
        ############# 按键对应的响应函数 ####################
        ###### win  ######
        def m_checkBox_win_OnCheck( self, event ):   #复选框1
            self.timer_val_win = int(self.m_textCtrl_timer_1.GetValue())
        
        def m_button_win_sync_OnButtonClick( self, event ): # win-button-start
            #如果复选框被选中,调用定时启动函数,实现定时同步
            if self.timer_val_win > 0:
                self.OnStart_Win(event)
            else:
                self.win_time_sync(event)
                
        def m_button_win_sync_stop_OnButtonClick( self, event ):# win-button-stop
            if self.timer_val_win > 0:
                self.OnStop_Win(event)
            else:
                pass
        ###### linux  ######        
        def m_checkBox_linux_OnCheck( self, event ): #复选框2
            self.timer_val_linux = int(self.m_textCtrl_timer_2.GetValue()) 
            
        def m_button_linux_sync_OnButtonClick( self, event ):# linux-button-start
            #如果复选框被选中,调用定时启动函数,实现定时同步
            
            if self.timer_val_linux > 0:
                self.OnStart_Linux(event)
            else:
                self.linux_time_sync(event)        
    	
        def m_button_linux_sync_stop_OnButtonClick( self, event ): # linux-button-stop 
            if self.timer_val_linux > 0:
                self.OnStop_Linux(event)
            else:
                pass
    
    ## show message    
    def Show_Message_Box(message):
        dlg = wx.MessageDialog(None, message, "提示信息", wx.CLOSE | wx.ICON_QUESTION)
        if dlg.ShowModal() == wx.ID_CLOSE:
            dlg.Close(True)
        dlg.Destroy        
            
    ######### 执行主函数   ###############     
    if __name__ == '__main__':
        app = wx.App()
        main_win = MainWindow(None)  
        main_win.Show()
        app.MainLoop()          
        
    


    注意:exe可执行文件的打包使用 pyinstaller 工具,具体的打包方法参见论坛的文章:https://blog.csdn.net/zt_xcyk/article/details/73786659
      
     下面是我打包生成的exe文件。
    exe

    展开全文
  • PYTHON 网络编程

    千次阅读 2019-06-04 11:16:53
    Socket 编程实战 文章目录 Socket库 0.1. 什么是 Socket? 0.2. socket()函数 0.3. Socket 对象(内建)方法 0.4. 简单实例 0.5. Python Internet 模块 概述 Python socket API 2.1. TCP socket 2.2. UDP socket 常见...

    Socket 编程实战

    文章目录

    1. Socket库
      0.1. 什么是 Socket?
      0.2. socket()函数
      0.3. Socket 对象(内建)方法
      0.4. 简单实例
      0.5. Python Internet 模块
    2. 概述
    3. Python socket API
      2.1. TCP socket
      2.2. UDP socket
    4. 常见陷阱
      3.1. 忽略返回值
      3.2. 误认为 TCP 具有 framing
    5. TCP 的状态机
    6. 实战
      5.1. HTTP UA
      5.2. Unix_domain_socket
      5.3. ping
      5.4. netstat vs ss
    7. 总结
    8. SocketTool 编程调试工具

    Socket 在英文中的含义为“(连接两个物品的)凹槽”,像the eye socket,意为“眼窝”,此外还有“插座”的意思。在计算机科学中,socket 通常是指一个连接的两个端点,这里的连接可以是同一机器上的,像unix domain socket,也可以是不同机器上的,像network socket。

    本文着重介绍现在用的最多的 network socket,包括其在网络模型中的位置、API 的编程范式、常见错误等方面,最后用 Python 语言中的 socket API 实现几个实际的例子。Socket 中文一般翻译为“套接字”,不得不说这是个让人摸不着头脑的翻译,我也没想到啥“信达雅”的翻译,所以本文直接用其英文表述。本文中所有代码均可在 socket.py https://github.com/jiacai2050/socket.py仓库中找到。

    0 Socket库

    0.1 什么是 Socket?

    Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。

    0.2 socket()函数

    Python 中,我们用 socket()函数来创建套接字,语法格式如下:

    socket.socket([family[, type[, proto]]])
    

    参数

    • family: 套接字家族可以使AF_UNIX或者AF_INET
    • type: 套接字类型可以根据是面向连接的还是非连接分为SOCK_STREAM或SOCK_DGRAM
    • protocol: 一般不填默认为0.

    0.3 Socket 对象(内建)方法

    函数 描述
    服务器端套接字
    s.bind() 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。
    s.listen() 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
    s.accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来
    客户端套接字
    s.connect() 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
    s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
    公共用途的套接字函数
    s.recv() 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
    s.send() 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
    s.sendall() 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
    s.recvfrom() 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
    s.sendto() 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
    s.close() 关闭套接字
    s.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
    s.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port)
    s.setsockopt(level,optname,value) 设置给定套接字选项的值。
    s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。
    s.settimeout(timeout) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
    s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
    s.fileno() 返回套接字的文件描述符。
    s.setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
    s.makefile() 创建一个与该套接字相关连的文件

    0.4 简单实例

    服务端

    我们使用 socket 模块的 socket 函数来创建一个 socket 对象。socket 对象可以通过调用其他函数来设置一个 socket 服务。

    现在我们可以通过调用 bind(hostname, port) 函数来指定服务的 port(端口)。

    接着,我们调用 socket 对象的 accept 方法。该方法等待客户端的连接,并返回 connection 对象,表示已连接到客户端。

    完整代码如下:

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    # 文件名:server.py
     
    import socket               # 导入 socket 模块
     
    s = socket.socket()         # 创建 socket 对象
    host = socket.gethostname() # 获取本地主机名
    port = 12345                # 设置端口
    s.bind((host, port))        # 绑定端口
     
    s.listen(5)                 # 等待客户端连接
    while True:
        c,addr = s.accept()     # 建立客户端连接
        print '连接地址:', addr
        c.send('欢迎访问菜鸟教程!')
        c.close()                # 关闭连接
    
    客户端

    接下来我们写一个简单的客户端实例连接到以上创建的服务。端口号为 12345。

    socket.connect(hosname, port ) 方法打开一个 TCP 连接到主机为 hostname 端口为 port 的服务商。连接后我们就可以从服务端获取数据,记住,操作完成后需要关闭连接。

    完整代码如下:

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    # 文件名:client.py
     
    import socket               # 导入 socket 模块
     
    s = socket.socket()         # 创建 socket 对象
    host = socket.gethostname() # 获取本地主机名
    port = 12345                # 设置端口号
     
    s.connect((host, port))
    print s.recv(1024)
    s.close()
    

    现在我们打开两个终端,第一个终端执行 server.py 文件:

    $ python server.py
    

    第二个终端执行 client.py 文件:

    $ python client.py 
    
    

    这时我们再打开第一个终端,就会看到有以下信息输出:

    连接地址: ('192.168.0.118', 62461)
    

    0.5 Python Internet 模块

    以下列出了 Python 网络编程的一些重要模块:

    协议 功能用处 端口号 Python 模块
    HTTP 网页访问 80 httplib, urllib, xmlrpclib
    NNTP 阅读和张贴新闻文章,俗称为"帖子" 119 nntplib
    FTP 文件传输 20 ftplib, urllib
    SMTP 发送邮件 25 smtplib
    POP3 接收邮件 110 poplib
    IMAP4 获取邮件 143 imaplib
    Telnet 命令行 23 telnetlib
    Gopher 信息查找 70 gopherlib, urllib

    更多内容可以参阅官网的 Python Socket Library and Modules。
    https://docs.python.org/2/library/socket.html

    2 篇笔记

    关于简单实例 Mac 上运行不出来,有几个报错,做了下修改。

    server.py

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    
    import socket
    # 建立一个服务端
    server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind(('localhost',6999)) #绑定要监听的端口
    server.listen(5) #开始监听 表示可以使用五个链接排队
    while True:# conn就是客户端链接过来而在服务端为期生成的一个链接实例
        conn,addr = server.accept() #等待链接,多个链接的时候就会出现问题,其实返回了两个值
        print(conn,addr)
        while True:
            try:
                data = conn.recv(1024)  #接收数据
                print('recive:',data.decode()) #打印接收到的数据
                conn.send(data.upper()) #然后再发送数据
            except ConnectionResetError as e:
                print('关闭了正在占线的链接!')
                break
        conn.close()
    

    client.py

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    
    import socket# 客户端 发送一个数据,再接收一个数据
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #声明socket类型,同时生成链接对象
    client.connect(('localhost',6999)) #建立一个链接,连接到本地的6969端口
    while True:
        # addr = client.accept()
        # print '连接地址:', addr
        msg = '欢迎访问菜鸟教程!'  #strip默认取出字符串的头尾空格
        client.send(msg.encode('utf-8'))  #发送一条信息 python3 只接收btye流
        data = client.recv(1024) #接收一个信息,并指定接收的大小 为1024字节
        print('recv:',data.decode()) #输出我接收的信息
    client.close() #关闭这个链接
    

    这个可以的。

    关于简单实例都修改。

    服务端:

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    
    import sys
    reload(sys)
    sys.setdefaultencoding('utf8')
    import socket
    # 建立一个服务端
    server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind(('localhost',9090)) #绑定要监听的端口
    server.listen(5) #开始监听 表示可以使用五个链接排队
    while True:# conn就是客户端链接过来而在服务端为期生成的一个链接实例
        conn,addr = server.accept() #等待链接,多个链接的时候就会出现问题,其实返回了两个值
        print(conn,addr)
        while True:
            data = conn.recv(1024)  #接收数据
            print('recive:',data.decode()) #打印接收到的数据
            conn.send(data.upper()) #然后再发送数据
        conn.close()
    

    客户端:

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    
    import sys
    reload(sys)
    sys.setdefaultencoding('utf8')
    import socket# 客户端 发送一个数据,再接收一个数据
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #声明socket类型,同时生成链接对象
    client.connect(('localhost',9090)) #建立一个链接,连接到本地的6969端口
    while True:
        # addr = client.accept()
        # print '连接地址:', addr
        msg = '欢迎访问菜鸟教程!'  #strip默认取出字符串的头尾空格
        client.send(msg.encode('utf-8'))  #发送一条信息 python3 只接收btye流
        data = client.recv(1024) #接收一个信息,并指定接收的大小 为1024字节
        print('recv:',data.decode()) #输出我接收的信息
    client.close() #关闭这个链接
    

    1 概述

    Socket 作为一种通用的技术规范,首次是由 Berkeley 大学在 1983 为 4.2BSD Unix 提供的,后来逐渐演化为 POSIX 标准。Socket API 是由操作系统提供的一个编程接口,让应用程序可以控制使用 socket 技术。Unix 哲学中有一条一切皆为文件,所以 socket 和 file 的 API 使用很类似:可以进行read、write、open、close等操作。

    现在的网络系统是分层的,理论上有OSI模型,工业界有TCP/IP协议簇。其对比如下:
    在这里插入图片描述

    osi vs tcp/ip

    每层上都有其相应的协议,socket API 不属于TCP/IP协议簇,只是操作系统提供的一个用于网络编程的接口,工作在应用层与传输层之间:

    where socket works in tcp/ip
    where socket works in tcp/ip

    我们平常浏览网站所使用的http协议,收发邮件用的smtp与imap,都是基于 socket API 构建的。

    一个 socket,包含两个必要组成部分:

    地址,由 ip 与 端口组成,像192.168.0.1:80。
    协议,socket 所是用的传输协议,目前有三种:TCP、UDP、raw IP。
    地址与协议可以确定一个socket;一台机器上,只允许存在一个同样的socket。TCP 端口 53 的 socket 与 UDP 端口 53 的 socket 是两个不同的 socket。

    根据 socket 传输数据方式的不同(使用协议不同),可以分为以下三种:

    • Stream sockets,也称为“面向连接”的 socket,使用 TCP 协议。实际通信前需要进行连接,传输的数据没有特定的结构,所以高层协议需要自己去界定数据的分隔符,但其优势是数据是可靠的。
    • Datagram sockets,也称为“无连接”的 socket,使用 UDP 协议。实际通信前不需要连接,一个优势时 UDP 的数据包自身是可分割的(self-delimiting),也就是说每个数据包就标示了数据的开始与结束,其劣势是数据不可靠。
    • Raw sockets,通常用在路由器或其他网络设备中,这种 socket 不经过TCP/IP协议簇中的传输层(transport layer),直接由网络层(Internet layer)通向应用层(Application layer),所以这时的数据包就不会包含 tcp 或 udp 头信息。

    数据包在各个层间的变更
    数据包在各个层间的变更

    2 Python socket API

    Python 里面用(ip, port)的元组来表示 socket 的地址属性,用AF_*来表示协议类型。
    数据通信有两组动词可供选择:send/recv 或 read/write。read/write 方式也是 Java 采用的方式,这里不会对这种方式进行过多的解释,但是需要注意的是:

    read/write 操作的具有 buffer 的“文件”,所以在进行读写后需要调用flush方法去真正发送或读取数据,否则数据会一直停留在缓冲区内。

    2.1 TCP socket

    TCP socket 由于在通信前需要建立连接,所以其模式较 UDP socket 复杂些。具体如下:

    TCP socket API
    TCP socket API

    API 的具体含义这里不在赘述,可以查看手册,https://en.wikipedia.org/wiki/Berkeley_sockets#Socket_API_functions
    这里给出 Python 语言实现的 echo server。

    echo_server.py https://github.com/jiacai2050/socket.py/blob/master/simple_tcp_echo/echo_server.py

    import socket
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    def handler(client_sock, addr):
        try:
            print('new client from %s:%s' % addr)
            msg = client_sock.recv(100)
            client_sock.send(msg)
            print('received data[%s] from %s:%s' % ((msg,) + addr))
        finally:
            client_sock.close()
            print('client[%s:%s] socket closed' % addr)
    
    if __name__ == '__main__':
        # 设置 SO_REUSEADDR 后,可以立即使用 TIME_WAIT 状态的 socket
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind(('', 5500))
        sock.listen(5)
    
        while 1:
            client_sock, addr = sock.accept()
            handler(client_sock, addr)
    

    echo_client.py https://github.com/jiacai2050/socket.py/blob/master/simple_tcp_echo/echo_client.py

    import socket
    
    if __name__ == '__main__':
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        data_to_sent = 'hello tcp socket'
        try:
            sock.connect(('', 5500))
    
            sent = sock.send(data_to_sent)
            print(sock.recv(1024))
        finally:
            sock.close()
    print('socket closed')
    

    上面代码有一点需要注意:server 端的 socket 设置了SO_REUSEADDR为1,目的是可以立即使用处于TIME_WAIT状态的socket,那么TIME_WAIT又是什么意思呢?后面在讲解 tcp 状态机时再做详细介绍。

    2.2 UDP socket

    udp_socket_api
    udp_socket_api

    UDP 版的 socket server 的代码在进行bind后,无需调用listen方法。

    udp_echo_server.py https://github.com/jiacai2050/socket.py/blob/master/simple_udp_echo/echo_server.py

    import socket
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 设置 SO_REUSEADDR 后,可以立即使用 TIME_WAIT 状态的 socket
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('', 5500))
    # 没有调用 listen
    
    if __name__ == '__main__':
        while 1:
            data, addr = sock.recvfrom(1024)
    
            print('new client from %s:%s' % addr)
            sock.sendto(data, addr)
    

    udp_echo_client.py https://github.com/jiacai2050/socket.py/blob/master/simple_udp_echo/echo_client.py

    
    import socket
    
    udp_server_addr = ('', 5500)
    
    if __name__ == '__main__':
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        data_to_sent = 'hello udp socket'
        try:
            sent = sock.sendto(data_to_sent, udp_server_addr)
            data, server = sock.recvfrom(1024)
            print('receive data:[%s] from %s:%s' % ((data,) + server))
        finally:
            sock.close()
    

    3 常见陷阱

    3.1 忽略返回值

    本文中的 echo server 示例因为篇幅限制,也忽略了返回值。网络通信是个非常复杂的问题,通常无法保障通信双方的网络状态,很有可能在发送/接收数据时失败或部分失败。所以有必要对发送/接收函数的返回值进行检查。本文中的 tcp echo client 发送数据时,正确写法应该如下:

    total_send = 0
    content_length = len(data_to_sent)
    while total_send < content_length:
        sent = sock.send(data_to_sent[total_send:])
        if sent == 0:
            raise RuntimeError("socket connection broken")
        total_send += total_send + sent
    

    同理,接收数据时也应该检查返回值:

    chunks = []
    bytes_recd = 0
    while bytes_recd < MSGLEN:   # MSGLEN 为实际数据大小
        chunk = self.sock.recv(min(MSGLEN - bytes_recd, 2048))
        if chunk == b'':
            raise RuntimeError("socket connection broken")
        chunks.append(chunk)
        bytes_recd = bytes_recd + len(chunk)
    return b''.join(chunks)
    

    send/recv操作的是网络缓冲区的数据,它们不必处理传入的所有数据。

    一般来说,当网络缓冲区填满时,send函数就返回了;当网络缓冲区被清空时,recv 函数就返回。

    可以通过下面的方式设置缓冲区大小。

    s.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, buffer_size)  # 发送
    s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, buffer_size)  # 接受
    

    3.2 误认为 TCP 具有 framing

    TCP 不提供 framing,这使得其很适合于传输数据流。这是其与 UDP 的重要区别之一。UDP 是一个面向消息的协议,能保持一条消息在发送者与接受者之间的完备性。

    Framing capabilities of UDP and the lack of framing in TCP
    Framing capabilities of UDP and the lack of framing in TCP

    代码示例参考:framing_assumptions
    https://github.com/jiacai2050/socket.py/tree/master/framing_assumptions

    4 TCP 的状态机

    在前面echo server 的示例中,提到了TIME_WAIT状态,为了正式介绍其概念,需要了解下 TCP 从生成到结束的状态机。(图片来源)

    tcp_state transition
    tcp_state transition

    这个状图转移图非常非常关键,也比较复杂,总共涉及了 11 种状态。我自己为了方便记忆,对这个图进行了拆解,仔细分析这个图,可以得出这样一个结论:

    连接的打开与关闭有被动(passive)与主动(active)两种情况。主动关闭时,涉及到的状态转移最多,包括FIN_WAIT_1、FIN_WAIT_2、CLOSING、TIME_WAIT。(是不是有种 no zuo no die 的感觉)

    此外,由于 TCP 是可靠的传输协议,所以每次发送一个数据包后,都需要得到对方的确认(ACK),有了上面这两个知识后,再来看下面的图:(图片来源)

    tcp 关闭时的状态转移时序图
    tcp 关闭时的状态转移时序图

    我们重点分析上图中链接断开的过程,其中主动关闭端为 Client,被动关闭端为 Server 。

    • Client 调用 close 方法的同时,会向 Server 发送一个 FIN,然后自己处于 FIN_WAIT_1 状态,在收到 server ACK 回应后变为 FIN_WAIT_2
    • Server 收到 FIN 后,向 Client 回复 ACK 确认,状态变化为 CLOSE_WAIT,然后开始进行一些清理工作
    • 在 Server 清理工作完成后,会调用close方法,这时向 Client 发送 FIN 信号,状态变化为 LAST_ACK
    • Client 接收到 FIN 后,状态由 FIN_WAIT_2 变化为 TIME_WAIT,同时向 Server 回复 ACK
    • Server 收到 ACK 后,状态变化为 CLOSE,表明 Server 端的 socket 已经关闭
    • 处于 TIME_WAIT 状态的 Client 不会立刻转为 CLOSED 状态,而是需要等待 2MSL(max segment life,一个数据包在网络传输中最大的生命周期),以确保 Server 能够收到最后发出的 ACK。如果 Server 没有收到最后的 ACK,那么 Server 就会重新发送 FIN,所以处于TIME_WAIT的 Client 会再次发送一个 ACK 信号,这么一来(FIN来)一回(ACK),正好是两个 MSL 的时间。如果等待的时间小于 2MSL,那么新的 socket 就可以收到之前连接的数据。

    上面是正常逻辑时的关闭顺序,如果任意一步出现问题都会导致 Socket 状态变化出现问题,下面说几种常见的问题:

    • 在上述过程第二步,回复完 ACK 后,如果忘记调用 CLOSE 方法,那么 Server 端在会一直处于 CLOSE_TIME 状态,处于 FIN_WAIT_2 状态的 Client 端会在 60 秒后超时,直接关闭。这个问题的具体案例可参考《This is strictly a violation of the TCP specification》https://blog.cloudflare.com/this-is-strictly-a-violation-of-the-tcp-specification
    • 前面 echo server 的示例也说明了,处于 TIME_WAIT 并不是说一定不能使用,可以通过设置 socket 的 SO_REUSEADDR 属性以达到不用等待 2MSL 的时间就可以复用socket 的目的,当然,这仅仅适用于测试环境,正常情况下不要修改这个属性。

    5 实战

    5.1 HTTP UA

    http 协议是如今万维网的基石,可以通过 socket API 来简单模拟一个浏览器(UA)是如何解析 HTTP 协议数据的。

    import socket
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    baidu_ip = socket.gethostbyname('baidu.com')
    sock.connect((baidu_ip, 80))
    print('connected to %s' % baidu_ip)
    
    req_msg = [
        'GET / HTTP/1.1',
        'User-Agent: curl/7.37.1',
        'Host: baidu.com',
        'Accept: */*',
    ]
    delimiter = '\r\n'
    
    sock.send(delimiter.join(req_msg))
    sock.send(delimiter)
    sock.send(delimiter)
    
    print('%sreceived%s' % ('-'*20, '-'*20))
    http_response = sock.recv(4096)
    print(http_response)
    

    运行上面的代码可以得到下面的输出

    --------------------received--------------------
    HTTP/1.1 200 OK
    Date: Tue, 01 Nov 2016 12:16:53 GMT
    Server: Apache
    Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
    ETag: "51-47cf7e6ee8400"
    Accept-Ranges: bytes
    Content-Length: 81
    Cache-Control: max-age=86400
    Expires: Wed, 02 Nov 2016 12:16:53 GMT
    Connection: Keep-Alive
    Content-Type: text/html
    
    <html>
    <meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
    </html>
    

    http_response是通过直接调用recv(4096)得到的,万一真正的返回大于这个值怎么办?我们前面知道了 TCP 协议是面向流的,它本身并不关心消息的内容,需要应用程序自己去界定消息的边界,对于应用层的 HTTP 协议来说,有几种情况,最简单的一种时通过解析返回值头部的Content-Length属性,这样就知道body的大小了,对于 HTTP 1.1版本,支持Transfer-Encoding: chunked传输,对于这种格式,这里不在展开讲解,大家只需要知道, TCP 协议本身无法区分消息体就可以了。对这块感兴趣的可以查看 CPython 核心模块 http.client

    5.2 Unix_domain_socket

    UDS 用于同一机器上不同进程通信的一种机制,其API适用与 network socket 很类似。只是其连接地址为本地文件而已。

    代码示例参考:uds_server.py、uds_client.py https://github.com/jiacai2050/socket.py/blob/master/in_action/uds_server.py

    5.3 ping

    ping 命令作为检测网络联通性最常用的工具,其适用的传输协议既不是TCP,也不是 UDP,而是 ICMP。
    ICMP 消息(messages)通常用于诊断 IP 协议产生的错误,traceroute 命令也是基于 ICMP 协议实现。利用 Python raw sockets API 可以模拟发送 ICMP 消息,实现类似 ping 的功能。

    代码示例参考:ping.py https://github.com/jiacai2050/socket.py/blob/master/in_action/ping.py

    5.4 netstat vs ss

    netstat 与 ss 都是类 Unix 系统上查看 Socket 信息的命令。netstat 是比较老牌的命令,常用的选择有

    • -t,只显示 tcp 连接
    • -u,只显示 udp 连接
    • -n,不用解析hostname,用 IP 显示主机,可以加快执行速度
    • -p,查看连接的进程信息
    • -l,只显示监听的连接

    ss 是新兴的命令,其选项和 netstat 差不多,主要区别是能够进行过滤(通过state与exclude关键字)。

    $ ss -o state time-wait -n | head
    Recv-Q Send-Q             Local Address:Port               Peer Address:Port
    0      0                 10.200.181.220:2222              10.200.180.28:12865  timer:(timewait,33sec,0)
    0      0                      127.0.0.1:45977                 127.0.0.1:3306   timer:(timewait,46sec,0)
    0      0                      127.0.0.1:45945                 127.0.0.1:3306   timer:(timewait,6.621ms,0)
    0      0                 10.200.181.220:2222              10.200.180.28:12280  timer:(timewait,12sec,0)
    0      0                 10.200.181.220:2222              10.200.180.28:35045  timer:(timewait,43sec,0)
    0      0                 10.200.181.220:2222              10.200.180.28:42675  timer:(timewait,46sec,0)
    0      0                      127.0.0.1:45949                 127.0.0.1:3306   timer:(timewait,11sec,0)
    0      0                      127.0.0.1:45954                 127.0.0.1:3306   timer:(timewait,21sec,0)
    0      0               ::ffff:127.0.0.1:3306           ::ffff:127.0.0.1:45964  timer:(timewait,31sec,0)
    

    这两个命令更多用法可以参考:

    6 总结

    我们的生活已经离不开网络,平时的开发也充斥着各种复杂的网络应用,从最基本的数据库,到各种分布式系统,不论其应用层怎么复杂,其底层传输数据的的协议簇是一致的。Socket 这一概念我们很少直接与其打交道,但是当我们的系统出现问题时,往往是对底层的协议认识不足造成的,希望这篇文章能对大家编程网络方面的程序有所帮助。

    7. SocketTool 编程调试工具

    作为网络工程师,我们经常需要在本地电脑上建立Socket服务端或客户端来测试软件,比如建立Socket服务端,就可以等待网络客户端软件的连接,通过和客户端进行通信来测试客户端软件是否正常;同时也可以建立Socket客户端来连接对方的测试服务器软件,来测试对方的服务器软件是否正常。
    在这里插入图片描述

    工具/原料

    SocketTool

    方法/步骤

    1 在百度中搜索SocketTool可以找到该测试软件的下载地址。

    在这里插入图片描述

    2 SocketTool怎么用/如何建立Socket服务端/客户端

    在这里插入图片描述
    下载完成后,软件是一个单独的运行程序,可以直接打开软件。

    3 软件的界面很简单,在左侧有tcp和udp的客户端或服务端的快捷按钮,上方有【创建】【删除】【退出】等选项按钮。

    在这里插入图片描述

    4 我们先来建立TCP的测试服务端。点击【TCP Server】再点击【创建】。

    在这里插入图片描述
    选择一个监听端口,这里我们使用6001作为服务端的监听端口。
    在这里插入图片描述
    建立完成后,服务端会自动启动,软件会显示【启动监听】的状态。
    在这里插入图片描述
    我们可以检测一下本机的6001端口是否已经打开。在DOS窗口中输入命令【netstat -a】,可以在列表中看到本机的6001端口的状态为listening的状态,表示本机的6001端口正处于监听的状态。
    在这里插入图片描述
    在DOS窗口中输入命令【telnet 192.168.0.140 6001】来登录本地的6001端口。
    在这里插入图片描述
    点击回车键,就可以成功登录6001端口。在测试软件中就可以看到状态是已连接的状态,同时也可以看到对方的ip就是本地ip。
    在这里插入图片描述
    在这里插入图片描述
    再来测试通信情况,在DOS窗口中输入a、b、c,在软件的接收窗口就可以看到收到的数据了。
    在这里插入图片描述
    在软件的发送窗口中输入1234567890,点击发送后,在DOS窗口中就可以看到软件发送过来的数据了。
    在这里插入图片描述
    测试完成后,在软件中点击【停止监听】,同时在DOS窗口中可以看到【失去了跟主机的连接】,表示测试连接已经断开。
    在这里插入图片描述
    在这里插入图片描述
    再来创建TCP的客户端,点击【TCP Client】再点击【创建】。会弹出【创建socket客户端】窗口,输入对方的ip和对方的端口,点击确认。
    在这里插入图片描述
    tcp的客户端已经建立好,如果对方的端口监听正常的话,点击【连接】就可以连接到对方的端口和对方进行测试通信了。
    在这里插入图片描述

    展开全文
  • python学习计划大全(从入门到放弃)

    千次阅读 多人点赞 2017-11-08 23:18:22
    python界面编程 python面向对象高级语法 命名空间和作用域应用案例分析 项目:图形界面实现数据查询、python实战2048、语音对话开发、语音控制开发 第二阶段-语言高级(15天) python处理txt,csv,pdf,jsons ...
  • Linux环境下Python的安装过程

    万次阅读 多人点赞 2012-06-22 13:35:51
    Linux环境下Python的安装过程 前言 一般情况下,Linux都会预装 Python了,但是这个预装的Python版本一般都非常低,很多 Python的新特性都没有,必须重新安装新一点的版本,从下边的截图,可以看到我的 ...
  • Python学习路线介绍

    千次阅读 2018-09-20 14:10:05
    3.Python网络编程 4.Web编程基础 5.MySQL 6.Django构架 7.Git与RabbitMQ 8.爬虫与Scrapy 9.实际应用 首先说说为什么要学习Linux操作系统,实际开发中很多的场景下面都需要使用到Linux操作系统,Linux操作系.....
  • 黑马程序员全新的Python入门教程来袭,目前正在连载中,16天的学习,让你由浅入深,深入学习Python编程! 讲解方式: python编程进阶,在跨过入门阶段以后更进一步的学习,依旧是由浅入深,通俗易懂,层层深入。 ...
  • Python初级入门精讲

    万人学习 2020-08-25 15:56:32
    本课程为Python全栈开发初级入门篇-语言基础章节,学习完本篇章可对python语言有初步的掌握与理解,本课程侧重于初级学员,课程内容详细有针对性,务求各种类型的学员都可以掌握python开发。
  • 为什么要选择Python? Python作为目前Linux系统下最流行...在网络应用,文本解析方面,Python编程有着其他语言无可比拟的优势。同时Python也是面向对象并且跨平台的语言,可以在linux/Unix、OSX、windows上无障碍运行。
  • Linux平台下Python脚本编程入门(一)

    万次阅读 2018-05-07 10:26:53
    原文地址:http://developer.51cto.com/art/201608/516305.htm众所周知,系统管理员需要精通一门脚本语言,而且招聘机构列出的职位需求上也会这么...首先,我们会使用 Python 的命令行工具,还会接触到 Python 的面...
  • Linux下安装matplotlib

    万次阅读 2018-05-05 16:49:23
    一:在Python编程从入门到实践这本书之中讲到linux下安装matplotlib 直接说是使用了一下命令 sudo apt-get install python3-matplotlib如果是Python2.7则使用sudo apt-get install python-matplotlib如果是比较新的...
  • &#13; &#13; &#13; &#13; &#13; &#13; &#13; 最近碰到的项目大多是多种语言与python混合编程,C、C++、Python等语言的编译环境linu...
  • Python最佳学习路线图

    万次阅读 多人点赞 2018-08-07 18:21:48
    python语言基础 (1)Python3入门,数据类型,字符串 (2)判断/循环语句,函数,命名空间...(1)Python常见第三方库与网络编程 (2)Python正则表达式 (3)邮箱爬虫,文件遍历,金融数据爬虫,多线程爬虫 (4)P...
  • 不懂编程的运维人员到底还能走多远? 首先,可以肯定的说,未来的IT岗位需要的是综合能力强的人员,运维、开发、数据库、网络,技术岗位对上述知识体系都要会一些,才能很好的胜任对应岗位工作。 下面已经不是...
  • 最新Python全栈工程师学习路线(初级+高级+大神)

    万次阅读 多人点赞 2018-11-23 13:34:44
    IT行业,技术要比学历、年龄、从业经验更为重要,技术水平直接决定就业薪资,想要学好python,首先要先了解精通Python语言基础、Python web开发、Python爬虫、Python数据分析这四大方面。 零基础学习需要要从如下几...
  • Python3.7学习笔记】一、环境搭建

    千次阅读 2019-11-14 11:26:57
    Python3.7学习笔记】一,环境搭建 【Python3.7学习笔记】一,环境搭建 Markdown和扩展Markdown简洁的语法 代码块高亮 图片链接和图片上传 LaTex数学公式 UML序列图和流程图 离线写博客 导入导出Markdown文件 ...
  • Linux下查看Python安装路径

    千次阅读 2018-07-15 12:26:17
    python在下载、安装好之后,需要配置环境变量。程序和可执行文件可以在许多目录,而这些路径很可能不...可通过以下命令查看Python安装路径 ,进入Python的交互式编程模式后。inport sys print sys.path特别注意的是,...
  • python用ThinkPad好还是MacBook好?

    千次阅读 2019-01-09 15:19:10
    我想题主的意图应该是做Python开发吧,如果是Python开发,还要看一下开发方向,如果是网络爬虫、服务器后端编程类的,那还是Windows系统上手更快,使用也方便。 如果要做一些机器学习、人工智能,很可能后面要应用...
  • python编程软件有哪些?

    万次阅读 2018-10-31 11:42:21
    Python开发软件可根据其用途不同分为两种,一种是Python代码编辑器,一种是Python集成开发工具,两者的配合使用可以极大的提高Python开发人员的编程效率,以下是常用的几款Python代码编辑器和Python集成开发工具。...
1 2 3 4 5 ... 20
收藏数 126,156
精华内容 50,462
关键字:

python网络编程linux