經過編寫聊天程序來熟悉python中多線程及socket的用法

一、引言

Python中提供了豐富的開源庫,方便開發者快速就搭建好本身所須要的應用程序。本文經過編寫基於tcp/ip協議的通訊程序來熟悉python中socket以及多線程的使用。python

二、python中的多線程以及socket的使用

在編寫聊天程序程序以前,咱們先熟悉一下python中多線程以及socket的使用方法。服務器

2.一、多線程使用方法

在python中提供了Thread這個類來實現多線程程序的開發。網絡

Thread類的原型以下:數據結構

class Thread(group=None, target=None, name=None, args=(), kwargs={})多線程

構造函數能帶有關鍵字參數被調用。這些參數是:併發

group 應當爲 None,爲未來實現Python Thread類的擴展而保留。app

target 是被 run()方法調用的回調對象. 默認應爲None, 意味着沒有對象被調用。socket

name 爲線程名字。默認,形式爲'Thread-N'的惟一的名字被建立,其中N 是比較小的十進制數。tcp

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點須要值得注意。

注意:

一、在使用Thread類的時候須要import threading

二、當多線程啓動的方法的參數只有一個參數的時候,實例化Thread的args的參數要表示爲(param1,)須要在參數後面打一個逗號,這是由於tuple元組只有一個元素的時候須要在後面加一個逗號,防止歧義。

2.二、socket的使用方法

下面介紹python中socket的使用方法。

注意:

1 在python中使用socket時要import socket

2 在使用socket中又服務器端和客戶端之分

 

服務器:

一、創建一個基於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綁定了。

三、基於python的聊天程序的流程設計

在第二點講述了聊天程序須要用到的知識點,以及須要注意的地方,如今咱們就開始來設計程序的流程吧。

把程序分爲即服務器與客戶端兩個部分。

服務器端的流程以下圖:

 

其中user對象表明一個客戶端的鏈接。

類結構以下圖所示:

 

客戶端的流程設計以下圖:

 

四、聊天程序的編碼過程

  該程序實現的是一個相對比較簡單的聊天程序,因爲是基於控制檯實現的,所只設計允許兩我的聊天,另外消息的編碼的分割符爲|

  4.一、服務器端

  首先創建一個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.二、客戶端  

  客戶端的編碼以下:

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()

  其效果截圖以下:

五、結論

  經過編寫該聊天的程序,瞭解了python中多線程以及socket的使用。該聊天的程序過於簡單,僅僅只是實現了這客戶端-服務端-客戶端信息交互的一個流程,並非很完善,在不少地方還存在不少異常。

相關文章
相關標籤/搜索