在 python 中发送和接收包裹

Sending and receiving package in python

这是 TCP 套接字服务器上的控制台聊天应用程序。客户端将 request/message 发送到服务器,服务器将消息分发给目标用户或提供请求 information.I am currently 运行 into a problem regarding recv package on the server side .我收到包裹并能够打印出来。但是系统出于某种原因仍然给我一个语法错误。

谢谢。

这是我的客户:

import socket
import select
import errno
import sys, struct
import pickle

HEADER_LENGTH = 1024
IP = "127.0.0.1"
PORT = 9669

def send_login_request(username):
    package = [1]
    length = len(username)
    if length > 1019:
        print ("Error: Username too long")
        sys.exit()
    package += struct.pack("I", length)
    package += username
    
    return package

def send_message(recv_id, message):
    package = [2]
    length = len(message)
    if length > 1015:
        print('message too long')
        sys.exit()
    package += recv_id
    package += struct.pack('I', length)
    package += message
    return package

def send_con_request(conv_id):
    package = [3]
    length = len(id)
    if length > 1015:
        print('id too long')
        sys.exit()
    package += struct.pack("I", length)
    package += conv_id
    return package
# Create a socket

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Connect to a given ip and port
client_socket.connect((IP, PORT))

client_socket.setblocking(False)
my_username = input("Username: ")
request = send_login_request(my_username)
user_request = str(request)
client_socket.send(user_request.encode())
username_conf = client_socket.recv(HEADER_LENGTH).decode()
if username_conf == "Welcome to the server":
    con_id = input("Please enter conversation's id, if don't have one, please enter no ")
    if con_id == 'no':
        con_request = send_con_request(con_id)
        con_request = str(con_request)
        client_socket.send(con_request.encode())

    else: 
        con_request = send_con_request(con_id)
        con_request = str(con_request)
        client_socket.send(con_request.encode())

    conversation = client_socket.recv(HEADER_LENGTH).decode()

    recv_id = input("Please enter receiver's id")

    while True:
            # Wait for user to input a message

            message = input(f'{my_username} > ').encode()
            # If message is not empty - send it
            if message:

                send_message = send_message(recv_id,message)
                client_socket.send(bytes(send_message))

            try:

                while True:
                    message_receiver = client_socket.recv(HEADER_LENGTH).decode()
                    x = message_receiver.split('|')
                    print(x)
                    username = x[0]
                    message = x[1]
                    # Print message
                    print(f'{username} > {message}')

            except IOError as e:

                if e.errno != errno.EAGAIN and e.errno != errno.EWOULDBLOCK:
                    print('Reading error: {}'.format(str(e)))
                    sys.exit()

                # We just did not receive anything
                continue

            except Exception as e:
                # Any other exception - something happened, exit
                print('Reading error: {}'.format(str(e)))
                sys.exit()

这是我的服务器:

import socket
import select
import struct
import sys
import pickle

HEADER_LENGTH = 1024
conversation ={}
users = [
    {
        'username': 'user1',
        'user_id': 1
    },
    {
        'username': 'user2',
        'user_id': 2
    },
    {
        'username': 'user3',
        'user_id': 3
    },
    {
        'username': 'user4',
        'user_id': 4
    },
    {
        'username': 'user5',
        'user_id': 5
    }
]

def login(username):
    for user in users:
        if user['username'] == username:
            return user
        else: 
            return False
        

IP = "127.0.0.1"
PORT = 9669

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

server_socket.bind((IP, PORT))

server_socket.listen()

# List of sockets for select.select()
sockets_list = [server_socket]

# List of connected clients - socket as a key, user header and name as data
clients_socket = {}

sessions = {
    (1,2) : '1.txt',

    (3,4) : '2.txt'

}




def getRecvSocket(user_id):
    try:
        return sessions[user_id]
    except:
        return None

def sendErrorMes(socketid, mes):
    package = [9]
    length = len(mes)
    if length > 1019:
        length = 1019
    package += struct.pack("I", length)
    package += mes
    

print(f'Listening for connections on {IP}:{PORT}...')

# Handles message receiving

def receive_message(client_socket):

    try:
        receive_message = client_socket.recv(HEADER_LENGTH)
        return receive_message
    except:
        return False

