• 关于socket知识整理


    一个完整的计算机系统是由硬件、操作系统、应用软件三者组成,具备了这三个条件,一台计算机系统就可以玩单机游戏。如果你想上网(访问个黄色网站,发个黄色微博啥的),就需要遵守网络协议,即计算机之间交流的标准,按照分工不同可以把互联网协议从逻辑上划分层级,即ios七层协议,详见另一篇博客。参考博客(网络通信原理):http://www.cnblogs.com/linhaifeng/articles/5937962.html

      先从服务器端说起,服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

    服务端套接字函数

    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.close() 关闭套接字

      根据相关流程和函数,先来实现一个简单的通讯。

    server端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import socket
     
    server = socket.socket()
    server.bind(('127.25.0.0',8080))    #绑定ip,端口
    server.listen(3)     #python3中listen必须有参数,次数设为3,排队等待个数为3
    while True:     #循环接收不同ip,端口信息
        print('等待联机...')
        conn, addr = server.accept()     #阻塞
        print('new conn:',addr)
        while True:     #循环接收同个ip,端口的信息
            try:    #异常处理,在服务端接收、发送失败时,提示客户端断开
                data = conn.recv(1024).decode('utf-8')
                print('recv:',data)
                while True:     #若输入为空,重新输入
                    msg = input('>>>:').strip()
                    if len(msg) == 0:continue
                    else:break
                conn.send(msg.encode('utf-8'))  #发送
            except ConnectionResetError as reason:
                print('client has lost... ')
                break
     
    server.close()

    client端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import socket
     
    client = socket.socket()
    client.connect(('127.25.0.0',8080))     #连接服务端ip,端口
    while True:     #为空,重新输入
        msg = input('>>>:').strip()
        if len(msg) == 0:continue
        client.send(msg.encode('utf-8'))    #发送
        data = client.recv(1024).decode('utf-8')   #接收
        print('recv:',data)
         
    client.close()

      这样,在服务端先运行的情况下,客户端能进行连接,实现一收一发的通讯方式,第一个连接未断开时,后面的连接只能等待前者断开,否则客户端无法接收后者的消息。要注意的是,当第一个连接断开时,会出现异常。在Windows中,出现ConnectionResetError的报错,可以利用try...except...进行异常捕捉处理。在linux中,客户端断开时,服务端无限接收空值,我们可以利用if进行判断。除了简单通讯,利用一收一发的原理,我们可以实现客户端命令服务端,并接收返回的命令结果。

    server端

    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
    import socket,os
     
    server = socket.socket()
    server.bind(('localhost',9999))
    server.listen(3)
    while True:
        print('等待联机...')
        conn, addr = server.accept()  
        print('new conn:',addr)
        while True:
            print('准备接收指令...')
            cmd = conn.recv(1024).decode('utf-8')
            if not cmd:     #在linux中,客户端断开即无限发送空值
                print('客服端已断开... ')
                break
            print('执行指令:',cmd)
            data = os.popen(cmd).read()    #接收字符串,执行结果也是字符串
            data = data.encode('utf-8')
            data_size = len(data)
            conn.send(str(data_size).encode('utf-8'))   #发送数据容量大小
            if data_size == 0:  #若指令不存在,则返回数据大小为0,只发送数据大小,不发送内容
                print('指令不存在...')
                continue
            conn.send(data.encode('utf-8'))     #发送数据
            print('执行完毕... ')
                                                                                                
    server.close()

    client端

    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
    import socket
     
    client = socket.socket()
    client.connect(('localhost',9999))
    while True:
        cmd = input('输入指令,q退出 >>>:')
        if len(cmd) == 0:
            print('指令不能为空...')
            continue
        if cmd == 'q':break     #退出
        client.send(cmd.encode('utf-8'))    #发送指令
        data_size = client.recv(1024)   #接收数据容量大小
        data_size = int(data_size.decode('utf-8'))
        if data_size == 0:  #若接收到的数据容量为0,说明指令不存在
            print('指令不存在...')
            continue
        print('接收数据大小',data_size)
        received_size = 0   #已接收的数据大小
        received_data = b'' #已接收的数据
        while data_size > received_size:    #循环接收数据,并计算接收到的数据大小
            #最后一次接收数据为剩余数据,防止连续接收导致出现粘包现象
            if data_size - received_size > 1024:    #接收不止一次
                size = 1024
            else:
                size = data_size - received_size    #最后一次接收,剩多少收多少
            data = client.recv(size)
            received_size += len(data)
            received_data += data
        else:
            print('执行完毕',received_size)     #接收的数据大小
            print()     #为了美观,数据单独打印
            print(received_data.decode('utf-8'))    #打印数据
             
    client.close()

      特别注意,在TCP中,当两次连续接收时会出现粘包的现象,即(1)发送数据时间间隔很短,数据了很小,会合到一起,产生粘包(2)客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包。解决粘包问题的方法,即(1)利用time模块,time.sleep(0.5)增长大约0.5的发送间隔(2)在两次发送之间插入一次接收(3)严格限制每次接收的数据大小。下面,我们来模拟客户端在服务端中下载文件。

    server端

    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
    import socket,os,hashlib
     
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(3)
    while True:
        print('等待联机...')
        conn, addr = server.accept()  
        print('new conn:',addr)
        while True:
            print('准备接收指令...')
            cmd = conn.recv(1024).decode('utf-8')   #接收客户端指令
            if not cmd:     #在linux中,客户端断开即无限发送空值
                print('客服端已断开... ')
                break
            cmd, filename = cmd.split()     #如:文件发送,send filename,get filename
            print(filename)
            if os.path.isfile(filename):
                = open(filename,'rb')
                = hashlib.md5()
                file_size = os.stat(filename).st_size   #获得文件大小
                conn.send(str(file_size).encode('utf-8'))   #发送文件大小
                conn.recv(1024)     #等待客户端确认
                for line in f:
                    m.update(line)
                    conn.send(line)
                f.close()
                conn.send(m.hexdigest().encode('utf-8'))   #发送md5
            print('发送完毕... ')
                 
    server.close()

    client端

    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
    import socket,hashlib
     
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    while True:
        cmd = input('>>>:').strip()     #执行下载
        if len(cmd) == 0:continue
        if cmd.startswith('get'):
            client.send(cmd.encode('utf-8'))
            data_size = int(client.recv(1024).decode('utf-8'))
            print('文件大小为:',data_size)
            client.send('准备接收...'.encode())     #客户端确认接收
            received_size = 0
            filename = cmd.split()[1]
            = open(filename+'.new','wb')
            = hashlib.md5()
            while data_size > received_size:
                if data_size - received_size > 1024:    #不止一次接收,接收1024
                    size = 1024
                else:
                    size = data_size - received_size    #最后一次接收,接收全部
                data = client.recv(size)
                received_size += len(data)
                m.update(data)
                f.write(data)
            else:
                f.close()
                new_md5 = m.hexdigest()     #客户端md5
                print('接受完毕...')
            old_md5 = client.recv(1024).decode('utf-8')     #接收服务端md5
            print('server file md5:',old_md5)   #验证md5
            print('client file md5:',new_md5)
     
    client.close()

      此时,依然是一收一发的方式,限制了我们的交流的效率,总觉得太low了。

    server端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import socketserver,time
     
    class MyTCPHandler(socketserver.BaseRequestHandler):
        def handle(self):   
            while True:
                try:
                    self.data = self.request.recv(1024).decode('utf-8')
                    print('来自:',self.client_address)
                    print('[%s]:%s ' % (time.ctime(),self.data))
                    self.request.send(self.data.upper().encode('utf-8'))
                except ConnectionResetError as reason:
                    print('{}已下线... '.format(self.client_address))
                    break
     
    if __name__ == '__main__':
        HOST, PORT = 'localhost'9999
        ADDR = (HOST, PORT)
        server = socketserver.ThreadingTCPServer(ADDR, MyTCPHandler)
        server.serve_forever()

    client端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import socket
     
    HOST, PORT = 'localhost'9999
    ADDR = (HOST, PORT)
     
    client = socket.socket()
    client.connect(ADDR)
    while True:
        msg = input('>>>:').strip()
        if len(msg) == 0:continue
        client.send(msg.encode('utf-8'))
        data = client.recv(1024)
        print('recv:',data.decode('utf-8'))
     
    client.close()

    效果图

      这样我们实现了单服务端与多客户端进行交流,此处用到的是多线程。此外,关于多线程,多进程,协程的原理和方法,之后再重新开篇单独记叙。

    此处留连接地址将来用。

    经过了多次实践,已经掌握了简单通讯,远程命令,文件下载,一对多交流。可以开发一个支持多用户在线的FPT程序,要求如下:

    1.用户加密认证

    2.允许多用户同时登录

    3.每个用户拥有自己的家目录,且只能访问自己的家目录

    4.对用户进行磁盘配额,每个用户可用空间不同

    5.允许用户在ftp server上随意切换目录

    6.允许用户查看当前的目录下的文件

    7.允许上传和下载文件,保证文件的一致性

    8.文件传输过程中显示进度条

    9.附加功能:支持文件的断点续传及下载

    此处留连接地址将来用。

  • 相关阅读:
    PHP生成名片、网址二维码
    利用google api生成二维码名片
    PHP实现视频文件上传完整实例
    delphi程序中定义热键
    如何制作手机自适应网页
    取MAC地址 (含多网卡),最好的方法,支持Vista,Win7
    PHP压缩与解压Zip(PHPZip类)
    如何在mysql中存储音乐和图片文件
    viewFlipper 之二
    ViewFlipper
  • 原文地址:https://www.cnblogs.com/pyxiaomangshe/p/7823376.html
Copyright © 2020-2023  润新知