简体   繁体   English

在Python中通过sockets收发对象

[英]Send and receive objects through sockets in Python

I have searched a lot on the Inte.net, but I haven't been able to find the solution to send an object over the socket and receive it as is.我在 Inte.net 上搜索了很多,但一直无法找到通过套接字发送 object 并按原样接收的解决方案。 I know it needs pickling which I have already done.我知道它需要我已经做过的酸洗。 And that converts it to bytes and is received on the other hand.然后将其转换为字节并在另一方面接收。 But how can I convert those bytes to that type of object?但是如何将这些字节转换为 object 的类型呢?

process_time_data = (current_process_start_time, current_process_end_time)
prepared_process_data = self.prepare_data_to_send(process_time_data)
data_string = io.StringIO(prepared_process_data)
data_string =  pack('>I', len(data_string)) + data_string
self.send_to_server(data_string)

This is the code which is converting the object to StringIO on the client and sending to the server.这是在客户端将 object 转换为 StringIO 并发送到服务器的代码。 And on the server side I am getting bytes.在服务器端,我正在获取字节。 Now I am searching for bytes to be converted to StringIO again so that I can get the object value.现在我正在搜索要再次转换为 StringIO 的字节,以便我可以获得 object 值。

In the code, Object is wrapped in StringIO and is being sent over the socket.在代码中,Object 包装在 StringIO 中并通过套接字发送。 Is there a better approach?有更好的方法吗?

The server-side code is as follows.服务器端代码如下。

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#server.setblocking(0)
server.bind(('127.0.0.1', 50000))
server.listen(5)
inputs = [server]
outputs = []
message_queues = {}

while inputs:
    readable, writeable, exceptional = select.select(inputs, outputs, inputs)
    for s in readable:
        if s is server:
            connection, client_address = s.accept()
            print(client_address)
            connection.setblocking(0)
            inputs.append(connection)
            message_queues[connection] = queue.Queue()
            print('server started...')
        else:
            print('Getting data step 1')
            raw_msglen = s.recv(4)
            msglen = unpack('>I', raw_msglen)[0]
            final_data = b''
            while len(final_data) < msglen:
                data = s.recv(msglen - len(final_data))
                if data:
                    #print(data)
                    final_data += data
                    message_queues[s].put(data)
                    if s not in outputs:
                        outputs.append(s)
                    else:
                        if s in outputs:
                            outputs.remove(s)
                else:
                    break
            inputs.remove(connection)
            #s.close()
            del message_queues[s]

            process_data = ProcessData()
            process_screen = ProcessScreen()

            if final_data is not None:
                try:
                    deserialized_data = final_data.decode("utf-8")
                    print(deserialized_data)
                except (EOFError):
                    break
            else:
                print('final data is empty.')

            print(process_data.project_id)
            print(process_data.start_time)
            print(process_data.end_time)
            print(process_data.process_id)

The two helper functions are as follows:两个辅助函数如下:

def receive_all(server, message_length, message_queues, inputs, outputs):
    # Helper function to recv message_length bytes or return None if EOF is hit
    data = b''
    while len(data) < message_length:
        packet = server.recv(message_length - len(data))
        if not packet:
            return None
        data += packet
        message_queues[server].put(data)
        if server not in outputs:
            outputs.append(server)
        else:
            if server in outputs:
                outputs.remove(server)
    inputs.remove(server)
    del message_queues[server]
    return data


def receive_message(server, message_queues, inputs, outputs):
    # Read message length and unpack it into an integer
    raw_msglen = receive_all(server, 4, message_queues, inputs, outputs)
    if not raw_msglen:
        return None
    message_length = unpack('>I', raw_msglen)[0]

    return receive_all(server, message_length, message_queues, inputs, outputs)

And two of the model classes are as follows:而model类中的两个类如下:

class ProcessData:
    process_id = 0
    project_id = 0
    task_id = 0
    start_time = 0
    end_time = 0
    user_id = 0
    weekend_id = 0

# Model class to send image data to the server
class ProcessScreen:
    process_id = 0
    image_data = bytearray()

What you're looking for here is pickle and the loads and dumps operations.您在这里寻找的是pickle以及loadsdumps操作。 Sockets are basically byte streams.套接字基本上是字节流。 Let us consider the case you have.让我们考虑一下您的情况。

class ProcessData:
    process_id = 0
    project_id = 0
    task_id = 0
    start_time = 0
    end_time = 0
    user_id = 0
    weekend_id = 0

An instance of this class needs to be pickled into a data string by doing data_string = pickle.dumps(ProcessData()) and unpickled by doing data_variable = pickle.loads(data) where data is what is received.此类的一个实例需要通过执行data_string = pickle.dumps(ProcessData())来腌制为数据字符串,并通过执行data_variable = pickle.loads(data)data_variable = pickle.loads(data) ,其中data是接收到的内容。

So let us consider a case where the client creates an object of ProcessData and sends it to server.因此,让我们考虑一个客户端创建ProcessData对象并将其发送到服务器的情况。 Here's what the client would look like.这是客户端的样子。 Here's a minimal example.这是一个最小的例子。

Client客户

import socket, pickle

class ProcessData:
    process_id = 0
    project_id = 0
    task_id = 0
    start_time = 0
    end_time = 0
    user_id = 0
    weekend_id = 0


HOST = 'localhost'
PORT = 50007
# Create a socket connection.
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

