1 '''
2 TCP Client Version 2.23 2015.12.194 '''
5
6 importselectors7 importqueue8 importre9 importthreading10 from socket import *
11 from tkinter import *
12 from time importctime13
14 BUFSIZE = 1024
15
16 #lock = threading.Lock() # Global Lock
17 que = queue.Queue(4096)18
19 classGUI(object):20 '''
21 This is the top module. It interacts with users. When a button is clicked22 or Return is pressed, a corresbonding event trigered. Connect is used to23 estanblish a TCP connection with server while Log In tells server a user24 comes. We also provide an Add button, which allows user to add contacts.25 All the contacts will be displayed in a list box, and a double click on26 each contact will direct the user's message to a specific contact.27 Note that this module is based on other modules like Send and Recv, so it28 is not concerned with send and receive details. In fact it is wisdom to29 throw this burden to others. We just do what we can and do it perfectly.30 '''
31 def __init__(self):32 self.root =Tk()33 self.root.title('Chat')34
35 self.frame_lft =Frame(self.root)36 self.frame_rgt =Frame(self.root)37 self.frame_lft.grid(row=0, column=0)38 self.frame_rgt.grid(row=0, column=1)39
40 self.entry_msg = Entry(self.frame_lft, width=46) #entry, collect input
41 self.entry_msg.grid(row=1, column=0)42 self.entry_msg.bind('', self.send_method)43
44 self.scrollbar_txt = Scrollbar(self.frame_lft, width=1)45 self.scrollbar_txt.grid(row=0, column=2, sticky=W+N+S)46
47 #self.button_qit = Button(self.root, text='Quit', command=self.root.quit)
48 #self.button_qit.pack()
49
50 self.text_msg = Text(self.frame_lft, state=DISABLED, width=49, wrap=WORD) #text, display message
51 self.text_msg.config(font='Fixedsys')52 self.text_msg.grid(row=0, column=0, columnspan=2, sticky=W+N+S+E)53 self.text_msg.config(yscrollcommand=self.scrollbar_txt.set)54 self.scrollbar_txt.config(command=self.text_msg.yview)55
56 self.entry_IP = Entry(self.frame_rgt, width=14) #an IP address is supposed to input here
57 self.entry_IP.grid(row=0, column=0, padx=5, pady=0)58 self.entry_IP.bind('', self.connect_method)59
60 self.button_cnt = Button(self.frame_rgt, text='connect', command=self.connect_method) #click this button to connect
61 self.button_cnt.config(height=1, width=8)62 self.button_cnt.grid(row=0, column=1, padx=0, pady=0)63
64 self.button_snd = Button(self.frame_lft, height=1, text='send', command=self.send_method)65 self.button_snd.config(width=8)66 self.button_snd.grid(row=1, column=1)67
68 ## New features in version 2.2 ##
69 self.entry_log = Entry(self.frame_rgt, width=14) #log in
70 self.entry_log.grid(row=1, column=0, padx=5, pady=0)71 self.entry_log.bind('', self.login_method)72
73 self.button_log = Button(self.frame_rgt, text='log in', command=self.login_method) #first click means log in, second means log out
74 self.button_log.config(width=8)75 self.button_log.grid(row=1, column=1, sticky=W)76
77 self.listbox_cat = Listbox(self.frame_rgt, height=17, width=24)78 self.listbox_cat.insert(END, '000000')79 self.listbox_cat.grid(row=3, column=0, columnspan=2, padx=5, sticky=N+S+E)80 self.listbox_cat.bind('', self.contact_method)81
82 self.scrollbar_cat = Scrollbar(self.frame_rgt, width=1)83 self.scrollbar_cat.grid(row=3, column=2, sticky=W+N+S)84 self.scrollbar_cat.config(command=self.listbox_cat.yview)85 self.listbox_cat.config(yscrollcommand=self.scrollbar_cat.set)86
87 self.entry_add = Entry(self.frame_rgt, width=14)88 self.entry_add.grid(row=4, column=0, padx=5, pady=0)89 self.entry_add.bind('', self.add_method)90
91 self.button_add = Button(self.frame_rgt, width=8, text='add')92 self.button_add.config(command=self.add_method)93 self.button_add.grid(row=4, column=1)94
95 self.label_cat = Label(self.frame_rgt, text='Contacts')96 self.label_cat.grid(row=2, column=0, sticky=W)97
98 def send_method(self, ev=None):99 data =self.entry_msg.get()100 self.entry_msg.delete(0, END)101 if notdata:102 pass
103 else:104 self.text_msg.config(state=NORMAL)105 self.text_msg.insert(END, data+'\n')106 self.text_msg.config(state=DISABLED)107 self.text_msg.see(END)108 self.send.send(data)109
110 defrecv_method(self):111 try:112 data = que.get(block=False)113 except:114 pass
115 else:116 self.text_msg.config(state=NORMAL)117 self.text_msg.insert(END, data+'\n')118 self.text_msg.config(state=DISABLED)119 self.text_msg.see(END)120 if re.match(r'^FROME', data): #log in failed
121 self.entry_log.config(state=NORMAL)122 self.button_log.config(text='log in', command=self.login_method)123
124 self.root.after(200, self.recv_method) #runs every 200ms
125
126 def connect_method(self, ev=None):127 IP =self.entry_IP.get()128 self.connt = Connt(IP) #make an instance of Connt class
129 self.connt() #establish connection
130 self.button_cnt.config(state=DISABLED)131 self.entry_IP.config(state=DISABLED)132 self.send = Send(self.connt.tcpCliSock) #make an instance of Send class
133 self.recv = Recv(self.connt.tcpCliSock) #make an instance of Recv class
134 self.recv_thread = threading.Thread(target=self.recv) #a new thread, dealing with receiving
135 self.recv_thread.daemon =True136 self.recv_thread.start()137 self.root.after(200, self.recv_method)138
139 def login_method(self, ev=None):140 ID =self.entry_log.get()141 if re.match(r'^[0-9]{6}$', ID) ==None:142 pass
143 else:144 self.send.send(ID) #this action is infalliable
145 self.button_log.config(text='log out', command=self.logout_method)146 self.entry_log.config(state=DISABLED)147
148 def logout_method(self, ev=None):149 self.send.send('::LOG OUT')150 self.button_log.config(text='log in', command=self.login_method)151 self.entry_log.config(state=NORMAL)152
153 def contact_method(self, ev=None):154 ID =self.listbox_cat.get(self.listbox_cat.curselection())155 self.send.send('::'+ID)156 self.text_msg.delete(1.0, END) #delete all text
157 self.text_msg.config(state=NORMAL)158 self.text_msg.insert(END, '[to'+ID+' '+ctime()+']\n')159 #if this contact action fails, server will send an error message.
160
161 def add_method(self, ev=None):162 ID =self.entry_add.get()163 if re.match(r'[0-9]{6}', ID) ==None:164 pass
165 else:166 self.listbox_cat.insert(END, ID)167
168 classSend(object):169 '''
170 This module deals with every detail in sending bytes through a socket,171 such as lock, encode, blocking, etc, and provide a send interface for172 GUI module.173 '''
174 def __init__(self, fd):175 self.fd =fd176 self.sel =selectors.DefaultSelector()177 self.sel.register(self.fd, selectors.EVENT_WRITE)178
179 defsend(self, data):180 self.sel.select() #wait until the socket is ready to write
181 #if lock.acquire():
182 self.fd.send(data.encode('utf-8'))183 #lock.release()
184 #else:
185 #pass
186
187 classRecv(object):188 '''
189 This module deals with every detail in receiving bytes from a socket,190 and providing a friendly recv interface for GUI module.191 '''
192 def __init__(self, fd):193 self.fd =fd194 self.sel =selectors.DefaultSelector()195 self.sel.register(self.fd, selectors.EVENT_READ)196
197 defrecv(self):198 whileTrue:199 self.sel.select()200 #if lock.acquire():
201 byte =self.fd.recv(BUFSIZE)202 que.put(byte.decode('utf-8'))203 #lock.release()
204 #else:
205 #pass
206
207 def __call__(self):208 self.recv()209
210 classConnt(object):211 '''
212 This module deals with establishing a TCP connection with host.213 '''
214 def __init__(self, IP):215 self.HOST =IP216 self.PORT = 21567
217 self.ADDR =(self.HOST, self.PORT)218 self.tcpCliSock =socket(AF_INET, SOCK_STREAM)219
220 defconnect(self):221 self.tcpCliSock.connect(self.ADDR)222
223 def __call__(self):224 self.connect()225
226 defmain():227 gui =GUI()228 gui.root.mainloop()229
230 if __name__ == '__main__':231 main()232