├── user_pass.txt ├── readme.txt ├── client.py └── server.py /user_pass.txt: -------------------------------------------------------------------------------- 1 | columbia 116bway 2 | seas summerisover 3 | csee4119 lotsofassignments 4 | foobar passpass 5 | windows withglass 6 | google partofalphabet 7 | facebook wastetime 8 | wikipedia donation 9 | network seemsez 10 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | README: 2 | 3 | A. Code Description : 4 | 5 | The program uses multi threading concept. There are two scripts , one running on the server side , the other on the client side as it is with every chat server. The server relays the messages. The server side script accepts connections and creates ?2 threads ? for every client that connects 6 | (i) To process and respond to the who , wholast , send , broadcast commands to the client 7 | (ii) To keep sending the messages that are destined to the client. 8 | 9 | 10 | Similarly the client uses two threads , one for prompting commands from commands from user and getting inputs. The other thread receives chat messages from the server and displays them. Note : The responses for the commands are received in thread1 11 | 12 | Each pair of thread is connected by a separate socket. (2 sockets are used per client-server) 13 | One socket between server and client is exclusively used for the server to get the messages from the client?s message queue and send it to the client thread that receives the chat messages. 14 | 15 | Shared queues are used. When a connection occurs , a queue is added to the set of queues with the index being the file descriptor of the connection. Hence every client has a separate index and therefore a separate queue in the server. All queues belong to the set sendqueue. 16 | A message intended for the client is put into the respective queue and the other thread is responsible for taking these messages out of the queue and send it to the appropriate client. 17 | 18 | Every queue access is wrapped up in a mutex lock to prevent clashes or race condition 19 | 20 | Common lists are used to store the information of online users , blocked users , offline users and mapping between username and the file descriptor to access the sendqueue. 21 | 22 | B. Development environment : 23 | Mac OS X 24 | Bash shell 25 | Python 3 26 | Editor used : Textpad 27 | 28 | C. How to run the code ? 29 | There are only two scripts. Place them on the different systems. If same systems use 127.0.0.1 as the address. 30 | Server is invoked from the terminal as 31 | Python server.py 32 | 33 | Client is invoked from the terminal as 34 | Python client.py 35 | D. Commands to use : 36 | 1. whoelse displays other users who are online 37 | 38 | 2. wholast lastnumber displays the users who were online until lastnumber minutes ago 39 | 3. send sends to 40 | 4. Broadcast message displays message to the users who are online 41 | 5. Broadcast user message broadcasts the message to list of users 42 | 6. Inbox - Shows the messages that were sent when the client was offline. It also shows chat history. 43 | 44 | Offline messaging implementation : The offline messaging system works in such a way that it stores the offline messages on a file that is allocated to the client. (Every client has a text file which has their respective chat history) The inbox command makes the server retrieve command from the file and send it across the client. 45 | P.s : File system is used instead of queue because queue cannot show history.Also offline messages is not available for broadcasted messages. Broadcast is generally done only to those who are online 46 | 47 | P.P.S : The chat messages gets displayed without any proper alignment on the terminal screen. Sometimes they might clash with the command prompt. 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import socket 4 | import time 5 | import threading 6 | from threading import Thread 7 | from SocketServer import ThreadingMixIn 8 | 9 | 10 | 11 | 12 | class ServerThread(Thread): 13 | 14 | def __init__(self,socket): 15 | Thread.__init__(self) 16 | self.socket = socket 17 | 18 | # print "New thread started for write" 19 | 20 | 21 | 22 | 23 | def run(self): 24 | print "send" 25 | 26 | while True: 27 | starttime = time.time() 28 | 29 | 30 | command = raw_input(" Enter command: ") 31 | 32 | 33 | 34 | curtime = time.time() 35 | 36 | if curtime - starttime > float(TIME_OUT): #Client tiems itself out after TIME_OUT idle time 37 | print " Your session has been timed out! Please log in again :(" 38 | self.socket.close() 39 | sys.exit() 40 | else: 41 | 42 | self.socket.send(command) 43 | 44 | ack = self.socket.recv(BUFFER_SIZE) 45 | print ack 46 | if ack == "logged out": 47 | log = 1 48 | self.socket.close() 49 | sys.exit() 50 | 51 | elif ack == "user already exists": 52 | print "user alread exists -.-" 53 | self.socket.close() 54 | sys.exit() 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | class ServerThreadread(Thread): 65 | 66 | def __init__(self,socket): 67 | Thread.__init__(self) 68 | self.socket = socket 69 | 70 | # print "New thread started for chat display" 71 | 72 | 73 | 74 | 75 | def run(self): 76 | 77 | s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 78 | s2.connect((TCP_IP, TCP_PORT2)) 79 | welcomemsg = s2.recv(BUFFER_SIZE) 80 | chat = "initial" 81 | print welcomemsg 82 | 83 | while True: 84 | if log == 0: 85 | # print "inside loop" 86 | chat=s2.recv(BUFFER_SIZE) 87 | print chat 88 | time.sleep(5) 89 | 90 | if log == 1: 91 | # print "going to exit" 92 | s2.close() 93 | sys.exit() 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | TCP_IP = sys.argv[1] 103 | TCP_PORT = int(sys.argv[2]) 104 | TCP_PORT2 = 125 105 | BUFFER_SIZE = 1024 106 | threads = [] 107 | global log 108 | log = 0 109 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 110 | s.connect((TCP_IP, TCP_PORT)) 111 | TIME_OUT = s.recv(BUFFER_SIZE) #Server exchanges tmeout details with client at the start of every socket 112 | count = [1, 2, 3] 113 | status = 0 114 | while status == 0: 115 | number = 0 116 | username = raw_input("Enter username: ") 117 | s.send(username) 118 | usernamecheck = s.recv(BUFFER_SIZE) 119 | if ( usernamecheck == "invalid login" ): 120 | print "Invalid username , enter details again " 121 | status =0 122 | 123 | continue 124 | else: 125 | if usernamecheck == " blocked ": 126 | status = 2 127 | print "I said you have been blocked for 60 seconds. Be patient -.-" 128 | sys.exit() 129 | elif usernamecheck == "same user": 130 | status = 1 131 | print "User is already online. Who are you ?" 132 | sys.exit() 133 | else: 134 | while status == 0: 135 | password = raw_input("Enter password: ") 136 | s.send(password) 137 | passwordcheck = s.recv(BUFFER_SIZE) 138 | if ( passwordcheck == "invalid password" ): 139 | 140 | status = 0 141 | number = number + 1 142 | if number == 3: 143 | status = 2 144 | break 145 | 146 | else: 147 | print " Invalid password , enter details again " 148 | continue 149 | else: 150 | status = 1 151 | 152 | 153 | if number == 3 and status == 2: 154 | print "I don't know. My instructor has asked me to block you for 60s." 155 | sys.exit() 156 | 157 | 158 | 159 | 160 | if ( status == 1 ): 161 | print "logged in" 162 | try: 163 | 164 | newthread = ServerThread(s) 165 | newthread.daemon = True 166 | newthread2 = ServerThreadread(s) 167 | newthread2.daemon = True 168 | newthread.start() 169 | newthread2.start() 170 | threads.append(newthread) 171 | threads.append(newthread2) 172 | while True: 173 | for t in threads: 174 | t.join(600) 175 | if not t.isAlive(): 176 | break 177 | break 178 | 179 | 180 | except KeyboardInterrupt: 181 | command = "logout" 182 | s.send(command) 183 | sys.exit() 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import socket 3 | import sys 4 | import collections 5 | import time 6 | import Queue 7 | import threading 8 | 9 | from threading import Thread 10 | from SocketServer import ThreadingMixIn 11 | 12 | class ClientThread(Thread): 13 | 14 | def __init__(self,socket,ip,port): 15 | Thread.__init__(self) 16 | self.socket = socket 17 | self.ip = ip 18 | self.port = port 19 | print "New thread started" 20 | 21 | 22 | 23 | 24 | def run(self): 25 | 26 | 27 | 28 | status = 0 29 | userpresent = 0 30 | while True: 31 | self.socket.send(str(TIME_OUT)) 32 | data2 = "successful" 33 | while userpresent == 0: 34 | num = 0 35 | userdata = self.socket.recv(2048) 36 | 37 | 38 | if not userdata: break 39 | 40 | line = open('user_pass.txt').readlines() 41 | for userpass in line: 42 | user = userpass.split(" ") 43 | if userdata == user[0]: 44 | userpresent = 1 45 | if userpresent == 0: 46 | 47 | data2 = "invalid login" 48 | status = 0 49 | print data2 50 | self.socket.send(data2) 51 | 52 | continue 53 | else: 54 | 55 | for p in blockusers: 56 | print "blockusers: " , p 57 | val = p.partition(" ") 58 | valin = val[2].partition(" ") 59 | curtime =time.time() 60 | if val[0] == userdata and float(valin[0]) >= curtime - BLOCK_TIME and valin[2] == str(ip): #Blocktime and ip 61 | data2 = " blocked " 62 | status = 2 63 | 64 | for p in curusers: 65 | print "curusers:", p 66 | if userdata == p: 67 | data2 = "same user" 68 | status = 1 69 | print data2 70 | if data2 == " blocked ": 71 | 72 | self.socket.send(data2) 73 | status = 2 74 | elif data2 == "same user": 75 | self.socket.send(data2) 76 | status = 1 77 | 78 | 79 | if data2 == "successful": 80 | self.socket.send(data2) 81 | passpresent = 0 82 | while status == 0: 83 | passdata = self.socket.recv(2048) 84 | validity = userdata + " " + passdata 85 | 86 | if (validity not in open('user_pass.txt').read()): 87 | data2 = "invalid password" 88 | print data2 89 | num = num + 1 90 | if num == 3: 91 | status = 2 92 | self.socket.send(data2) 93 | print "breaking" 94 | break 95 | 96 | 97 | else: 98 | status = 0 99 | 100 | 101 | self.socket.send(data2) 102 | 103 | else: 104 | data2 = "successful" 105 | self.socket.send(data2) 106 | for p in offlineusers: 107 | t = p.partition(" ") 108 | if t[0] == userdata: 109 | lock.acquire() 110 | offlineusers.remove(p) 111 | lock.release() 112 | 113 | lock.acquire() 114 | curusers.append(userdata) 115 | lock.release() 116 | print userdata + " logged in" 117 | status = 1 # 0 for offline , 1 for online , 2 for blocked 118 | logtime=time.time() 119 | fd = self.socket.fileno() 120 | userfd = userdata + " " + str(fd) 121 | lock.acquire() 122 | userfdmap.append(userfd) 123 | lock.release() 124 | 125 | 126 | 127 | 128 | 129 | 130 | # print "[+] thread ready for "+ip+":"+str(port) 131 | if (status == 2 and num == 3): 132 | blockuserdata = userdata + " " + str(time.time()) + " " + str(ip) 133 | blockusers.append(blockuserdata) 134 | fd = self.socket.fileno() 135 | lock.acquire() 136 | del sendqueues[fd] 137 | lock.release() 138 | print blockuserdata, " blocked for 60 seconds" 139 | sys.exit() 140 | 141 | 142 | else: 143 | 144 | while True: 145 | self.socket.settimeout(TIME_OUT) 146 | command = self.socket.recv(2048) 147 | if "send " in command: 148 | content = command.partition(" ") 149 | contentinner = content[2].partition(" ") 150 | sendmsg = userdata + ": " + contentinner[2] 151 | 152 | receiver = contentinner[0] 153 | errorflag = 1 154 | 155 | 156 | for z in userfdmap: 157 | zi = z.partition(" ") 158 | if zi[0] == receiver: 159 | receiverfd = int(zi[2]) 160 | 161 | errorflag = 0 162 | lock.acquire() 163 | sendqueues[receiverfd].put(sendmsg) 164 | lock.release() 165 | 166 | 167 | 168 | if errorflag == 1: 169 | replymsg = "User is offline. Don't worry , we will get it delivered." #offline messaging 170 | file = open('{0}.txt'.format(receiver),"a+") 171 | localtime = time.asctime( time.localtime(time.time()) ) 172 | sendmsg = sendmsg + " " + "on" + " " + localtime 173 | file.write(sendmsg) 174 | file.write("\n") 175 | file.close() 176 | 177 | else: 178 | 179 | replymsg = "message sent" 180 | 181 | self.socket.send(replymsg) 182 | 183 | elif "broadcast user" in command: 184 | content = command.split(" ") 185 | receivers = [] 186 | messageflag = 0 187 | sendmessage = userdata + ":" 188 | for i, val in enumerate(content): 189 | if ( i != 0 or i != 1): 190 | if val != "message" and messageflag == 0: 191 | receivers.append(val) 192 | elif val == "message": 193 | i = i + 1 194 | messageflag = 1 195 | elif messageflag == 1: 196 | sendmessage = sendmessage + " " + val 197 | 198 | 199 | 200 | 201 | 202 | for p in receivers: 203 | print p 204 | errorflag = 1 205 | for z in userfdmap: 206 | zi = z.partition(" ") 207 | if p == zi[0]: 208 | receiverfd = int(zi[2]) 209 | print receiverfd 210 | errorflag = 0 211 | lock.acquire() 212 | sendqueues[receiverfd].put(sendmessage) 213 | lock.release() 214 | if errorflag == 1: 215 | 216 | replymsg = "Cannot broadcast message to all , few users offline" 217 | else: 218 | replymsg = "message broadcasted" 219 | self.socket.send(replymsg) 220 | 221 | 222 | 223 | elif command == "inbox": 224 | sendmsg = "" 225 | file = open('{0}.txt'.format(userdata),"r") 226 | file.seek(0) 227 | first_char = file.read(1) 228 | if not first_char: 229 | sendmsg = "Your Inbox is empty" 230 | else: 231 | file.seek(0) 232 | for msg in file: 233 | sendmsg = sendmsg + "\n" + msg 234 | self.socket.send(sendmsg) 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | elif command == "whoelse": 243 | online = " " 244 | for p in curusers: 245 | 246 | if p != userdata: 247 | online = online + p + " " 248 | self.socket.send(online) 249 | elif "wholast" in command : 250 | div = command.partition(" ") 251 | print div[0] 252 | print div[2] 253 | lastnumber = int(div[2])*60 254 | #lastnumber = float(lastnumber)*60.0 255 | offline = " " 256 | for p in offlineusers: 257 | print p 258 | t = p.partition(" ") 259 | curtime = time.time() 260 | if ( curtime - float(lastnumber) ) <= float(t[2]): 261 | offline = offline + t[0] + " " 262 | 263 | self.socket.send(offline) 264 | elif command == "logout": 265 | curusers.remove(userdata) 266 | offlinedata = userdata + " " + str(logtime) 267 | lock.acquire() 268 | offlineusers.append(offlinedata) 269 | lock.release() 270 | print offlinedata , "removed" 271 | logoutack = "logged out" 272 | self.socket.send(logoutack) 273 | print "[+] thread disconnected for "+ip+":"+str(port) 274 | fd = self.socket.fileno() 275 | lock.acquire() 276 | del sendqueues[fd] 277 | userfdmap.remove(userfd) 278 | lock.release() 279 | sys.exit() 280 | 281 | elif "broadcast message" in command: 282 | message = command.partition(" ") 283 | messagef = message[2].partition(" ") 284 | 285 | msg = userdata + ": " + messagef[2] 286 | lock.acquire() 287 | for q in sendqueues.values(): 288 | q.put(msg) 289 | lock.release() 290 | ack = "broadcasted" 291 | self.socket.send(ack) 292 | else: 293 | error = "Invalid command. Please enter a proper one" 294 | self.socket.send(error) 295 | 296 | 297 | 298 | lock.acquire() 299 | curusers.remove(userdata) 300 | lock.release() 301 | offlinedata = userdata + " " + str(logtime) 302 | lock.acquire() 303 | offlineusers.append(offlinedata) 304 | lock.release() 305 | print offlinedata , "removed" 306 | print "logged out" 307 | sys.exit() 308 | 309 | class ClientThreadread(Thread): 310 | def __init__(self,sock): 311 | Thread.__init__(self) 312 | 313 | self.sock = sock 314 | 315 | print "New thread for chat relying started" 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | def run(self): 324 | 325 | 326 | tcpsock2.listen(1) 327 | (conn2, addr) = tcpsock2.accept() 328 | welcomemsg = "hi" 329 | conn2.send(welcomemsg) 330 | chat = "initial" 331 | print "ind here is" 332 | print self.sock.fileno() 333 | while True: 334 | for p in userfdmap: #userfdmap contains mapping between usernames and their socket's file despcriptor which we use as index to access their respective queue 335 | if str(self.sock.fileno()) in p: 336 | connectionpresent = 1 337 | else: 338 | connectionpresent = 0 #We will use this to implement other features - no use as of now 339 | 340 | 341 | 342 | try: 343 | chat = sendqueues[self.sock.fileno()].get(False) 344 | 345 | print chat 346 | conn2.send(chat) 347 | except Queue.Empty: 348 | 349 | chat = "none" 350 | time.sleep(2) 351 | except KeyError, e: 352 | pass 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | lock = threading.Lock() 370 | global command 371 | command = "" 372 | 373 | sendqueues = {} 374 | TCP_IP = '0.0.0.0' 375 | TCP_PORT = int(sys.argv[1]) 376 | TCP_PORT2 = 125 377 | BUFFER_SIZE = 20 # Normally 1024, but we want fast response 378 | TIME_OUT = 1800.0 #seconds - For time_out Block_time is 60 seconds 379 | BLOCK_TIME = 60.0 380 | 381 | 382 | 383 | curusers = [] 384 | offlineusers = [] 385 | blockusers = [] 386 | userlog = {} 387 | userfdmap = [] 388 | 389 | 390 | tcpsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 391 | tcpsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 392 | #host = socket.gethostname() 393 | tcpsock.bind(('', TCP_PORT)) 394 | 395 | tcpsock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 396 | tcpsock2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 397 | tcpsock2.bind(('', TCP_PORT2)) 398 | 399 | 400 | threads = [] 401 | 402 | while True: 403 | tcpsock.listen(6) 404 | print "Waiting for incoming connections..." 405 | (conn, (ip,port)) = tcpsock.accept() 406 | q = Queue.Queue() 407 | lock.acquire() 408 | 409 | 410 | sendqueues[conn.fileno()] = q 411 | lock.release() 412 | 413 | 414 | print "new thread with " , conn.fileno() 415 | newthread = ClientThread(conn,ip,port) 416 | newthread.daemon = True 417 | newthread.start() 418 | newthread2 = ClientThreadread(conn) 419 | newthread2.daemon = True 420 | 421 | newthread2.start() 422 | threads.append(newthread) 423 | threads.append(newthread2) 424 | 425 | 426 | 427 | for t in threads: 428 | t.join() 429 | 430 | print "eND" 431 | --------------------------------------------------------------------------------