У меня есть сервер, написанный на Python, который запускает новый поток с новым клиентским сокетом для каждого подключаемого клиента.
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((socket.gethostname(), server_port))
s.listen(5)
while True:
(clientsock, address) = s.accept()
# Instantiates a 'Client' object
user = client.client(clientsock, address)
# Set up client thread
client_thread = threading.Thread(target=do_client_operations , args=(user,))
client_thread.start()
В каждом порожденном потоке сервер вводит свой собственный цикл отправки / получения с подключенным клиентским сокетом и обрабатывает сообщения от клиента.
Я реализовал простой протокол обмена сообщениямимежду сервером и клиентом, и несколько автоматических сообщений, чтобы проверить, подключен ли этот конкретный сокет.Когда клиент сбрасывает соединение, сервер обновляет свой «список» тех, кто все еще подключен соответственно.
Эта функция работает нормально, но часто, когда клиенты отправляют конкретные сообщения на сервер, например, Клиент 1 спрашивает: «Скажите, кто в данный момент подключен к серверу», сервер отправляет ответвместо этого для клиента 2, как если бы client2.recv()
собирал сообщение до client1.recv()
В некоторых исследованиях кажется, что сокеты не являются поточно-ориентированными, и что написание программы таким способом не былохороший выбор дизайна (я начал этот проект очень давно, когда я был новичком в Python)
Мне кажется, что я не понимаю, почему два отдельных сокета в двух отдельных потоках «получают» из одного сетевого буфера?Делают ли это несколько сокетов, созданных одним и тем же процессом независимо от потоков?
К сожалению, я зашел слишком далеко, чтобы переписать эту логику было бы огромной задачей.Я думаю о том, чтобы добавить какое-то дополнение к протоколу обмена сообщениями, в котором клиент добавляет уникальный уникальный идентификатор к сообщению, чтобы сервер знал, откуда приходит сообщение и кому отвечать.
НоЯ должен представить, что есть какой-то более простой способ сделать это.
Есть мысли?(Также я надеюсь, что это достаточно ясно, я не публикую здесь слишком много)
Обновление: больше кода
Итак, вот основной цикл, в котором каждый новый потоквходит.Параметр user
- это созданный объект Client
, который содержит клиентский сокет, возвращаемый s.accept()
в главном цикле сервера.
def main_client_loop(user):
# Do not let socket timeout, wait on messages from client
user.socket_object.settimeout(None)
client_msg_list = user.receive_msg()
s_globals.message_handler(client_msg_list, user)
Вот функция приема для сервера
def receive_msg(self):
logger.debug("In receive function")
is_pickle = False
# Receive initial data and extract message length
data = self.socket_object.recv(s_globals.BUFFER)
logger.debug("Received data: " + str(data.decode()))
if len(data) is 0:
logger.debug("Client terminated connection")
self.is_connected = False
s_globals.client_master_list.remove(self)
quit()
msg_list_out = []
msg_list_in = data.decode().split('\0')
for msg in msg_list_in:
if len(msg) is 0:
break
else:
# Otherwise add to outlist normally
msg_list_out.append(msg[4:len(msg)])
return msg_list_out
Относительно простой код, клиентский сокет получает сообщения, разделенные нулевыми байтами, и помещает их в список, который передается обработчику для обработки.Эта функция является частью объекта Client
, поэтому она действует на self
.
def message_handler(server_messages, caller):
logger.debug("In message_handler with caller: " + str(caller))
"""
:type server_message: str
:type call: client_class_user.client_user
"""
handler_table= {
SERVER_USERLIST_REQUEST: partial(send_active_user_list, caller),
CLIENT_HELLO: caller.poll_response_good
}
for msg in server_messages:
try:
exec_response = handler_table[msg]
logger.debug("Executing function for msg: " + str(msg) + " - "+ str(exec_response))
exec_response()
except Exception as e:
logger.debug("Exception raised when executing function for: " + str(msg) + str(e))
do_nothing()
Выше приведен фрагмент кода, который обрабатывает входящие сообщения от клиента.Из некоторых данных журнала, которые я видел, которые я опубликую, когда смогу воспроизвести, обработчик сообщений входит в функцию с параметром caller
, который отличается от сокета, который был зарегистрирован, когда тот же объект в параметре caller
назвал егособственный метод receive_msg()
.
Это то место, где я сейчас нахожусь в тупике.Будет обновляться, как я иду.
Если кто-то все еще читает, вот распечатка журнала, где, кажется, ошибка происходит.
1545428257.7718635: Received data from: ('CLIENT_1_ADDRESS', 49807) on socket: <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('SERVER_ADDRESS', 6881), raddr=('CLIENT_1_ADDRESS', 49807)>
**************
0024server_userlist_request^@
1545428257.7718995: In message_handler with caller: ('CLIENT_1_ADDRESS', 49807)
1545428257.7719202: Executiing message for caller ('CLIENT_1_ADDRESS', 49807) @ <class_client_user.client_user object at 0x7f905a5c1ba8>
1545428257.7719324: Executing function for msg: server_userlist_request - functools.partial(<function send_active_user_list at 0x7f905af79378>, <class_client_user.client_user object at 0x7f905a5c1ba8>)
1545428257.7719395: Sending user list in pickled format to caller: ('CLIENT_1_ADDRESS', 49807)
1545428257.7719553: in send_msg with ('CLIENT_2_ADDRESS', 48266)
1545428257.7719624: Received bytes message to send: b'\x80\x03]q\x00(X\x05\x00\x00\x0028263q\x01X\x05\x00\x00\x0027528q\x02e.'
1545428257.7719736: Sending data: b'p0033\x80\x03]q\x00(X\x05\x00\x00\x0028263q\x01X\x05\x00\x00\x0027528q\x02e.\x00' to socket ('CLIENT_2_ADDRESS', 48266)
1545428258.1330225: in main_client_loop
1545428258.1330776: In receive function with ('CLIENT_2_ADDRESS', 48266)
1545428260.775301: in main_client_loop
1545428260.7753537: In receive function with ('CLIENT_1_ADDRESS', 49807)
Клиентский сокет 1 принимает запрос клиента 1 о списке пользователей, доставляет сообщение обработчику с сообщением и клиентским сокетом 1 в качестве аргументов.Обработчик сообщений выполняет функцию отправки списка пользователей с помощью сокета Client 1.
Эта функция в основном берет текущий список, выбирает его и вызывает метод send_msg()
переданного объекта сокета.
Как только вызывается send_msg()
, журнал указывает, что он на самом делеКлиентский сокет 2, который собирается отправить сообщение (со списком пользователей).Поэтому клиент 1 запрашивает список, а клиент 2 получает его.
Этот момент Эврики еще не настиг меня, я пытаюсь понять, почему Client Socket 2 внезапно получает контроль над полученным сообщением и методами, вызываемымиКлиентский сокет 1, так как эти два сокета существуют в разных потоках.
ОБНОВЛЕНИЕ: РЕШЕНО
def send_active_user_list(user):
"""
:type user: class_client_user.client_user
"""
logger.debug("Sending user list in pickled format to caller: " + str(user.ip_address))
uid_list = []
for user in client_master_list:
if user.uid not in client_master_list:
uid_list.append(user.uid)
user.send_msg(pickle.dumps(uid_list))
Не могу поверить, что я этого не видел.По какой-то нелепой (возможно, ленивой) причине я решил использовать ту же самую переменную user
в моих итерациях в client_master_list
(которая содержит всех зарегистрированных пользователей), что и мою итеративную переменную, что в конечном итоге изменило пользовательский параметр, который я первоначально передал, поэтому send_msg()
вызывается для объекта user
, который последний раз повторялся в списке.
Это объясняет, почему первый оператор журнала правильно записывает исходный параметр, но send_msg()
выполняется для последнего пользователявойти на сервер.
Это даже не было проблемой с многопоточностью, просто из-за небрежности.
Это может закрыть любой, кто бы ни