• 21网络编程(socket、黏包现象、socketserver模块)


    前言:鉴于socket实际工作基本不会直接接触,但是面试经常问。所以不花太多时间在这里。

    1、利用socket来实现最基础的网络通信。

      服务端:

    # coding:utf-8
    import socket
    # 创建socket对象
    server = socket.socket()
    
    # 绑定IP和端口
    server.bind(('127.0.0.1', 8000))
    
    # 连接数
    server.listen(5)
    
    # 等待客户端来连接,如果没人来就等待
    # conn是客户端和服务端连接的对象(伞),服务端以后要通过该对象进行收发数据。
    # addr是客户端的地址信息
    conn, addr = server.accept()
    
    
    # 通过对象去获取,(客户端通过伞发送的消息)
    data = conn.recv(1024)
    print(data)
    
    # 服务端通过连接对象(伞)给客户端回复消息
    conn.send(b'stop')
    
    # 与客户端断开连接(放开伞)
    conn.close()
    
    # 关闭服务端的连接。
    server.close()

      用户端:

    import socket
    
    client = socket.socket()
    
    # 向服务端发起连接请求(递伞)
    # 阻塞,知道连接成功后才会继续向下走
    client.connect(('127.0.0.1', 8000))
    
    
    # 链接上服务端后,向服务端发送消息。
    client.send(b'hello')
    
    # 等待服务端回消息
    data = client.recv(1024)
    print(data)
    
    client.close()

      上述代码实现了一个很简单的功能。服务端等待用户连接。用户端连接之后发送一个hello然后关闭连接。服务端收到之后回复一个stop。然后断开连接,关闭服务。

    2、黏包

      黏包产生的原因:连续多次消息的快速发送到接收方的缓存池中。接收方取的时候无法正确判断每次接收的消息长度(也就是无法正确区分消息与消息之间的边界值)。

      黏包产生的结果:最简单的是消息会乱掉。上次没接收完的消息,会混在下次接收的头部。具体看场景。

      解决黏包的方法:struct模块的pack和unpack两个方法。首先用pack对于字符串进行压缩,这样长度就固定了。然后接收后unpack解压缩,循环取值拼接(笨方法就是接收方每次接收完给个回复,然后发送方再进行下一次发送。还有一种就是发送方每次发送等待个1s左右。)

      如下:一个简单的文件上传的例子

      项目结构如下:

      

      客户端代码:

    # coding:utf-8
    import socket
    import os
    import json
    import struct
    import hashlib
    sock = socket.socket()
    sock.connect(('127.0.0.1', 8800))
    
    
    while True:
        cmd = input("请输入命令:")
        if cmd == 'exit':
            break
    
        action, filename = cmd.strip().split(" ")
        filesize = os.path.getsize(filename)
        if action == 'put':
            file_info = {
                "action": action,  # put
                "filename": filename,  # 文件名
                "filesize": filesize,  # 文件大小
            }
            file_info_json = json.dumps(file_info).encode('utf8')
            ret = struct.pack('i', len(file_info_json))
            print(len(ret))
    
            # 发送file_info_json的打包长度
            sock.send(ret)
            # 发送file_info_json字节串
            sock.send(file_info_json)
    
            md5 = hashlib.md5()
            # 发送文件数据
            with open(filename, 'rb') as f:
                for line in f:
                    sock.send(line)
                    md5.update(line)
            data = sock.recv(1024).decode('utf8')
            if data == '200':
                print(md5.hexdigest())
                md5_val = md5.hexdigest()
                sock.send(md5_val.encode('utf8'))
                valid = sock.recv(1024).decode('utf8')
                if valid == '100':
                    print('文件上传成功')
                else:
                    print('文件上传失败!')
            else:
                print('服务器出错了!')
        else:
            print('请输入正常的命令')

      服务端代码:

    # coding:utf-8
    import socket
    import json
    import struct
    import hashlib
    sock = socket.socket()
    sock.bind(('127.0.0.1', 8800))
    sock.listen(5)
    
    while True:
        print("server is working.......")
        conn, addr = sock.accept()
        while True:
            # 接收json的打包长度
            file_info_lenght_pack = conn.recv(4)
            file_info_lenght = struct.unpack('i', file_info_lenght_pack)[0]
    
            # 接收json字符串
            file_info_json = conn.recv(file_info_lenght).decode('utf8')
            file_info = json.loads(file_info_json)
    
            action = file_info.get('action')
            filename = file_info.get('filename')
            filesize = file_info.get('filesize')
    
            md5 = hashlib.md5()
    
            # 循环接收文件
            with open("put/"+filename, "wb") as f:
                recv_data_length = 0
                while recv_data_length < filesize:
                    data = conn.recv(1024)
                    recv_data_length += len(data)
                    f.write(data)
                    md5.update(data)
                    print("文件总大小:%s,已成功接收%s" % (filesize, recv_data_length))
            print('接收成功')
            conn.send(b'200')
            print(md5.hexdigest())
            md5_val = md5.hexdigest()   # 服务端的md5
            client_md5 = conn.recv(1024).decode('utf8')     # 接收用户端的md5。会黏包。要处理下。
            if md5_val == client_md5:
                conn.send(b'100')
            else:
                conn.send(b'10000')

      注:这里因为最后用md5做了个文件一致性的对比。所以struct显得反倒没那么突出了。理解能用就行。

    3、socketserver模块

    import socketserver
    '''
    在socketserver模块中
    conn = self.request
    '''
    
    
    class Myserver(socketserver.BaseRequestHandler):
    
        def handle(self):
            '''
            这里写逻辑代码
            :return:
            '''
            pass
    
    
    # 相当于 1、创建socket对象   2、self.socket.bing()  3、self.socket.listen(5)
    server = socketserver.ThreadingTCPServer(('127.0.0.1', 8899), Myserver)
    
    # 相当于执行server.accept(),返回两个参数。
    server.serve_forever()

      socketserver就相当于把最开始的几行代码封装起来了。并且还开了个多线程。不过这里稍微了解下感觉就ok了。  

    
    
  • 相关阅读:
    Linux Enterprise Cluster NOtes Ch4 同步:ssh和rsync
    e805上不安装中文外挂支持中文,很简单而且实用
    ARM的一些概念性问题
    C#调用WORD处理的小项目 转
    ASP.NET面试题
    .net清除cookie代码|.net为什么不能清除cookie|.net cookie 过期代码
    c#字符串转数字的函数|c#字符串转数字的无错函数|c#字符串转数字的最好函数
    Wiki简介
    new 和override 重写区别
    禁用IE的后退按钮|显示网页已过期|几种语言的实现方法|c#|javascript|html
  • 原文地址:https://www.cnblogs.com/cbslock/p/11280567.html
Copyright © 2020-2023  润新知