Commit 8b3f7c51 authored by wx002's avatar wx002

irc project done

parent a5807308
import select
import socket
import logging
class Channel:
def __init__(self, name, server_ip):
self.name = name
self.server = server_ip
self.ch_socket_list = {} #name:skt
def broadcast(self, skt, msg):
if skt in self.ch_socket_list.values():
packet = msg.encode()
for s in self.ch_socket_list.values():
try:
s.sendall(packet)
except:
continue
else:
skt.sendall('442 - {}:You\'re not on that channel'.format(self.name).encode())
def welcome(self, name, user_skt):
if name not in self.ch_socket_list:
reply = name + '!'+name + '@'+ self.server + ' JOIN ' + self.name
self.ch_socket_list[name] = user_skt
self.broadcast(user_skt, reply)
# send current list of user to socket
user_list =','.join(self.ch_socket_list.keys())
user_packet = ':{} 332\n{} {} :{}'.format(self.server, name, self.name, user_list)
user_skt.sendall(user_packet.encode())
else:
user_skt.sendall('443 - {} is already in {}'.format(name, self.name).encode())
def leave(self, name):
if name in self.ch_socket_list:
skt = self.ch_socket_list[name]
self.broadcast(skt, '{}:{} has left the channel!'.format(self.name, name))
del self.ch_socket_list[name]
from socket import *
import re
import sys
import select
import threading
import curses
class IRC_Cient:
COMMAND_LIST = ['/join', '/msg', '/part', '/quit', '/list', 'help', '/create', '/names']
def __init__(self, server_ip, server_port, nick):
self.IP = server_ip
self.port = server_port
self.name = nick
self.skt = socket(AF_INET, SOCK_STREAM)
self.skt.connect((self.IP, self.port))
def register(self):
# sending register nick
register_str = b'NICK ' + self.name.encode()
self.skt.sendall(register_str)
def print_server_reply(self):
try:
while True:
resp = '\n'+self.skt.recv(5000).decode() + '\n'
if len(resp)>0:
print(resp)
else:
continue
except Exception as e:
print(e)
sys.exit()
def is_valid_message(self, msg):
msg_list = msg.split(' ')
if msg_list[0] not in IRC_Cient.COMMAND_LIST and len(msg_list) < 3:
return False
else:
return True
def main(self):
print('Simple IRC Client, CSCI 363 IRC Project\nMade by Ben Xu\nRun using Python 3.7.1\nUse /help for supported command list!')
self.register()
while True:
user_input = input()
if self.is_valid_message(user_input):
self.skt.sendall(user_input.encode())
if user_input == '/quit':
print('Good bye!')
sys.exit()
elif user_input == 'help':
print('command list: /msg, /join, /part, /quit, /list, /create, /help. For details, do /help --detail')
elif user_input == 'help --detail':
print('/msg : /msg [target] : [message] -- sends a message to a user or channel\n'
'/join: /join [channel] -- join a specific channel\n'
'/part: /part [channel] -- leave a channel\n'
'/quit: /quit -- log off\n'
'/names: /names -- display list of online users'
'/create: /create [channel name] -- create a channel with given name')
else:
print('Invalid message format')
# TODO help function
if __name__ == '__main__':
if len(sys.argv) == 4:
ip = sys.argv[1]
port = int(sys.argv[2])
nick = sys.argv[3]
client = IRC_Cient(ip,port, nick)
else:
client = IRC_Cient('127.0.0.1', 6667, 'test')
try:
recv_thread = threading.Thread(target=client.print_server_reply)
recv_thread.setDaemon(True)
recv_thread.start()
client.main()
except Exception as e:
print(e)
sys.exit()
import socket
import select
import logging
import sys
from irc_db import *
class IRC_Server:
def __init__(self, ip, port, name):
self.ip = ip
self.port = port
self.server_name = name
self.db = irc_db()
self.log = logging.getLogger()
def main(self):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((self.ip, self.port))
server.listen()
server.setblocking(False)
self.db.create_channel('#Lobby', self.ip)
self.log.info('Initalized #Lobby.....\nCurrent Channel List: {}'.format(list(self.db.channelList.keys())))
self.log.info("Server running on {}".format((self.ip, self.port)))
while True:
try:
r, w, e = select.select([server] + self.db.socket_list, [], self.db.socket_list, 1)
#self.log.info('users: {}'.format(self.users))
for skt in e:
self.log.info("Socket closed on {}".format(skt))
self.db.remove_by_skt(skt)
if len(e) > 0:
continue
for skt in r:
if skt == server:
cli, cli_addr = skt.accept()
cli.setblocking(False)
# got client
self.db.init_user(cli)
else:
try:
msg = skt.recv(5000)
except ConnectionResetError:
msg = ''
if len(msg) > 0:
self.log.info('packet recv from {}, content: {}'.format(skt.getpeername(), msg))
self.log.info('Current user in db : {}'.format(self.db.users.keys()))
if not self.db.is_in_db_by_skt(skt):
# expect NICK packet
resp_code = self.auth(msg, skt)
self.log.info('auth resp: {}'.format(resp_code))
if resp_code == -1:
self.db.remove_by_skt(skt)
skt.close()
else:
continue
else:
# handle msg packet
cmd, target, content = self.parse_packet(msg)
self.log.info('recv msg packet! cmd = {}, target = {}, content = {}'.format(cmd, target, content))
if cmd == '/msg' and target is not None and content is not None:
if '#' in target and target in self.db.channelList:
sender_ip = skt.getpeername()[0]
name = self.db.users[skt]
header = self.build_packet_header(name, sender_ip)
sending_packet = header + ' ' + msg.decode()
self.db.channelList[target].broadcast(skt, sending_packet)
self.log.info('Broadcasting: {}'.format(sending_packet))
elif '#' not in target and content is not None:
# send to users,
if target in self.db.users:
name = self.db.users[skt]
sender_ip = skt.getpeername()[0]
header = self.build_packet_header(name, sender_ip)
sending_packet = header + ' ' + msg.decode()
self.db.users[target].sendall(sending_packet.encode())
else:
skt.sendall('444 {}:User not logged in!'.format(target).encode())
else:
p = '461 {} not enough or invalid parameters'.format(cmd)
skt.sendall(p.encode())
elif cmd == '/join' and target is not None:
if target in self.db.channelList:
if skt not in self.db.channelList[target].ch_socket_list and skt in self.db.users:
name = self.db.users[skt]
self.db.channelList[target].welcome(name, skt)
else:
p = '443 - Already in channel'
skt.sendall(p.encode())
else:
skt.sendall('ERROR - Channel {} does not exists'.format(target).encode())
elif cmd == '/part' and target is not None:
name = self.db.users[skt]
if target in self.db.channelList:
if name in self.db.channelList[target].ch_socket_list:
self.db.channelList[target].leave(name)
else:
skt.sendall('442 - {}:You\'re not in that channel'.format(name).encode())
else:
skt.sendall(b'Channel does not exists!')
elif cmd == '/quit':
self.db.remove_by_skt(skt)
skt.close()
elif cmd == '/list':
skt.sendall('332 - Channels: {}'.format(','.join(self.db.channelList.keys())).encode())
elif cmd == '/create' and target is not None:
if target not in self.db.channelList:
name = self.db.users[skt]
self.db.create_channel(target, self.ip)
self.db.channelList[target].welcome(name, skt)
else:
skt.sendall('Error - channel {} already exists'.format(target).encode())
elif cmd == '/names':
names = [elm for elm in self.db.users.keys() if isinstance(elm, str)]
nameList = ','.join(names)
p = 'Online Users: {}'.format(nameList)
skt.sendall(p.encode())
else:
skt.sendall(b'Error - unknown packet!')
else:
try:
self.log.debug("disconnect from {}".format(skt.getpeername()))
self.db.remove_by_skt(skt)
skt.close()
except:
pass
except KeyboardInterrupt:
print("Ctrl-c, quit!")
break
def auth(self, packet, skt):
msg = packet.decode()
if 'NICK' in msg:
msgList = msg.split(' ')
name = msgList[1]
if self.db.register_user(name, skt):
RPL_WELCOME = ':{} 001 {} Welcome to the {}!{}@{}'.format(self.ip, name, self.server_name, name,name,skt.getpeername()[0])
skt.sendall(RPL_WELCOME.encode())
# join #Lobby
self.db.channelList['#Lobby'].welcome(name, skt)
return 1
else:
skt.sendall(b'433 - Nickname already in use')
return -1
else:
return 0
def parse_packet(self, packet):
#cmd target :msg
if b':' in packet:
msgList = packet.decode().split(':')
prefix = ''.join(msgList[0]).split(' ')
cmd = prefix[0]
target = prefix[1]
content = msgList[1]
return cmd, target, content
else:
# server commands
cmd_str = packet.decode()
cmd_list = cmd_str.split(' ')
if len(cmd_list) == 2:
return cmd_list[0], cmd_list[1], None
elif len(cmd_list) == 1:
return cmd_list[0], None, None
else:
return None, None, None
def build_packet_header(self,name, sender_ip):
header = ':{}!{}@{}'.format(name, name, sender_ip)
return header
if __name__ == '__main__':
if len(sys.argv) == 4:
ip = sys.argv[1]
port = int(sys.argv[2])
name = sys.argv[3]
else:
print('--usage: Python IRC_Server [ip] [port] [server name]')
debug = True
if debug:
FORMAT = '%(asctime)-15s %(levelname)-6s: %(message)s'
logging.basicConfig(filename='server.log', format=FORMAT, level=logging.DEBUG)
logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
server = IRC_Server(ip, port, name)
server.main()
from Channel import *
class irc_db:
def __init__(self):
self.users = {} # name: socket and socket:name
self.channelList = {} # channelName : Channel Object
self.socket_list = [] # list of sockets
def is_in_db(self, name):
if name in self.users:
return True
else:
return False
def is_in_db_by_skt(self, skt):
if skt in self.users:
return True
else:
return False
def get_socket_by_name(self, name):
if self.is_in_db(name):
return self.users[name]
def remove_by_name(self, name):
if self.is_in_db(name):
skt = self.users[name]
del self.users[name]
if skt in self.users:
del self.users[skt]
if skt in self.socket_list:
self.socket_list.remove(skt)
for c in self.channelList:
if name in self.channelList[c].ch_socket_list:
self.channelList[c].leave(name)
def remove_by_skt(self, skt):
name = self.users[skt]
if skt in self.users:
del self.users[skt]
if name in self.users:
del self.users[name]
if skt in self.socket_list:
self.socket_list.remove(skt)
for c in self.channelList:
if name in self.channelList[c].ch_socket_list:
self.channelList[c].leave(name)
def register_user(self, name, skt):
if not self.is_in_db(name) and skt not in self.users:
self.users[name] = skt
self.users[skt] = name
return True
else:
return False
def create_channel(self, ch_name, ip):
if ch_name not in self.channelList and '#' in ch_name:
self.channelList[ch_name] = Channel(ch_name, ip)
def init_user(self, skt):
if skt not in self.socket_list:
self.socket_list.append(skt)
2019-05-01 12:52:30,568 INFO : server running on ('127.0.0.1', 6667)
2019-05-01 12:56:08,232 INFO : server running on ('127.0.0.1', 6667)
2019-05-01 12:58:43,775 INFO : server running on ('127.0.0.1', 6667)
2019-05-01 12:59:58,776 INFO : server running on ('127.0.0.1', 6667)
2019-05-01 13:02:33,909 INFO : server running on ('127.0.0.1', 6667)
2019-05-01 13:03:12,696 INFO : server running on ('127.0.0.1', 6667)
2019-05-01 13:03:40,592 INFO : server running on ('127.0.0.1', 6667)
2019-05-01 13:04:48,698 INFO : server running on ('127.0.0.1', 6667)
2019-05-01 13:07:07,219 INFO : server running on ('127.0.0.1', 6667)
2019-05-01 13:07:08,610 INFO : recv packet from ('127.0.0.1', 56323), msg = NICK test
2019-05-01 13:07:49,931 INFO : server running on ('127.0.0.1', 6667)
2019-05-01 13:09:01,766 INFO : server running on ('127.0.0.1', 6667)
2019-05-01 13:09:27,851 INFO : server running on ('127.0.0.1', 6667)
2019-05-01 13:09:29,382 INFO : server running on ('127.0.0.1', 6667)
2019-05-02 02:13:43,312 INFO : server running on ('127.0.0.1', 6667)
2019-05-02 02:13:58,013 DEBUG : disconnect from ('127.0.0.1', 59371)
2019-05-02 02:14:06,715 INFO : recv packet from ('127.0.0.1', 59373), msg = NICK test
2019-05-02 02:14:30,309 DEBUG : disconnect from ('127.0.0.1', 59373)
2019-05-02 02:14:46,047 INFO : recv packet from ('127.0.0.1', 59374), msg = NICK test
2019-05-02 02:14:58,169 DEBUG : disconnect from ('127.0.0.1', 59374)
2019-05-02 02:32:50,226 INFO : recv packet from ('127.0.0.1', 61283), msg = NICK test
This is the basic implementation of IRC Server/Client over TCP.
Currently only supports one server with many clients, capable of one-one and one-to-many communication
To run:
- IRC_Server: Python IRC_Server.py [ip] [port] [server name]
- Client: Python Client.py [server ip] [server port] [Name]
COMMAND_LIST = ['/join', '/msg', '/part', '/quit', '/list', 'help', '/create', '/names']
Command Details:
**/msg:**
- syntax: /msg [target] : [message]
- desc: send a message to target, target is either a channel or a user
**/join:**
- syntax: /join [channel name]
- desc: joins the specified channel
**/part:**
- syntax: /part [channel name]
- desc: leaves the specified channel
**/quit:**
- syntax: /quit
- desc: exit the server
**/list:**
- syntax: /list
- desc: display a list of existing channels
**/create:**
- syntax: /create [channel name]
- desc: create the channel with the specified name
**/names:**
- syntax: /names
- desc: display the list of online users
Implementation Structure:
- IRC_Server: The main server class, handles all packet transfer
- irc_db: The database to keep track of all connections and channels, and delete records for user logging out
- Channel: The class that handles packets for in channel communication as well as user leaving
Organization for irc_db:
- Users: store as bidirectional dictionary, allows to be able to access by name and socket
- ChannelList: organized using dictionary as name:Channel_Obj
Limitations:
- Lacks support for server network
- Lack authentication
- Require unique NICK for registration, which can be problematic when use for large user base
Reference:
[IRC RFC](https://tools.ietf.org/html/rfc2812#section-3.1.7)
[Example of IRC Communication](http://chi.cs.uchicago.edu/chirc/irc_examples.html)
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment