Мультипоточный сокет-сервер

Материал из Mindsellers
Перейти к: навигация, поиск

Python

Всем наверняка известно, что в Python есть модуль socket, который реализует, собственно, сокеты и двусторонний обмен данными. Но вот незадача: при классическом использовании, как в официальном примере

# Echo server program
import socket

HOST = ''                 # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen(1)
    conn, addr = s.accept()
    with conn:
        print('Connected by', addr)
        while True:
            data = conn.recv(1024)
            if not data: break
            conn.sendall(data)

сервер обслуживает только одно подключение в единицу времени! Пока не свершится подключение conn, addr = s.accept(), не начнется чтение. Пока не будут прочитаны данные data = conn.recv(1024), сервер не будет ждать новых подключений, даже если задать s.listen(10000).

Получается, для того, чтобы иметь, скажем так, ожидаемый функционал сокет-сервера нужно постоянно слушать подключения - метод accept, а для каждого нового подключения формировать отдельный поток, в котором будет производиться общение непосредственно с ним. И уже в потоках нужно работать с каждым клиентом по отдельности.

Поискав готовое решение, я наткнулся на фреймворк SocketServer, но и от него ожидаемого поведения в одну строку получить не удалось, а также библиотеку Twisted, но она довольно обширна и сложна для понимания. Но, как говорится, не нравится готовое - пиши свое. Результатом трехдневных поисков и страданий стал класс, который обеспечивает прием сообщений от каждого из клиентов и рассылку оного всем остальным

#!/usr/bin/python
import socket
import threading

class SockServer(object):
    def __init__(self, host, port):
	self.clients=set()
        self.host = host
        self.port = port
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind((self.host, self.port))

    def listen(self):  #Функция, ожидающая новых подключений
        self.sock.listen(500)
        while True:
            client, address = self.sock.accept()  #При подключении клиента
	    self.clients.add(client)              #Он добавляется в множество clients
            threading.Thread(target = self.listenToClient,args = (client,address)).start()
                                                  #И в отдельном потоке запускается 
	    
    def listenToClient(self, client, address):    #Функция прослушивания сообщений
        size = 1024
        while True:
            try:
                data = client.recv(size)         #Получаем сообщение
                if data:
                    for other in self.clients:   #Рассылаем его всем остальным клиентам
			if  other != client:
				other.send(data)
		else:
		    self.clients.remove(client)  #Если клиент отключился, вычеркиваем его из множества

	    except:
                client.close()
		try: 
		    self.clients.remove(client)
		except:
		    pass
                


SockServer('localhost',8000).listen()

В принципе, можно запускать скрипт, подключаться телнетом к адресу и порту, заданным в последней строке, и наслаждаться работающим чатом.

Исходник выложен на Гитхабе