1、引言
Python中提供了豐富的開源庫,方便開發者快速就搭建好自己所需要的應用程序。本文通過編寫基於tcp/ip協議的通信程序來熟悉python中socket以及多線程的使用。
2、python中的多線程以及socket的使用
在編寫聊天程序程序之前,我們先熟悉一下python中多線程以及socket的使用方法。
2.1、多線程使用方法
在python中提供了Thread這個類來實現多線程程序的開發。
Thread類的原型如下:
class Thread(group=None, target=None, name=None, args=(), kwargs={})
構造函數能帶有關鍵字參數被調用。這些參數是:
group 應當為 None,為將來實現Python Thread類的擴展而保留。
target 是被 run()方法調用的回調對象. 默認應為None, 意味着沒有對象被調用。
name 為線程名字。默認,形式為'Thread-N'的唯一的名字被創建,其中N 是比較小的十進制數。
args是目標調用參數的tuple,默認為()。
kwargs是目標調用的參數的關鍵字dictionary,默認為{}。
而Thread類還提供了很多方法,而本文只講述程序中所需要的1個方法,其他的方法讀者可以根據需要去查閱python的官方幫助文檔。
start():開啟一個線程
下面將通過一段簡單的程序來實驗Thread的使用。
程序如下:
import threading def print_work(cunt): for i in range(cunt): print 'new thread print:',i def main(): t=threading.Thread(target=print_work,args=(10,)) t.start(); sum=0; for i in range(100): sum=sum+i print 'sum=%s' % sum if __name__=="__main__": main()
程序比較簡單,就不多做解釋,不過有2點需要值得注意。
注意:
1、在使用Thread類的時候需要import threading
2、當多線程啟動的方法的參數只有一個參數的時候,實例化Thread的args的參數要表示為(param1,)需要在參數后面打一個逗號,這是因為tuple元組只有一個元素的時候需要在后面加一個逗號,防止歧義。
2.2、socket的使用方法
下面介紹python中socket的使用方法。
注意:
1 在python中使用socket時要import socket
2 在使用socket中又服務器端和客戶端之分
服務器:
1、建立一個基於tcp協議的socket類
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
其中AF_INET指定的ipv4的協議,也可以使用AF_INET6指定ipv6的協議,而STREAM是指定面向流的tcp協議。
2、s.bind(‘', 8089))
綁定一個端口號,其中'127.0.0.1'是客戶端的ip地址,可以使用’0.0.0.0’來綁定網絡中所有的ip,8089是指定的端口,其中端口在小於1024的時候需要有管理員的權限才能綁定。
3、s.listen(5)
開始實行監聽參數:代表連接的最大數量
4、sock, addr = s.accept()
接受一個客戶端的連接,返回的是一個與客戶端保持連接的socket對象以及客戶端的ip地址和端口。該方法也會阻塞線程,直到獲得客戶端的連接。
客戶端:
1、s.connect(('127.0.0.1', 80))
連接到服務器,其中'www.baidu.com’也可以是服務器的ip地址。
2、s.send('hello')
發送數據’hello’。TCP連接創建的是雙向通道,雙方都可以同時給對方發數據。但是誰先發誰后發,怎么協調,要根據具體的協議來決定。
3、s. recv(1024)
接受連接的對方發來的數據。該方法會阻塞當前線程,所以需要一個專門的線程來接受數據。
注意:
同一個端口,被一個Socket綁定了以后,就不能被別的Socket綁定了。
3、基於python的聊天程序的流程設計
在第二點講述了聊天程序需要用到的知識點,以及需要注意的地方,現在我們就開始來設計程序的流程吧。
把程序分為即服務器與客戶端兩個部分。
服務器端的流程如下圖:
其中user對象代表一個客戶端的連接。
類結構如下圖所示:
客戶端的流程設計如下圖:
4、聊天程序的編碼過程
該程序實現的是一個相對比較簡單的聊天程序,由於是基於控制台實現的,所只設計容許兩個人聊天,另外消息的編碼的分割符為|。
4.1、服務器端
首先建立一個User的數據結構,代碼如下:
import socket class User: def __init__(self,skt,username='none'): self.skt=skt self.username=username def send_msg(self,msg): self.skt.send(msg) def logout(self): self.skt.close()
服務端的編碼如下:
import sys import socket import threading,time import User #global variable userlist=[] def hand_user_con(usr): try: isNormar=True while isNormar: data=usr.skt.recv(1024) time.sleep(1) msg=data.split('|')#分析消息 if msg[0]=='login': print 'user [%s] login' % msg[1] usr.username=msg[1] notice_other_usr(usr) if msg[0]=='talk': print 'user[%s]to[%s]:%s' % (usr.username,msg[1],msg[2]) send_msg(msg[1],msg[2])#發送消息給目標用戶,參數1:目標用戶,參數2:消息內容 if msg[0]=='exit': print 'user [%s] exit' % msg[0] isNormar=False usr.close() userlist.remove(usr) except: isNormar=False #通知其他用戶以上的好友 def notice_other_usr(usr): if(len(userlist)>1): print 'The two users' userlist[0].skt.send(("login|%s" % userlist[1].username)) userlist[1].skt.send(("login|%s" % userlist[0].username)) else: print 'The one users' def send_msg(username,msg): for usr in userlist: if(usr.username==username): usr.skt.send(msg) #程序入口 def main(): s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind(('0.0.0.0',9999)) s.listen(5) print u'waiting for connection...' while True: sock,addr=s.accept()#等待用戶連接 user=User.User(sock) userlist.append(user) t=threading.Thread(target=hand_user_con,args=(user,)); t.start() s.close() if(__name__=="__main__"): main()
4.2、客戶端
客戶端的編碼如下:
import sys import socket import threading,time #global variable isNormar=True other_usr='' def recieve_msg(username,s): global isNormar,other_usr print 'Please waiting other user login...' s.send('login|%s' %username) while(isNormar): data= s.recv(1024)#阻塞線程,接受消息 msg=data.split('|') if msg[0]=='login': print u'%s user has already logged in, start to chat' % msg[1] other_usr=msg[1] else: print msg[0] #程序入口 def main(): global isNormar,other_usr try: print 'Please input your name:' usrname=raw_input() s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(("127.0.0.1",9999)) t=threading.Thread(target=recieve_msg,args=(usrname,s)) t.start() except: print 'connection exception' isNormar=False finally: pass while isNormar: msg=raw_input()#接受用戶輸入 if msg=="exit": isNormar=False else: if(other_usr!=''): s.send("talk|%s|%s" % (other_usr,msg))#編碼消息並發送 s.close() if __name__=="__main__": main()
其效果截圖如下:
5、結論
通過編寫該聊天的程序,了解了python中多線程以及socket的使用。該聊天的程序過於簡單,僅僅只是實現了這客戶端-服務端-客戶端信息交互的一個流程,並不是很完善,在很多地方還存在很多異常。