Проблемы с реализацией сетевого сервера с SocketServer

Я новичок в программировании сокетов и нуждаюсь в вашей помощи. Я реализовал простой эхо-сервер, используя пример ThreadedTCPServer из документации Python. Он работает нормально, но у меня есть следующие проблемы:

  1. Сервер зависает (в socket.recv), когда клиент пытается отправить данные нулевой длины.
  2. Сервер зависает (в socket.recv), когда данные, отправленные клиентом, кратны BUF_SIZE.
  3. Я не знаю, как правильно остановить сервер извне. Я хотел бы иметь скрипт, например, stopServer.py, который можно запустить с хоста сервера, когда нужно остановить сервер. Я реализовал команду «STOP», которая отправляется на порт сервера. Этот подход выглядит хорошо для меня, но имеет риск для безопасности. Обратите внимание, что мне нужно кроссплатформенное решение. Так что, наверное, сигналы не подходят.

Буду признателен, если у вас есть какие-либо идеи, как исправить проблемы, перечисленные выше. Код примера приведен ниже.

Хорошего дня! Захар

клиент.py

import sys
import socket
from common import recv

if len (sys.argv) == 1 :
    print "The file to be sent is not specified"
    sys.exit (1)
fname = sys.argv [1]

print "Reading '%s' file..." % fname,
f = open (sys.argv [1])
msg = f.read ()
f.close()
print "OK"

if len (msg) == 0 :
    print "Nothing to send. Exit."
    sys.exit (0)

print "Client is sending:"
print msg
print "len (msg)=%d" % len (msg)

sock = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
sock.connect ( ('localhost', 9997) )
sock.sendall (msg)
print "Sending has been finished"
print "Waiting for response..."
response = recv (sock)
print "Client received:"
print response
print "len (response)=%d\n" % len (response)
sock.close ()

сервер.py

import threading
import SocketServer
from common import recv

class ThreadedTCPRequestHandler (SocketServer.BaseRequestHandler):

    def handle(self) :
        recvData = recv (self.request)
        print "NEW MSG RECEIVED from %s" % self.client_address [0]
        print "Data:"
        print recvData
        print "dataSize=%d" % len (recvData)
        if recvData == "!!!exit!!!" :
            print 'Server has received exit command. Exit.'
            self.server.shutdown()
        else :
            curThread = threading.currentThread ()
            response = "%s: %s" % (curThread.getName (), recvData)
            self.request.sendall (response)

class ThreadedTCPServer (SocketServer.ThreadingMixIn, SocketServer.TCPServer) :
    pass

if __name__ == "__main__":
    HOST, PORT = "localhost", 9997

    server = ThreadedTCPServer ((HOST, PORT), ThreadedTCPRequestHandler)
    server.serve_forever ()

общий.py

'''
Common code for both: client and server.
'''

def recv (sock) :
    '''
    Reads data from the specified socket and returns them to the caller.
    IMPORTANT:
    This method hangs in socket.recv () in the following situations (at least
    on Windows):
    1. If client sends zero-length data.
    2. If data sent by client is multiple of BUF_SIZE.
    '''
    BUF_SIZE = 4096
    recvData = ""
    while 1 :
        buf = sock.recv (BUF_SIZE)
        if not buf :
            break
        recvData += buf
        if len (buf) < BUF_SIZE : # all data have been read
            break
    return recvData

person Zakhar    schedule 02.10.2010    source источник


Ответы (2)


Я не знаю python, но я постараюсь ответить, основываясь на своих знаниях в области программирования сокетов.

Сообщение с нулевым байтом НЕ приведет к выходу sock.recv. См. здесь для объяснения. Если sock.recv возвращает длину 0, это указывает на то, что другая сторона разорвала соединение TCP.

if len (buf) < BUF_SIZE

Как эта строка определяет, что ВСЕ данные были прочитаны. Вызов TCP recv не является сопоставлением 1-1 между вызовом отправки TCP, т. е. то, что клиент отправляет в 1 вызове отправки, может быть получено в нескольких вызовах recv.

Когда вы отправляете несколько BUF_SIZE, то есть, скажем, 2 * BUF_SIZE, вполне возможно, что вы можете получить все это за один раз. В этот момент ваш сервер зависнет как len(buf) > BUF_SIZE, и вы застрянете в sock.recv. Попробуйте и выясните, как использовать неблокирующие сокеты.

person Aditya Sehgal    schedule 02.10.2010
comment
Спасибо за ответ. Насчет if len (buf) ‹ BUF_SIZE... похоже, вы не уловили идею. BUF_SIZE — это максимальное количество данных, которое должно быть получено за один раз. Итак, я получаю входящее сообщение частями (каждая часть не может быть больше BUF_SIZE), одну за другой. Если полученная часть меньше BUF_SIZE, это означает, что мы достигли конца сообщения. Итак, ситуация, когда len(buf) › BUF_SIZE невозможна. Подробности смотрите в документации по python socket.recv. - person Zakhar; 05.10.2010
comment
Вызовы recv обычно возвращают ЛЮБЫЕ данные, доступные в сокете, ограниченном BUF_SIZE (в вашем случае). Как я уже говорил, в TCP, если вы отправляете данные BUF_SIZE в одном приеме, НЕТ НИКАКОЙ гарантии, что вы получите их через ОДИН единственный вызов приема. - person Aditya Sehgal; 05.10.2010

После некоторых экспериментов я обнаружил следующее:

  1. Нужно переименовать common.recv в common.recvall
  2. Необходимо прокомментировать следующие 2 строки в common.recv (ошибка):

    if len (buf) < BUF_SIZE : # all data have been read
    break
    
  3. Этот пример будет работать только в том случае, если данные, отправленные на сервер, могут быть прочитаны 1 вызовом socket.recv на стороне сервера. Таким образом, отправляемые данные должны быть меньше BUF_SIZE. В противном случае клиент и сервер находятся в тупике, как в вызове sock.recv. Это происходит из-за одновременного доступа к общему ресурсу (сокету). Чтобы решить эту проблему, клиент должен прослушивать ответ на другом порту.
person Zakhar    schedule 17.10.2010