# Create an instance of ProcessData() to send to server.
variable = ProcessData()
# Pickle the object and send it to the server
data_string = pickle.dumps(variable)
s.send(data_string)

s.close()
print 'Data Sent to Server'

Now your server which receives this data looks as follows现在您接收此数据的服务器如下所示

Server服务器

import socket, pickle

class ProcessData:
    process_id = 0
    project_id = 0
    task_id = 0
    start_time = 0
    end_time = 0
    user_id = 0
    weekend_id = 0


HOST = 'localhost'
PORT = 50007
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connected by', addr

data = conn.recv(4096)
data_variable = pickle.loads(data)
conn.close()
print data_variable
# Access the information by doing data_variable.process_id or data_variable.task_id etc..,
print 'Data received from client'

Running the server first creates a bind on the port and then running the client makes the data transfer via the socket.运行服务器首先在端口上创建一个bind ,然后运行client通过套接字进行数据传输。 You could also look at this answer你也可以看看这个答案

Pickle is not particularly safe for network communications, because it can be used to inject executable code. Pickle 对于网络通信并不是特别安全,因为它可用于注入可执行代码。 I suggest you try json instead.我建议你试试 json 。

pseudocode:伪代码:

import json
to_send  = json.dumps(object)
s.sendall (to_send)

An option is to use JSON serialization.一种选择是使用 JSON 序列化。 However Python Object are not serializable, so you have to map your class object into Dict first, using either function vars (preferred) or the built-in __dict__ .但是 Python 对象不可序列化,因此您必须首先使用函数vars (首选)或内置__dict__将类对象映射到Dict

Adapting the answer from Sudheesh Singanamalla and based on this answer :改编来自Sudheesh Singanamalla的答案并基于此答案

Client客户

import socket, json

class ProcessData:
    process_id = 0
    project_id = 0
    task_id = 0
    start_time = 0
    end_time = 0
    user_id = 0
    weekend_id = 0


HOST = 'localhost'
PORT = 50007
# Create a socket connection.
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

# Create an instance of ProcessData() to send to server.
variable = ProcessData()

# Map your object into dict 
data_as_dict = vars(variable)

# Serialize your dict object
data_string = json.dumps(data_as_dict)

# Send this encoded object
s.send(data_string.encode(encoding="utf-8"))

s.close()
print 'Data Sent to Server'

Server服务器

import socket, json

class ProcessData:
    process_id = 0
    project_id = 0
    task_id = 0
    start_time = 0
    end_time = 0
    user_id = 0
    weekend_id = 0


HOST = 'localhost'
PORT = 50007
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connected by', addr

data_encoded = conn.recv(4096)
data_string = data_encoded.decode(encoding="utf-8")

data_variable = json.loads(data_string)
# data_variable is a dict representing your sent object

conn.close()
print 'Data received from client'

Warning警告

One important point is that dict mapping of an object instance does not map class variable , only instance variable .重要的一点是对象实例的字典映射不映射class variable ,只映射instance variable See this anwser for more information.有关更多信息,请参阅此anwser Example:例子:

class ProcessData:
    # class variables
    process_id = 0 
    project_id = 1

    def __init__(self):
        # instance variables
        self.task_id = 2
        self.start_time = 3

obj = ProcessData()
dict_obj = vars(obj)

print(dict_obj)
# outputs: {'task_id': 2, 'start_time': 3}

# To access class variables:
dict_class_variables = vars(ProcessData)

print(dict_class_variables['process_id'])
# outputs: 0

Shameless plug here, but a friend and I have recently released tlspyo , an open-source library whose purpose is to help you transfer python objects over.network easily and in a secure fashion.无耻地插在这里,但我和一个朋友最近发布了tlspyo ,这是一个开源库,其目的是帮助您轻松安全地通过网络传输 python 个对象。

Transferring pickled objects via Inte.net sockets without using something like tlspyo is basically an open door for hackers, so don't do it.通过 Inte.net sockets 传输 pickled 对象而不使用 tlspyo 之类的东西基本上是为黑客敞开的大门,所以不要这样做。

With tlspyo, your code looks like this:使用 tlspyo,您的代码如下所示:

Server:服务器:

from tlspyo import Relay

if __name__ == "__main__":
    my_server = Relay(port=3000,password="<same strong password>")

    # (...)

Client 1:客户 1:

from tlspyo import Endpoint

if __name__ == "__main__":
    client_1 = Endpoint(
        ip_server='<ip of your server>'
        port=3000,
        password="<same strong password>",
        groups="client 1")

    # send an object to client 2:
    my_object = "my object"  # doesn't have to be a string, of course
    client_1.broadcast(my_object, "client 2")

    # (...)

Client 2:客户 2:

from tlspyo import Endpoint

if __name__ == "__main__":
    client_2 = Endpoint(
        ip_server='<ip of my Relay>'
        port=3000,
        password="<same strong password>",
        groups="client 2")

    # receive the object sent by client 1:
    my_object = client_2.receive_all(blocking=True)[0]


    # (...)

(You will need to setup TLS for this code to work, check out the documentation - or you can disable TLS using security=None, but if you are transferring over the Inte.net you don't want to do that.) (您需要设置 TLS 才能使此代码正常工作,请查看文档- 或者您可以使用 security=None 禁用 TLS,但如果您通过 Inte.net 进行传输,则不想这样做。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM