Мультиклиентский чат. Укажите мне путь

 
0
 
Python
ava
deHimer | 28.03.2011, 22:21
Здравия Всем! Выставляю на обсуждение вопрос, над которым бьюсь несколько дней.
Есть сервер и множество клиентов. Вся работа выполняется на сокетах.
Как сделать так чтобы пользователь мог в одно и то же время и получать сообщения из общего чата и писать их?

Я думал просто пока пользователь не начнет ввод - ждать сообщений и выводить их.

Мои варианты:
2 потока, один принимает данные и выводит их на экран.
или
1 цикл с проверкой на пользовательский ввод с клавиатуры. Если ввода не происходит - слушать сокет.

Если первый вариант - то надо на сервер отправить информацию о номере порта UDP сервера, ожидающего сообщения.
Во втором мне не понятно как сделать ожидание события "нажатие клавиши".

Какие идеи?

Код с попыткой реализации первого варианта:
Сервер:

import socket
import threading
import re

host = 'localhost'
common_chat_port = 14
individual_port = 13

#имя клиента -> (статус, (хост, порт)).
#статус - в общем чате(1)/личная переписка(2)
clients = {}

class Common_messages_send(threading.Thread):
"""
Рассылка сообщений клиентам из состоящим в общем чате.
"""
def __init__(self, sock, data, sendler_addres):
threading.Thread.__init__(self)
self.sock = sock
self.data = data
self.sendler_addres = sendler_addres

def run(self):
for client in clients.values():

#отправка только если статус в общем чате и получатель не является оптправителем
if (client[0] == 1) and (client[1]!=self.sendler_addres):

self.sock.sendto(self.data, client[1])
print ' cm_done'

class Common_chat(threading.Thread):
"""
Процесс, получающий и перенаправляющий клиентам сообщения для общего чата.
"""
def __init__(self, sock):
threading.Thread.__init__(self)
self.sock = sock

def run(self):

while True:

data, sendler_addres = self.sock.recvfrom(1024)
Common_messages_send(self.sock, data, sendler_addres).start()
print ' cc_done'

class Clients(threading.Thread):
"""
Процесс, поддерживающий связь с конкретным пользователем.
Выполняет команды от пользователя и организует личную переписку.
"""
def __init__(self, sock, host_port):
threading.Thread.__init__(self)
self.sock = sock
self.host_port = host_port

def run(self):

have_connection = True

#получение, проверка и сохранение информации о клиенте
try:
user_name = self.sock.recv(20)
except:
have_connection = False

while have_connection:

if clients and (user_name in clients.keys()):
self.sock.send('Name chosen busy. Choose another name')
else:
if re.search('\W', user_name):
self.sock.send("Login invalid")
else:
self.sock.send("Ok")
#сохраняем данные пользователя
clients[user_name]=(1, self.host_port) # 1 -
print ('User %s has connected' % user_name)
break

user_name = self.sock.recv(20)


#информация о собеседнике (в режиме работы 2 - индивидуальный чат)
#[name]->(host, port)
companion = {}

#цикл работы с клиентом
have_connection = True
while have_connection:

try:
buffer = self.sock.recv(1024)
except:
have_connection = False
break

if buffer == '!c':
connected_clients_list=''
for client in clients.keys():
connected_clients_list += client + ' '

self.sock.send(connected_clients_list)
elif buffer == '!a':
client = clients[user_name]
client[0] = 1
clients[user_name] = client
elif buffer =='!e':
have_connection = False
print ('User %s is disconnected' % user_name)

self.sock.close()

#создание и настрока сокета для общего чата
common_chat_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
common_chat_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
common_chat_socket.bind((host, common_chat_port))

#создание и настрока сокета для соединения с клиентом
individual_chat_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
individual_chat_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
individual_chat_socket.bind((host, individual_port))
individual_chat_socket.listen(5)

#запуск потока реализующего общий чат
Common_chat(common_chat_socket).start()

while True:

#создание потока связи с клиентом
sock, client_host_port = individual_chat_socket.accept()
Clients(sock, client_host_port).start()



Клиент:

import socket
import threading


#параметры соединений
host = 'localhost'
common_chat_port = 14
individual_port = 13


common_chat_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((host, individual_port))

#режим чата. 1 - общий. 2 - индивидуальный
chat_mode = 1

class Common_chat_client(threading.Thread):
def __init__(self, sock):
self.sock = sock
threading.Thread.__init__(self)

def run(self):

global chat_mode
self.sock.sendto('new user connected', ('localhost', 14))
while True:
if chat_mode==1:
buffer, addres=self.sock.recvfrom(1024)
print 'All>'+ buffer

class Work(threading.Thread):
def __init__(self, client_sock, common_sock):
self.client_sock = client_sock
self.common_sock = common_sock
threading.Thread.__init__(self)
def run(self):

global chat_mode

#выбор имени для клиента
while True:
user_name = raw_input('Select the desired name: ')
self.client_sock.send(user_name)
server_answer = self.client_sock.recv(100)

if server_answer=='Ok':
print 'Name valid. Connection is established.'
break
else:
print server_answer

print "!h - help\n!a - common chat\n!c - client list\n!l - chat with login\n!e - exit"


#основной цикл работы
while True:
buffer = raw_input('>')
if buffer=='!c':
#получение списка контактов
command = '!c'
self.client_sock.send(command)
connected_clients_list = self.client_sock.recv(64000)
print connected_clients_list
elif buffer=='!a':
chat_mode = 1
self.client_socket.send(buffer)
elif buffer=='!h':
print "!h - help\n!a - common chat\n!c - client list\n!l - chat with login\n!e - exit"
elif buffer=='!e':
self.client_socket.send('!e')
break
elif buffer=='!l':
chat_mode = 2
pass
else:
#получение и отправка сообщений
if chat_mode==1:
self.common_sock.sendto(buffer, (host, common_chat_port))
else:
pass

client_socket.close()

Work(client_socket ,common_chat_socket).start()
Common_chat_client(common_chat_socket).start()
Kommentare (4)
ava
deHimer | 31.03.2011, 06:22 #
4ый вопрос.. и 4ый ответ самому же себе..
потом выложу код решения
ava
bilbobagginz | 31.03.2011, 07:03 #
можно посмотреть как такое делается в существующих протоколах. примеры реализации протоколов (в хронологическом порядке изобретения):
  • TALK
  • IRC
  • XMPP
ava
deHimer | 31.03.2011, 21:12 #
Более менее дышащий код.

Код Клиента:

#Сети ЭВМ. лр №3
#мультиклиентский чат. клиентская часть. Автор: deHimer

import socket
from threading import Thread

#глобалные переменные клиента
host = 'localhost'
tcp_socket_port = 13

opp_udp_host_port = () # host, port оппонента в режиме приватноо чата

#режим чата. 1 - общий. 2 - индивидуальный
chat_mode = 1



# UDP сервер принимающий входящие сообщения и ответственный за получение
# приглашений от другого пользователя чата на личную переписку

class UDP_server(Thread):
def __init__(self, udp_sock):
self.udp_server_socket = udp_sock
Thread.__init__(self)

def run(self):

global opp_udp_host_port
global chat_mode

# бесконечный цикл ожидающий новых сообщений
while True:
#получаем сообщение/команду
data, address = self.udp_server_socket.recvfrom(100)

if data != '!privateChat':
#вывод сообщения из общего чата
print data
else:
#обработка запроса на установление приватного чата

#узнаем как зовут оппонента
data, address = self.udp_server_socket.recvfrom(100)
print data

#отвечаем, хочется ли
data = raw_input('now enter \'yes\' or \'no\' ')
self.udp_server_socket.sendto(data, address)

# если мы согласны, то мы должны перейти в режим личного чата
# и получить адрес UDP порта оппонента
if data == 'yes':

#получить имя хоста и номер порта оппонента
opp_host, address = self.udp_server_socket.recvfrom(25)
self.udp_server_socket.sendto('udp host accept', address)

opp_port, address = self.udp_server_socket.recvfrom(5)
self.udp_server_socket.sendto('udp port accept', address)

opp_udp_host_port = (opp_host, int(opp_port))
chat_mode = 2

else:
pass


#настрока сокета и запуск UDP сервера
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.bind((host, 0)) # если указан 0 - выберется совбодный порт

