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了。  


原文地址:https://www.cnblogs.com/cbslock/p/11280567.html