用Python实现一个简单的聊天程序
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
⽤Python实现⼀个简单的聊天程序
学习完⽹络套接字之后,我产⽣了写⼀个聊天程序的想法。
思路很简单,⾸先创建⼀个套接字,客户端和服务器可以通过套接字通信;然后,为了使通信变为全双⼯,接收信息和发送信息由两个线程分别完成;最后,我还给客户端加了⼀个图形界⾯,使它看起来不是那么丑陋。
得益于Python的强⼤,所有这些实现起来都不是特别困难。
⽐如Python中的很多数据结构,像列表,都是线程安全的,这样就免去了处理⼀⼤堆线程锁的烦恼;Python提供了⽅便的图形界⾯接⼝,tkinter,使得像我这种从来没有过图形界⾯编程经验的⼈,也可以在短时间内创建⼀个还说得过去的界⾯;同时,Python还拥有⼤量high-level Interface,相⽐起那些更贴近系统的低级接⼝,尤其是对于那些对底层操作系统不太熟的⼈来说,使⽤起来更加⽅便,并且在⼤部分情况下,这些⾼级接⼝都能满⾜你的需求;Python中的垃圾回收机制,让程序员从内存管理这项繁重的劳动中解脱出来,程序员再也不⽤像在C 中那样⼩⼼谨慎,释放掉每⼀块不⽤的内存;最后不得不提到Python简洁灵活的语法,它使得代码more readable and more close to humanity,很多语法即使你以前从来没有⽤过,你也很有可能猜对它该怎麽⽤!
下⾯来介绍⼀下程序的基本功能。
客户端提供了简单的⽤户登录、登出、注册、发送信息、选择联系⼈等功能。
⾸先,输⼊服务器的IP地址,点击connect(或按回车键),在客户端与服务器之间建⽴连接;然后,输⼊⽤户账号(只⽀持6位数字),点击log in(或按回车键),如果登录成功,log in 按钮会变为log out,再点击即位登出操作。
登录成功之后,⽤户可以在contacts⼀栏⾥边选择联系⼈发送信息。
服务器主要⽤来处理⽤户连接以及信息的转发。
服务器为每个⽤户提供⼀个user-buffer,所有发给该⽤户的信息都先存在这个缓冲区中,由服务器统⼀进⾏转发;每⼀个登录的⽤户都享有⼀个独⽴的线程,该线程时刻监听⽤户的输⼊并把它存⼊合适的缓冲区中。
程序运⾏截图
1'''
2TCP Client Version 2.2
32015.12.19
4'''
5
6import selectors
7import queue
8import re
9import threading
10from socket import *
11from tkinter import *
12from time import ctime
13
14 BUFSIZE = 1024
15
16#lock = threading.Lock() # Global Lock
17 que = queue.Queue(4096)
18
19class GUI(object):
20'''
21 This is the top module. It interacts with users. When a button is clicked
22 or Return is pressed, a corresbonding event trigered. Connect is used to
23 estanblish a TCP connection with server while Log In tells server a user
24 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 on
26 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 it
28 is not concerned with send and receive details. In fact it is wisdom to
29 throw this burden to others. We just do what we can and do it perfectly.
31def__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('<Return>', 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('<Return>', 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('<Return>', 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('<Double-1>', 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('<Return>', 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 bel_cat = Label(self.frame_rgt, text='Contacts')
96 bel_cat.grid(row=2, column=0, sticky=W)
97
98def send_method(self, ev=None):
99 data = self.entry_msg.get()
100 self.entry_msg.delete(0, END)
101if not data:
102pass
103else:
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
110def recv_method(self):
111try:
112 data = que.get(block=False)
113except:
114pass
115else:
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)
120if 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)
124 self.root.after(200, self.recv_method) # runs every 200ms
125
126def 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 = True
136 self.recv_thread.start()
137 self.root.after(200, self.recv_method)
138
139def login_method(self, ev=None):
140 ID = self.entry_log.get()
141if re.match(r'^[0-9]{6}$', ID) == None:
142pass
143else:
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
148def 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
153def 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
161def add_method(self, ev=None):
162 ID = self.entry_add.get()
163if re.match(r'[0-9]{6}', ID) == None:
164pass
165else:
166 self.listbox_cat.insert(END, ID)
167
168class Send(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 for
172 GUI module.
173'''
174def__init__(self, fd):
175 self.fd = fd
176 self.sel = selectors.DefaultSelector()
177 self.sel.register(self.fd, selectors.EVENT_WRITE)
178
179def send(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
187class Recv(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'''
192def__init__(self, fd):
193 self.fd = fd
194 self.sel = selectors.DefaultSelector()
195 self.sel.register(self.fd, selectors.EVENT_READ)
196
197def recv(self):
198while True:
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
207def__call__(self):
208 self.recv()
209
210class Connt(object):
211'''
212 This module deals with establishing a TCP connection with host.
213'''
214def__init__(self, IP):
215 self.HOST = IP
217 self.ADDR = (self.HOST, self.PORT)
218 self.tcpCliSock = socket(AF_INET, SOCK_STREAM)
219
220def connect(self):
221 self.tcpCliSock.connect(self.ADDR)
222
223def__call__(self):
224 self.connect()
225
226def main():
227 gui = GUI()
228 gui.root.mainloop()
229
230if__name__ == '__main__':
231 main()
232
1'''
2TCP Server Version 2.2
32015.12.19
4'''
5import selectors
6import threading
7import queue
8import re
9from socket import *
10from time import ctime
11
12 BUFSIZE = 1024
13 HOST = input('HOST: ')
14 PORT = 21567
15 ADDR = (HOST, PORT)
16 tcpServSock = socket(AF_INET, SOCK_STREAM)
17 tcpServSock.bind(ADDR)
18 tcpServSock.listen(100)
19
20 user_list = [] # all registered users
21 user_buff = {} # each user has a buffer
22 acti_user = [] # once a user logs in, he/she becomes active
23 acti_user_list = []
24
25 sel = selectors.DefaultSelector()
26 lock = threading.Lock()
27
28def recv():
29global tcpServSock
30while True:
31 lock.acquire(blocking=True)
32 ret = sel.select(timeout=1)
33 lock.release()
34for key, event in ret: # some socket is readable
35if key.fileobj == tcpServSock: # a new connection comes
36 new_socket, new_addr = tcpServSock.accept()
37print('connected from %s [%s]' %(new_addr, ctime()))
38 lock.acquire(blocking=True)
39 sel.register(new_socket, selectors.EVENT_READ)
40 lock.release()
41else: # some one clicked Log In
42try:
43 ID = key.fileobj.recv(BUFSIZE).decode('utf-8')
44except:
45print('%s disconnected' %(key.fileobj))
46 lock.acquire(blocking=True)
47 sel.unregister(key.fileobj)
48 lock.release()
49continue
50if ID not in user_list:
51 user_list.append(ID)
52 acti_user_list.append(ID)
53 user_buff[ID] = queue.Queue(4096)
54print('%s signed up [%s]' %(ID, ctime()))
55else:
56if ID in acti_user_list: # already logged in, deny
57 server_msg = 'FROME SERVER: already logged in'
58 key.fileobj.send(server_msg.encode('utf-8'))
59continue
60else:
61 acti_user_list.append(ID)
62print('%s logged in [%s]' %(ID, ctime()))
63 user = User(key.fileobj, ID) # create an instance for logged in user
64 acti_user.append(user)
65 user()
66 lock.acquire(blocking=True)
67 sel.unregister(key.fileobj)
68 lock.release()
69
70
71def forward():
72while True:
73for eachUser in acti_user:
74if eachUser.zombie == True: # this user has logged out
75 lock.acquire(blocking=True)
78 lock.release()
79 acti_user.remove(eachUser)
80del(eachUser)
81continue
82if eachUser.death == True:
83 acti_user.remove(eachUser)
84del(eachUser)
85continue
86while user_buff[eachUser.ID].qsize():
87 msg = user_buff[eachUser.ID].get()
88## some ckecking is desired, for the socket may not be writable 89 eachUser.socket.send(msg.encode('utf-8'))
90
91
92class User(object):
93def__init__(self, socket, ID):
94 self.socket = socket
95 self.ID = ID
96 self.zombie = False
97 self.death = False
98 self.contact = None
99 self.sel = selectors.DefaultSelector()
100 self.sel.register(self.socket, selectors.EVENT_READ)
101def recv(self):
102while True:
103 self.sel.select()
104try:
105 msg = self.socket.recv(BUFSIZE).decode('utf-8')
106except:
107print('%s disconnected' %(self.socket))
108 acti_user_list.remove(self.ID)
109 self.death = True
110return None
111if re.match(r'^::[0-9]{6}', msg):
112# user wants to contact with some one
113 contact = msg[2:]
114if contact not in user_list:
115 server_msg = 'FROME SERVER: no such user'
116 self.socket.send(server_msg.encode('utf-8'))
117else:
118 self.contact = msg[2:]
119elif msg == '::LOG OUT':
120# user wants to log out
121 acti_user_list.remove(self.ID)
122print('%s logged out [%s]' %(self.ID, ctime()))
123 self.zombie = True
124print(self.zombie)
125return None
126else:
127if self.contact != None:
128 msg = '[' + 'frome ' + self.ID + '' + ctime() + ']\n' + msg
129 user_buff[self.contact].put(msg)
130else:
131 server_msg = 'FROME SERVER: choose a contact first' 132 self.socket.send(server_msg.encode('utf-8'))
133
134def__call__(self):
135 self.recv_thread = threading.Thread(target=self.recv)
136 self.recv_thread.daemon = True
137 self.recv_thread.start()
138
139
140def main():
141 fd = open(r'.\user.txt', 'r+')
142for eachUser in fd:
143 user_list.append(eachUser[0:6])
144 user_buff[eachUser[0:6]] = queue.Queue(4096)
145print(user_list)
146
147 sel.register(tcpServSock, selectors.EVENT_READ)
148
149 recv_thread = threading.Thread(target=recv)
150 recv_thread.daemon = True
151 recv_thread.start()
152
153 forward_thread = threading.Thread(target=forward)
154 forward_thread.daemon = True
155 forward_thread.start()
156
157while True:
158pass# infinate loop
159
160if__name__ == '__main__':
161 main()。