#узнаем, какой порт получил наш UDP сервер
udp_socket_host_port = udp_socket.getsockname()
udp_socket_port = str(udp_socket_host_port[1])

print 'you on port'+udp_socket_port

#запуск сервера UDP
UDP_server(udp_socket).start()



#настрока TCP клиента, выполняющего связь с сервером

# создание TCP клиента
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_socket.connect((host, tcp_socket_port))

#отправка серверу адреса UDP сервера
tcp_socket.send(host)
tcp_socket.recv(100)

tcp_socket.send(udp_socket_port)
tcp_socket.recv(100)



#выбор клиентом имени и согласование его с сервером
while True:
user_name = raw_input('Select the desired name: ')
tcp_socket.send(user_name)
server_answer = tcp_socket.recv(100)

if server_answer=='Name valid':
print 'Name valid. Connection is established.'
break
else:
print server_answer


#создание UDP сокета для приватного общения с сервером оппонента
udp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

print "!help !common_chat !clients_list !private_chat !exit\n"

#основной цикл общения с TCP сервером
while True:
user_input = raw_input('')
if user_input == '!clients_list':
#получение списка контактов
tcp_socket.send(user_input)
print tcp_socket.recv(64500)

elif user_input=='!common_chat':

if chat_mode == 2:
udp_client_socket.sendto(user_name + 'out from private chat', opp_udp_host_port)

#переход в режим общего чата
chat_mode = 1
tcp_socket.send(user_input)

print 'you in common chat mode'

elif user_input=='!help':
print "!help !common_chat !clients_list !private_chat !exit\n"

elif user_input=='!exit':
tcp_socket.send('!exit')
break

elif user_input=='!private_chat':
#запрос на создание приватного чата
tcp_socket.send(user_input)

#отправка имени оппонента
opp_name = raw_input('Enter opponent name: ')
tcp_socket.send(opp_name)

#получение результата запроса
answer = tcp_socket.recv(100)

if answer=='yes':
#получаем адресс UDP сервера оппонента
opponent_host = tcp_socket.recv(100)
tcp_socket.send('opponent udp host accept')

opponent_port = tcp_socket.recv(100)
opponent_port = int(opponent_port)
tcp_socket.send('opponent udp port accept')

opp_udp_host_port = (opponent_host, opponent_port)

chat_mode = 2

print 'you in private chat mode'

else:
print answer
else:

#получение и отправка сообщений
if chat_mode==1:
tcp_socket.send(user_name+' com> '+user_input)
else:
#тут должна быть отправка данных на UDP сервер оппонента
udp_client_socket.sendto(user_name+' priv> '+user_input, opp_udp_host_port)

tcp_socket.close()



Код Сервера:

#Сети ЭВМ. лр №3
#мультиклиентский чат. серверная часть. Автор: deHimer

import socket
from threading import Thread
import re

#глобальные переменные
host = 'localhost'
tcp_serv_port = 13

clients = {} #[name] -> (chat_mode, (udp_host, udp_port))


#поток обрабатывающий команды/сообщения пользователя
class Connect(Thread):
def __init__(self, client_socket, client_host_port):
self.client_tcp_socket = client_socket
self.client_tcp_addres = client_host_port
Thread.__init__(self)

def run(self):

#создание UDP клиента
self.udp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

#получение адреса UDP сервера клиента
self.client_udp_host = self.client_tcp_socket.recv(100)
self.client_tcp_socket.send('udp host received')

self.client_udp_port = int(self.client_tcp_socket.recv(100))
self.client_tcp_socket.send('udp port received')

#получение имени клиента
have_connection = True

try:
self.user_name = self.client_tcp_socket.recv(20)
except:
have_connection = False

while have_connection:

#проверка имени на уникальность и правильность
if clients and (self.user_name in clients.keys()):
self.client_tcp_socket.send('Name chosen busy. Choose another name')

else:
if re.search('\W', self.user_name):
self.client_tcp_socket.send("Name invalid")
else:
self.client_tcp_socket.send("Name valid")
#сохраняем данные пользователя
clients[self.user_name]=(1, (self.client_udp_host, self.client_udp_port))
print ('User %s has connected' % self.user_name)
break