while True:

    read_sockets, _, exception_sockets = select.select(sockets_list, [], sockets_list)

    # Iterate over notified sockets
    for notified_socket in read_sockets:

        # If notified socket is a server socket - new connection, accept it
        if notified_socket == server_socket:

            client_socket, client_address = server_socket.accept()
            
            sockets_list.append(client_socket)
            
        else:
        # Receive message
            
            package = receive_message(notified_socket)
            print(package)
            package_recv = eval(package.decode())
            print(package_recv)
            print(type(package_recv))
            package_type = package_recv[0]
            if package_type == 1:
                size = struct.unpack("I", package[1:5])
                if size[0] > 1019:
                    continue
                username = package[5:5+size[0]]
                username = username.decode()
                # username = package_recv[1]
                user = login(username)
                if user == False: 
                    notified_socket.send("no user found".encode())
                else: 
                    sessions[user["user_id"]] = notified_socket
                    notified_socket.send(("Welcome to the server").encode())
            elif package_type == 2:
                recv_id = struct.unpack("I", package[1:5])
                size = struct.unpack("I", package[5:9])
                if size[0] > 1015:
                    continue
                # recv_id = package_recv[1]
                if getRecvSocket(recv_id) == None:
                    sendErrorMes(notified_socket, "User is offline")
                else:
                    message = package[9:9+size[0]]
                    # message = package_recv[2]
                    for socket in sessions.values(): 
                        if socket == notified_socket: 
                            user = sessions[notified_socket]

                            # print(f'Received message from {user}, {message}')
            
                    # fIterate over connected clients and broadcast message
                    for client_socket in clients_socket:
                        # if clients[client_socket] == receive_user and client_socket != notified_socket:
                        # But don't sent it to sender
                        if client_socket != notified_socket and clients_socket[client_socket] == recv_id:

                            # Send user and message (both with their headers)
                            # We are reusing here message header sent by sender, and saved username header send by user when he connected
                            a = sessions[notified_socket]
                            b = recv_id 
                            with open(f"{conversation[a,b]}.txt", "w"):
                                f.write(user + message)


                            client_socket.send((user + "|" + message).encode())
                            
                    if message is False:
                        # print('Closed connection from: {}'.format(user))

                        # Remove from list for socket.socket()
                        sockets_list.remove(notified_socket)

                        # Remove from our list of users
                        del clients_socket[notified_socket]

                        continue
            
            elif package_type == 3: 
                size = struct.unpack("I", package[1:5])
                if size[0] > 1019:
                    continue
                convo_id = package[5:5+size[0]]
                convo_id = convo_id.decode()
                # convo_id = package_recv[2]
                if convo_id in conversation:
                    with open(conversation[convo_id], 'rb') as file_to_send:
                        for data in file_to_send:
                            notified_socket.sendall(data)
                    print('send successful')

                else: 
                    f = open(f"{len(conversation)+1}.txt", "w+")

这是服务器端的错误,我正在定位和解决问题:

Listening for connections on 127.0.0.1:9669...
b"[1, 5, 0, 0, 0, 'u', 's', 'e', 'r', '1']"
[1, 5, 0, 0, 0, 'u', 's', 'e', 'r', '1']
<class 'list'>
b''
Traceback (most recent call last):
  File "c:/Users/Duong Dang/Desktop/bai 2.3/server.py", line 134, in <module>
    package_recv = eval(package.decode())
  File "<string>", line 0

    ^
SyntaxError: unexpected EOF while parsing

发送代码没有多大意义。您正在创建一个 python 列表,这是实现协议的最奇怪的方式。然后,您将获取该列表的 python 字符串表示形式并将其发送到服务器。您没有在服务器端做任何事情来确保您收到完整的消息。然后您使用 eval 来解释您在客户端创建的字符串。这是一种非常危险的做法,因为您的同行基本上可以指示您的 python 口译员做任何事情。

此外,您的 send_con_request 正在调用 len(id) 这根本不起作用,因为 id 是 python built-in 不提供__len__ 方法。我认为应该是 len(conv_id)?

无论如何,你应该修改你的协议。使用 struct 工具创建所需的正确二进制字符串。有很多可能的方法来构建它,但这里有一个。在客户端,创建一个 fixed-length header 来标识您发送的请求类型以及剩余“有效负载”字节的长度。您将首先使用 str.encode.

将您的字符串(用户名或其他)转换为字节
import struct

# ProtoHeader encodes a 16 bit request identifer, plus a 32 bit payload
# length. A protocol data unit consists of this 6-byte header followed by 
# payload bytes (which will vary according to the request)
ProtoHeader = struct.Struct("!HI")

LoginRequest = 1
SomeOtherRequest = 2
...

def format_login_request(username):
    """ Create a protocol block containing a user login request.
        Return the byte string containing the encoded request """
    username_bytes = username.encode()
    proto_block = ProtoHeader.pack(LoginRequest, len(username_bytes)) + username_bytes
    return proto_block

...

conn.sendall(format_login_request(username))

在服务器端,您将首先收到 fixed-length header(它告诉您请求类型是什么以及存在多少其他有效负载字节)。然后接收那些剩余的字节,以确保您得到那么多。 socket.recv 不保证您将从对等方接收到任何特定 send 中发送的字节数。它确实 保证您会以正确的顺序收到它们,因此您必须继续收到,直到收到您期望的号码。这就是为什么将固定长度的字节字符串作为 header 并对可变长度有效负载中预期的字节数进行编码很重要。

服务器看起来像这样:

import struct

ProtoHeader = struct.Struct("!HI")
LoginRequest = 1

def receive_bytes(conn, count):
    """ General purpose receiver:
        Receive exactly @count bytes from @conn """
    buf = b''
    remaining = count
    while remaining > 0:
        # Receive part or all of data
        tbuf = conn.recv(remaining)
        tbuf_len = len(tbuf)
        if tbuf_len == 0:
            # Really you probably want to return 0 here if buf is empty and
            # allow the higher-level routine to determine if the EOF is at
            # a proper message boundary in which case, you silently close the
            # connection. You would normally only raise an exception if you 
            # EOF in the *middle* of a message.
            raise RuntimeError("end of file")
        buf += tbuf
        remaining -= tbuf_len
    return buf

def receive_proto_block(conn):
    """ Receive the next protocol block from @conn. Return a tuple of
        request_type (integer) and payload (byte string) """

    proto_header = receive_bytes(conn, ProtoHeader.size)
    request_type, payload_length = ProtoHeader.unpack(proto_header)
    payload = receive_bytes(conn, payload_length)
    return request_type, payload

...

request_type, payload = receive_proto_block(conn)
if request_type == LoginRequest:
    username = payload.decode()