Сервер чата не закрывает потоки и не закрывается, когда SIGINT - PullRequest
0 голосов
/ 05 января 2019

У меня есть следующая серверная программа на Python, которая имитирует чат-комнату. Код принимает подключения от клиентов и для каждого из них запускает новый поток. Этот поток будет ждать сообщений от этого клиента. Сообщения могут быть L, поэтому сервер ответит списком подключенных клиентов, ip:port msg сервер отправит сообщение msg клиенту ip:port.

На стороне клиента будет 2 потока, один для получения сообщений с сервера, другой для отправки.

import socket
from threading import Thread
#from SocketServer import ThreadingMixIn
import signal
import sys
import errno

EXIT = False
address = []
address2 = []

# handler per il comando Ctrl+C
def sig_handler(signum, frame):
    if (signum == 2):
        print("Called SIGINT")
        EXIT = True

signal.signal(signal.SIGINT, sig_handler) # setto l'handler per i segnali


# Multithreaded Python server : TCP Server Socket Thread Pool
class ClientThread(Thread):

    def __init__(self,conn,ip,port):
        Thread.__init__(self)
        self.conn = conn
        self.ip = ip
        self.port = port
        print ("[+] New server socket thread started for " + ip + ":" + str(port))
    def run(self):
        while True:
            data = self.conn.recv(1024)
            print ("Server received data:", data)
            if (data=='L'):
                #print "QUI",address2
                tosend = ""
                for i in address2:
                    tosend = tosend + "ip:"+str(i[0]) + "port:"+str(i[1])+"\n"
                self.conn.send(tosend)
                #mandare elenco client connessi
            else:
                #manda ip:port msg
                st = data.split(" ")
                msg = st[1:]
                msg = ' '.join(msg)
                print ("MSG 2 SEND: ",msg)
                ipport = st[0].split(":")
                ip = ipport[0]
                port = ipport[1]
                flag = False
                print ("Address2:",address2)
                print ("ip:",ip)
                print ("port:",port)
                for i in address2:
                    print (i[0],ip,type(i[0]),type(ip),i[1],type(i[1]),port,type(port))
                    if str(i[0])==str(ip) and str(i[1])==str(port):
                        i[2].send(msg)
                        self.conn.send("msg inviato")
                        flag = True
                        break
                if flag == False:
                    self.conn.send("client non esistente")


if __name__ == '__main__':
    # Multithreaded Python server : TCP Server Socket Program Stub
    TCP_IP = '127.0.0.1'
    TCP_PORT = 2004
    TCP_PORTB = 2005
    BUFFER_SIZE = 1024  # Usually 1024, but we need quick response

    tcpServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcpServer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    tcpServer.bind((TCP_IP, TCP_PORT))

    tcpServerB = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcpServerB.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    tcpServerB.bind((TCP_IP, TCP_PORTB))
    threads = []

    tcpServer.listen(4)
    tcpServerB.listen(4)

    while True:
        print("Multithreaded Python server : Waiting for connections from TCP clients...")
        try:
            (conn, (ip,port)) = tcpServer.accept()
        except socket.error as e: #(code, msg):
            if e.errno != errno.EINTR:
                raise
            else:
                break
        address.append((ip,port,conn))

        (conn2, (ip2,port2)) = tcpServerB.accept()
        address2.append((ip2,port2,conn2))

        newthread = ClientThread(conn,ip,port)
        newthread.start()
        threads.append(newthread)
        if EXIT==True:
            break

    print ("SERVER EXIT")

    for t in threads:
        t.join()

В коде есть обработчик сигнала для SIGINT, чтобы сделать уборщик выхода (закрытие соединений, отправка сообщения клиенту (еще не реализовано) и т. Д.). Обработчик записывает глобальный флаг EXIT для завершения бесконечных циклов.

  1. Код работает как в Python2, так и в Python3. Однако есть некоторые проблемы с сигналом SIGINT, генерируемым CTRL-C. Когда клиент не подключен, программа, запущенная с Python2, корректно завершает работу, а в Python3 - нет. Почему такая поведенческая разница?
  2. Учитывая, что программа запускается только на Python2, когда клиент подключается, и я нажимаю CTRL-C, основное время завершается, как будто сигнал всегда перехватывается основным потоком, и это прерывает системный вызов блокировки accept. Однако другие потоки этого не делают, я думаю, из-за блокировки системного вызова data = self.conn.recv(1024). В C я бы блокировал сигналы SIGINT для одного потока, а затем вызвал бы pthread_cancel из другого потока. Как выйти из всех потоков при генерации SIGINT в Python?

Клиентская программа, которая на данный момент работает только на Python2 и сталкивается с той же проблемой:

# Python TCP Client A
import socket
from threading import Thread

class ClientThread(Thread):

    def __init__(self,conn):
        Thread.__init__(self)
        self.conn = conn

    def run(self):
        while True:
            data = self.conn.recv(1024)
            print "Ricevuto msg:",data

host = socket.gethostname()
print "host:",host
port = 2004
portB = 2005
BUFFER_SIZE = 2000

tcpClientA = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpClientB = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpClientA.connect(('127.0.0.1', port))
tcpClientB.connect(('127.0.0.1', portB))

newthread = ClientThread(tcpClientB)
newthread.start()


while(True):
    msg = raw_input("Inserisci comando: ")
    tcpClientA.send (msg)
    data = tcpClientA.recv(BUFFER_SIZE)
    print "data received:",data

tcpClientA.close()

1 Ответ

0 голосов
/ 05 января 2019

Что касается различия в поведении с accept() в Python 3, посмотрите полное описание в документах . Я думаю, что это ключевое утверждение:

Изменено в версии 3.5: если системный вызов прерывается и обработчик сигнала не вызывает исключение, метод теперь повторяет системный вызов, а не вызывает исключение InterruptedError (объяснение см. В PEP 475).

Другая проблема, указанная в вашем предпоследнем предложении:

Как выйти из всех потоков, когда SIGINT генерируется в Python 2?

Взгляните на документацию threading :

Поток может быть помечен как «поток демона». Значение этого флага заключается в том, что вся программа Python завершается, когда остаются только потоки демона. Начальное значение наследуется от потока создания. Флаг можно установить через свойство демона.

...