self.user_name = self.client_tcp_socket.recv(20)


#цикл связи с клиентом
have_connection = True
while have_connection:

try:
buffer = self.client_tcp_socket.recv(1024)
except:
have_connection = False
break

if buffer == '!clients_list':
#составление списка клиентов
connected_clients_list=''
for client in clients.keys():
connected_clients_list += client + ' '

self.client_tcp_socket.send(connected_clients_list)
elif buffer == '!common_chat':
#переключение в общий чат

client = clients[self.user_name]
client = (1, client[1])
clients[self.user_name] = client

elif buffer =='!exit':

have_connection = False
print ('User %s is disconnected' % self.user_name)
del clients[self.user_name]
break

elif buffer == '!private_chat':
#создание приватного чата

#получение имени оппонента
try:
opponent_name = self.client_tcp_socket.recv(100)
except:
have_connection = False
break

#проверка на существование выбранного оппонента
if opponent_name in clients.keys():
#запрос согласия оппонента на установление контакта
opp_mode_host_port = clients[opponent_name]
self.udp_client_socket.sendto('!privateChat', opp_mode_host_port[1])
self.udp_client_socket.sendto('Allow chat with ' + self.user_name + '. (before answering, press Enter)', opp_mode_host_port[1])

opp_answer = self.udp_client_socket.recv(100)


if opp_answer == 'yes':

#отправка оппоненту адреса UDP сервера клиента
print opp_mode_host_port[1]
self.udp_client_socket.sendto(str(self.client_udp_host), 0, opp_mode_host_port[1])
data, address = self.udp_client_socket.recvfrom(15)
print data

self.udp_client_socket.sendto(str(self.client_udp_port), 0, opp_mode_host_port[1])
data, address = self.udp_client_socket.recvfrom(15)
print data

#изменение состояний клиента и оппонента

#переключение оппонента в режим прив. чата
opp_date = clients[opponent_name]
new_opp_mode = (2, opp_date[1])
clients[opponent_name] = new_opp_mode

#переключение пользователя
client = clients[self.user_name]
client = (2, client[1])
clients[self.user_name] = client


#пишем клиенту что все гуд
self.client_tcp_socket.send('yes')

#отправляем клиенту адрес UDP сервера оппонента
opp_host_port = opp_date[1]
self.client_tcp_socket.send(str(opp_host_port[0]))
self.client_tcp_socket.recv(100)
self.client_tcp_socket.send(str(opp_host_port[1]))
self.client_tcp_socket.recv(100)


else:
#иначе пишем что с ним не хотят иметь дел
self.client_tcp_socket.send('no')
else:
#если такого нет
self.client_tcp_socket.send('Opponent with the same name is missing.')


else:
client_mode_address = clients[self.user_name]
client_chat_mode = client_mode_address[0]
if client_chat_mode == 1:
for client in clients.values():
if (client[0] == 1) and (client[1] != (self.client_udp_host, self.client_udp_port)):
self.udp_client_socket.sendto(buffer, client[1])
else:
self.udp_client_socket.sendto(buffer, companion[1])


self.udp_client_socket.close()
self.client_tcp_socket.close()

tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcp_server_socket.bind((host, tcp_serv_port))
tcp_server_socket.listen(5)

#ожидание и подключние новых клиентов
while True:
client_socket, client_host_port = tcp_server_socket.accept()
Connect(client_socket, client_host_port).start()


später ergänzt:
С самого начала хотел на Tkinter'e сделать интерфейс, но на то что выше ушло времени больше чем ожидалось.
Если есть желающие привинтить интерфейс и улучшить код - только за.
Буду приятно удивлен увидеть тут развитие темы smile
ava
afiskon | 31.03.2011, 21:36 #
Есть событие - "клиент прислал данные". При получении сообщений они помещаются в очередь. Специально выделенный поток обрабатывает очередь, рассылая сообщения из нее клиентам. По-моему, все достаточно просто.
Registrieren Sie sich oder melden Sie sich an, um schreiben zu können.
Unternehmen des Tages
Вы также можете добавить свою фирму в каталог IT-фирм, и публиковать статьи, новости, вакансии и другую информацию от имени фирмы.
Подробнее
Mitwirkende
advanced
Absenden