python之socket

python之socket

一、初識socket

socket 是網絡連接端點,每個socket都被綁定到一個特定的IP地址和端口。IP地址是一個由4個數組成的序列,這4個數均是範圍 0~255中的值(例如,220,176,36,76);端口數值的取值範圍是0~65535。端口數小於1024的都是為眾所周知的網絡服務所保留的 (例如Web服務使用的80端口);最大的保留數被存儲在socket模塊的IPPORT_RESERVED變量中。你也可以為你的程序使用另外的端口數 值。

    不是所有的IP地址都對世界的其它地方可見。實際上,一些是專門為那些非公共的地址所保留的(比如形如192.168.y.z或10.x.y.z)。地址127.0.0.1是本機地址;它始終指向當前的計算機。程序可以使用這個地址來連接運行在同一計算機上的其它程序。

     IP地址不好記,你可以花點錢為特定的IP地址註冊一個主機名或域名(比如使用www.jb51.net代替222.76.216.16)。域名服務器(DNS)處理名字到IP地址的映射。

  多少信息通過一個網絡被傳送基於許多因素,其中之一就是使用的協議。許多的協議是基於簡單

的、低級協議以形成一個協議棧。例如HTTP協議,它是用在Web瀏覽器與Web服務器之間通信的協議,它是基於TCP協議,而TCP協議又基於IP協議。

  當 在你自己的兩個程序間傳送信息的時候,你通常選擇TCP或UDP協議。

TCP協議在兩端間建立一個持續的連接,並且你所發送的信息有保證的按順序到達它們 的目的地。

UDP不建立連接,它的速度快但不可靠。你發送的信息也可能到不了另一端;或它們沒有按順序到達。有時候一個信息的多個複製到達接收端,即使你只發送了一次。

二、使用地址和主機名

  socket模塊提供了幾個函數用於使用主機名和地址來工作。

  socket 也定義了一些變量來代表保留的IP地址。

INADDR_ANY和INADDR_BROADCAST是被保留的IP地址分別代表任意IP地址和廣播地 址; 

INADDR_LOOPBACK  代表loopback設備,總是地址127.0.0.1。這些變量是32位位元組數字形式的。

getfqdn([name])函數返回關於給定主機名的全域名(如果省略,則返回本機的全域名)。

server.py

import socket

ip_port=('127.0.0.1',5555)

s=socket.socket()

s.bind(ip_port)

s.listen(5)

conn,addr=s.accept()

while True:

    try:

        recv_data=conn.recv(1024)

        if str(recv_data,encoding='utf-8')=='exit':break

        print(str(recv_data,encoding='utf8'))

        send_data=recv_data.upper()

        conn.send(send_data)

    #如果客戶端斷開連接,服務器會拋出異常,自動停止

    except Exception as ex:

        break

conn.close() 

client.py

import socket

ip_port=('127.0.0.1',5555)

s=socket.socket()

s.connect(ip_port)

while True:

    data=input('>>').strip()

    if len(data)==0:continue

#如果直接輸入空格或者回車,直接會卡住,因為服務器方面recv不會接受空值,會導致阻塞

    s.send(bytes(data,encoding='utf8'))

    if data=='exit':break

    recv_data=s.recv(1024)

    print(str(recv_data,encoding='utf8'))

s.close()

三、使用低級的socket通信

儘管Python提供了一些封裝,使得使用socket更容易,但是你也可以直接使用socket來工作。

1、創建和銷毀socket

socket 模塊中的socket(family,type[,proto])函數創建一個新的socket對象。family的取值通常是AF_INET。type 的取值通常是SOCK_STREAM(用於定向的連接,可靠的TCP連接)或SOCK_DGRAM(用於UDP):

>>> from socket import *

>>> s=socket(AF_INET,SOCK_STREAM)

family和type參數暗指了一個協議,但是你可以使用socket的第三個可選的參數(proto的取值如IPPROTO_TCP或IPPROTO_RAW)來指定所使用的協議。代替使用IPPROTO_XX變量,你可以使用函數

getprotobyname:

>>> getprotobyname('tcp')

6

>>> IPPROTO_TCP

6

fromfd(fd,type[,proto]) 是一個很少被使用的函數,它用來從打開的一個文件描述符創建一個socket對象(文件描述符由文件的fileno()方法返回)。文件描述符與一個真實 的socket連接,而非一個文件。socket對象的fileno()方法返回關於這個socket的文件描述符。

當你使用完工 socket對象時,你應調用close()方法顯式的關閉socket以儘快釋放資源(儘管socket被垃圾回收器回收時將自動被關閉)。另外,你也

可以使用shutdown(how)方法來關閉連接一邊或兩邊。參數0阻止socket接收數據,1阻止發送,2阻止接收和發送。

2、連接socket

當 兩個socket連接時(例如使用TCP),一端監聽和接收進來的連接,而另一端發起連接。監聽端創建一個socket,調用bind(address) 函數去綁定一個特定的地址和端口,調用listen(backlog)來臨聽進來的連接,最後調用accept()來接收這個新的,進來的連接,下面是在 服務器端的代碼:

>>> s=socket(AF_INET,SOCK_STREAM)

>>> s.bind(('127.0.0.1',44444))

>>> s.listen(1)

>>> q,v=s.accept() #返回socket q和地址v

注意:上面的代碼將一直處於等待直到連接被建立。下面我們再打開另一個Python解釋器,用作客戶端;然後鍵入如下代碼:

>>> from socket import *

>>> s=socket(AF_INET,SOCK_STREAM)

>>> s.connect(('127.0.0.1',44444) #發起連接

好了,我們驗證一下連接是否建立了。我們在服務器端鍵入以下代碼來發送一條信息:

>>> q.send('hello,i come from pythontik.com')

註:有時可能出現send() argument 1 must be string or buffer,not str 錯誤,原因可能是您的機器不支持UTF-8字符集,臨時解決方案是q.send(b' hello…')

31 #發送的位元組數

在客戶端鍵入以下代碼來接收信息:

>>> s.recv(1024)

'hello,i come from pythontik.com'

你傳遞給bind和connect的地址是一個關於AF_INET的socket的元組(ipAddress,port)。代替connect,你也可以調 用connect_ex(address)方法。如果背後對C的connect的調用返回一個錯誤,那麼connect_ex也將返回一個錯誤(否則返回 0代表成功),代替引發一個異常。

當你調用listen時,你給了它一個參數,這個數值表示在等待隊列中允許放置的進來的連接總數。

當等待隊列已滿時,如果有更多的連接到達,那麼遠程端將被告知連接被拒絕。

在socket模塊中的SOMAXCONN變量表明了等待隊列所能容納的最大量。

accept()方法返回形如bind和connect的一個地址,代表遠程socket的地址。下面顯示變量v的值:

>>> v

('127.0.0.1', 1334)

UDP是不定向的連接,但是你仍然可以使用給定的目的地址和端口來調用connect去關聯一個socket。

3、發送和接收數據

函 數send(string[,flags])發送給定的字符串到遠程socket。

sendto(string[,flags],address)發送給 定的字符串到一個特定的地址。

通常,

send方法用於可靠連接的socket,

sendto方法用於不可靠連接的socket,但是如果你在一個 UDP socket上調用connect來使它與一個特定的目標建立聯繫,那麼這時你也可以使用send方法來代替sendto。

send和sendto都返回實際發送的位元組數。當你快速發送大量的數據的時候,你可能想去確保全部信息已被發送,那麼你可以使用如下的一個函數:

def safeSend(sock,msg):

sent=0

while msg:

i=sock.send(msg)

if i==-1: #發生了錯誤

return -1

sent+=i

msg=msg[i:]

time.sleep(25)

return sent

recv(bufsize[,flags]) 方法接收一個進來的消息。如果有大量的數據在等待,它只返回前面的bufsize位元組數的數據。recvfrom(bufsize[,flags])做同 樣的事,除了它使用AF_INET socket的返回值是(data,(ipAddress,port)),這便於你知道消息來自哪兒(這對於非連接的 socket是有用的)。

send,sendto,recv和recvfrom方法都有一個可選的參數flags,默認值為0。你可以通過對socket.MSG_*變量進行組合(按位或)來建立flags的值。這些值因平台而有所不同,但是最通用的值如下所示:

MSG_OOB:處理帶外數據(既TCP緊急數據)。

MSG_DONTROUTE:不使用路由表;直接發送到接口。

MSG_PEEK:返回等待的數據且不把它們從隊列中刪除。

例如,如果你有一個打開的socket,它有一個消息等待被接收,你可以接收這個消息後並不把它從進來的數據的隊列中刪除:

>>> q.recv(1024,MSG_PEEK)

'hello'

>>> q.recv(1024,MSG_PEEK) #因為沒有刪除,所以你可以再得到它。

'hello'

makefile([mode[,bufsize]]) 方法返回一個文件類對象,其中封裝了socket,以便於你以後將它傳遞給要求參數為一個文件的代碼(或許你喜歡使用文件的方法來代替send和 recv)。這個可選的mode和bufsize參數的取值和內建的open函數一樣。

4、使用socket選項

socket對象的getpeername()和 getsockname()方法都返回包含一個IP地址和端口的二元組(這個二元組的形式就像你傳遞給connect和bind的)。 getpeername返回所連接的遠程socket的地址和端口,getsockname返回關於本地socket的相同信息。

在默認 情況下,socket是阻塞式的,意思就是socket的方法的調用在任務完成之前是不會返回的。例如,如果存儲向外發送的數據的緩存已滿,你又企圖發送 更多的數據,那麼你對send的調用將被阻塞直到它能夠將更多的數據放入緩存。你可以通過調用setblocking(flag)方法(其中flag取值 是0,setblocking(0))來改變這個默認行為,以使socket為非阻塞式。當socket為非阻塞式的時候,如果所做的動作將導致阻塞,將 會引起error異常。下面一段代碼將試圖不斷地接受新的連接並使用函數proce***equest來處理。如果一個新連接無效,它將間隔半秒再試。另 一方法是在你的監聽socket上調用select或poll來檢測一個新的連接的到達。

別的socket的選項可以使用 setsockopt(level,name,value)和getsockopt(level,name[,buflen])方法來設置和獲取。 socket代表了一個協議棧的不同層,level參數指定了選項應用於哪一層。level的取值以SOL_開頭(SOL_SOCKET,SOL_TCP 等等)。name表明你涉及的是哪個選項。對於value,如果該選項要求數值的值,value只能傳入數字值。你也可以傳遞入一個緩存(一個字符串), 但你必須使用正確的格式。對getsockopt,不指定buflen參數意味你要求一個數字值,並返回這個值。如果你提供了 buflen,getsockopt返回代表一個緩存的字符串,它的最大長度是buflen的位元組數。下面的例子設置了一個socket的用於發送的緩存 尺寸為64KB:

>>> s=socket(AF_INET,SOCK_STREAM)

>>> s.setsockopt(SOL_SOCKET,SO_SNDBUF,65535)

要得到一個包在被路由丟棄前所能有的生命周期(TTL)和跳數,你可以使用如下代碼:

>>> s.getsockopt(SOL_IP,IP_TTL)

32

5、數值轉換

由於不同平台的位元組順序不一樣,所以當在網絡中傳輸數據時我們使用標準的網絡位元組順序。nthol(x)和ntohs(x)函數要求一個網絡位元組順序的數值並把它轉換為當前主機位元組順序的相同數值,而htonl(x)和htons(x)則相反:

>>> import.socket

>>> socket.htons(20000) #轉換為一個16位的值

8270

>>> socket.htonl(20000) #轉換為一個32位的值

541982720

>>> socket.ntohl(541982720)

20000

使用SocketServers

SocketServers模塊為一組socket服務類定義了一個基類,這組類壓縮和隱藏了監聽、接受和處理進入的socket連接的細節。

1、SocketServers家族

TCPServer和UDPServer都是SocketServer的子類,它們分別處理TCP和UDP信息。

注意:SocketServer也提供UnixStreamServer(TCPServer的子類)和UNIXdatagramServer(UDPServer的子類),它們都如同其父類一樣除了在創建監聽socket時使用AF_UNIX代替了AF_INET。

默 認情況下,socket服務一次處理一個連接,但是你可以使用ThreadingMixIN和ForkingMixIn類來創建任一 SocketServer的線程和子進程。實際上,SocketServer模塊提供了一些對些有用的類來解決你的麻煩,它們 是:ForkingUDPServer、ForkingTCPServer、ThreadingUDPServer、 ThreadingTCPServer、ThreadingUnixStreamServer和 ThreadingUnixDatagramServer。

SocketServer以通常的方法處理進入的連接;要使它更有用,你應該 提供你自己的請求處理器類給它以便它傳遞一個socket去處理。SocketServer模塊中的BaseRequestHandler類是所有請求處 理器的父類。假設,例如你需要寫一個多線程的電子郵件服務器,首先你要創建一個MailRequestHandler,它是 BaseRequestHandler的子類,然後把它傳遞給一個新創建的SocketServer:

import SocketServer

…#創建你的MailRequestHandler

addr=('220.172.20.6',25) #監聽的地址和端口

server=SocketServer.ThreadingTCPServer(addr,MailRequestHandler)

server.serve_forever()

每 次一個新的連接到來時,這個server創建一個新的MailRequestHandler實例並調用它的handle()方法來處理這個新的請求。因為 server繼承自ThreadingTCPServer,對於每個新的請求它都啟動一個單獨的線程來處理這個請求,以便於多個請求能夠被同時處理。如果 用handle_request()代替server_forever,它將一個一個的處理連接請求。server_forever 只是反覆調用 handle_request而已。

一般來說,你只需使用socket服務之一,但是如果你需要創建你自己的子類的話,你可以覆蓋我們下面提到的方法來定製它。

當 服務被第一次創建的時候,__init__函數調用server_bind()方法來綁定監聽socket(self.socket)到正確的地址 (self.server_address)。然後調用server_activate()來激活這個服務(默認情況下,調用socket的listen 方法)。

這個socket服務不做任何事情直到調用了handle_request或serve_forever方法。 handle_request調用get_request()去等待和接收一個新的socket連接,然後調用 verify_request(request,client_address)去看服務是否會處理這個連接(你可以在訪問控制中使用這個,默認情況下 verify_request總是返回true)。如果會處理這個請求,handle_request然後調用 process_request(request,client_address),如果 process_request(request,client_address)導致一個異常的話,將調用 handle_error(request,client_address)。默認情況下,process_request簡單地調用 finish_request(request,client_address);子進程和線程類覆蓋了這個行為去開始一新的進程或線程,然後調用 finish_request。finish_request實例化一個新的請求處理器,請求處理器輪流調用它們的handle()方法。

當SocketServer創建一個新的請求處理器時,它傳遞給這個處理器的__init__函數的self變量,以便於這個處理器能夠訪問關於這個服務的信息。

SocketServer 的fileno()方法返回監聽socket的文件描述符。address_family成員變量指定了監聽socket的socket族(如 AF_INET),server_address包含了監聽socket被綁定到的地址。socket變量包含監聽socket自身。

2、請求處理器

請 求處理器有setup()、handle()和finish()方法,你可以覆蓋它們來定製你自己的行為。一般情況下,你只需要覆蓋handle方法。 BaseRequestHandler的__init__函數調用setup()方法來做初始化的工作,handle()服務於請求,finish()用 於執行清理工作,如果handle或setup導致一個異常,finish不會被調用。記住,你的請求處理器會為每個請求創建一個新的實例。

request 成員變量有關於流(TCP)服務的最近接受的socket;對於數據報服務,它是一個包含進入消息和監聽socket的元組。 client_address包含發送者的地址,server有對SocketServer的一個引用(通過這你可以訪問它的成員,如 server_address)。

下面的例子實現了一個EchoRequestHandler,這作為一個服務端它將客戶端所發送的數據再發送回客戶端:

>>> import SocketServer

>>> class EchoRequestHandler(SocketServer.BaseRequestHandler):

… def handle(self):

… print 'Got new connection!'

… while 1:

… mesg=self.request.recv(1024)

… if not msg:

… break

… print 'Received:',msg

… self.request.send(msg)

… print 'Done with connection'

>>> server=SocketServer.ThreadingTCPServer(('127.0.0.1',12321),EchoReuestHandler)

>>> server.handle_request() #執行後將等待連接

Got new connection!

Received: Hello!

Received: I like Tuesdays!

Done with connection

打開另一個Python解釋器作為客戶端,然後執行如下代碼:

>>> from socket import *

>>> s=socket(AF_INET,SOCK_STREAM)

>>> s.connect(('120.0.0.1',12321))

>>> s.send('Hello!')

6

>>> print s.recv(1024)

Hello!

>>> s.send('I like Tuesdays!')

16

>>> print s.recv(1024)

I like Tuesdays!

>>> s.close()

SocketServer 模塊也定義了BaseRequestHandler的兩個子類:StreamRequestHandler和 DatagramRequestHandler。它們覆蓋了setup和finish方法並創建了兩個文件對象rfile和wfile,你可以用這兩個文 件對象來向客戶端讀寫數據,從而代替使用socket方法。

socket的阻塞或同步編程

三、使用socket

網 絡編程中最基本的部分就是socket(套接字)。socket有兩種:服務端socket和客戶端 socket。在你創建了一個服務端socket之 後,你告訴它去等待連接。然後它將監聽某個網絡地址(形如:xxx.xxx.xxx.xxx:xxx) 直到客戶端連接。然後這兩端就可以通信了。

處理客戶端socket通常比處理服務端socket要容易一點,因為服務端必須時刻準備處理來自客戶端的連接,並且它必須處理多個連接,而客戶端只需要簡單的連接,然後做點什麼,然後斷開連接。

實 例化一個socket時,可以指定三個參數:地址系列(默認為socket.AF_INET)、流socket(這是個默認 值: socket.SOCK_STREAM)或數據報socket(socket.SOCK_DGRAM)、協議(默認值是0)。對於簡單的 socket,你可以不指定任何參數而全部使用默認值。

服務端socket在使用bind方法之後調用listen方法去監聽一個給定的 地址。然後,客戶端socket就可以通過使用connect方法(connect方法所使用的地址參數與bind相同)去連接服務端。listen方法 要求一個參數,這個參數就是等待連接隊列中所能包含的連接數。

一旦服務端socket調用了listen方法,就進入了臨聽狀態,然後通 常使用一個無限的循環:1、開始接受客房端的連接,這通過調用accept方法來實現。調用了這個方法後將處於阻塞狀態(等待客戶端發起連接)直到一個客 戶端連接,連接後,accept返回形如(client,address)的一個元組,其中client是一個用於與客戶端通信的 socket,address是客戶端的形如xxx.xxx.xxx.xxx:xxx的地址;2、然後服務端處理客戶端的請求;3、處理完成之後又調用 1。

關於傳輸數據,socket有兩個方法:send和recv。send使用字符串參數發送數據;recv參數是位元組數,表示一次接受的數據量,如果你不確定一次該接受的數據量的話,最好使用1024。

下面給出一個最小的服務器/客戶機的例子:

服務端:

import socket

s = socket.socket()

host = socket.gethostname()

port = 1234

s.bind((host, port))

s.listen(5)

while True:

c, addr = s.accept()

print 'Got connection from', addr

c.send('Thank you for connecting')

c.close()

客戶端:

import socket

s = socket.socket()

host = socket.gethostname()

port = 1234

s.connect((host, port))

print s.recv(1024)

注意:如果你使用Ctrl-C來停止服務端的話,如果再次使用相同的端口可能需要等待一會兒。

四、使用SocketServer

SocketServer模塊簡單化了編寫網絡服務器的工作。

它提供了四個基本的服務類:TCPServer(使用TCP協議)、UDPServer(使用數據報)、UnixStreamServer、

UnixDatagramServer。UnixStreamServer和UnixDatagramServer用於類Unix平台。

這四個類處理請求都使用同步的方法,也就是說,在下一個請求處理開始之前當前的請求處理必須已完成

用SocketServer創建一個服務器需要四步:

1、通過子類化BaseRequestHandler類和覆蓋它的handle()方法來創建一個請求處理器類,用於處理進來

的請求;

2、實例化服務類如TCPServer,並傳遞給它參數:服務器地址和請求處理器類;

3、調用服務實例對象的handle_request()或serve_forever()方法去處理請求。

下面使用SocketServer用同步的方法寫一個最簡單的服務器:

from SocketServer import TCPServer, StreamRequestHandler

#第一步。其中StreamRequestHandler類是BaseRequestHandler類的子類,它為流socket定義了

#rfile和wfile方法

class Handler(StreamRequestHandler):

def handle(self):

addr = self.request.getpeername()

print 'Got connection from', addr

self.wfile.write('Thank you for connecting')

#第二步。其中''代表運行服務器的主機

server = TCPServer(('', 1234), Handler)

#第三步。serve_forever()導致進入循環狀態

server.serve_forever()

注意:使用阻塞或同步的方法一次只能連接一個客戶端,處理完成後才能連接下一個客戶端。

非阻塞或異步編程

五、簡單的例子

  1.socket server 

#!/usr/bin/env python

# -*- coding:utf-8 -*-

# Author:Alex Li

#!/usr/bin/env python

# -*- coding:utf-8 -*-

#import SocketServer

import socketserver,json,sys,subprocess

class MyServer(socketserver.BaseRequestHandler):

    def handle(self):

        # print self.request,self.client_address,self.server

        self.request.sendall(bytes('歡迎進入管理界面.',encoding="utf-8"))

        while True:

            #第一次獲取客服端發送內容

            data = self.request.recv(1024)

            #如果輸入為空,繼續下一次輸入

            if len(data) == 0:break

            #如果輸入為查看目錄內容

            if str(data,encoding='utf-8')=='dir':

                cmd=subprocess.Popen(data.decode(),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)

                cmd_res=cmd.stdout.read()

                if not cmd_res:

                    cmd_res=cmd.stderr.read()

                if len(cmd_res)==0:

                    cmd_res=bytes("result",encoding='gbk')

                self.request.send(cmd_res)

                #接受下一次任務

                continue

            #目錄切換,如果接收內容包含cd

            if 'cd' in str(data,encoding='utf-8'):

                cmd=subprocess.Popen(data.decode(),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)

                cmd_res=cmd.stdout.read()

                if not cmd_res:

                    cmd_res=cmd.stderr.read()

                if len(cmd_res)==0:

                    cmd_res=bytes("result",encoding='gbk')

                self.request.send(bytes("%s成功"%data,encoding='gbk'))

                continue

            print("data", data)

            print("[%s] says:%s" % (self.client_address,data.decode() ))

            #執行傳輸任務

            task_data = json.loads( data.decode()  )

            task_action = task_data.get("action")

            if hasattr(self, "task_%s"%task_action):

               func = getattr(self,"task_%s" %task_action)

               func(task_data)        

            else:

               print("task action is not supported",task_action)

    def task_put(self,*args,**kwargs):

        print("—put",args,kwargs)         

        filename = args[0].get('filename')

        filesize = args[0].get('file_size')

        server_response = {"status":200}

        self.request.send(bytes( json.dumps(server_response), encoding='utf-8'  ))

        f = open(filename,'wb')

        recv_size = 0

        while recv_size < filesize:

            data = self.request.recv(4096)

            f.write(data)

            recv_size += len(data)

            #進度條

            rate = recv_size / filesize

            rate_num= int(rate * 100)

            r = 'r[%-100s]%d%%' % ('=' * rate_num,rate_num, )

            sys.stdout.write(r)

            print("connect success")

            sys.stdout.flush()

        f.close()

if __name__ == '__main__':

    server = socketserver.ThreadingTCPServer(('127.0.0.1',8009),MyServer)

    server.serve_forever()

  2.socket client

#!/usr/bin/env python

# -*- coding:utf-8 -*-

# Author:Alex Li

import socket

import os ,json

ip_port=('127.0.0.1',8009)

#買手機

s=socket.socket()

#撥號

s.connect(ip_port)

#發送消息

welcome_msg = s.recv(1024)

print("from server:",welcome_msg.decode())

while True:

    send_data=input(">>: ").strip()

    if len(send_data) == 0:continue

    #查看目錄下的文件

    if send_data=='dir':

         s.send(bytes(send_data,encoding='utf-8'))

         recv_data=s.recv(1024)

         print(str(recv_data,encoding='gbk'))

         continue

    cmd_list = send_data.split( ' ')

    if len(cmd_list) <2:continue

    task_type = cmd_list[0]

    #目錄切換方法

    if task_type=='cd':

         s.send(bytes(send_data,encoding='utf-8'))

         recv_data=s.recv(1024)

         print(str(recv_data,encoding='gbk'))

         continue

    #文件傳輸

    if task_type == 'put':

        abs_filepath = cmd_list[1]

        if os.path.isfile(abs_filepath):

            file_size = os.stat(abs_filepath).st_size

            filename = abs_filepath.split("\")[-1]

            print('file:%s size:%s' %(abs_filepath,file_size))

            msg_data = {"action":"put",

                        "filename":filename,

                        "file_size":file_size}

            s.send(  bytes(json.dumps(msg_data),encoding="utf-8")  )

            server_confirmation_msg = s.recv(1024)

            confirm_data = json.loads(server_confirmation_msg.decode())

            if confirm_data['status'] ==200:

                print("start sending file ",filename)

                f = open(abs_filepath,'rb')

                for line in f:

                    s.send(line)

                print("send file done ")

                #跳出本次任務,開始下個任務

                continue

        else:

            print("33[31;1mfile [%s] is not exist33[0m" % abs_filepath)

            continue

    else:

        print("doesn't support task type",task_type)

        continue

    #s.send(bytes(send_data,encoding='utf8'))

    #收消息

    recv_data=s.recv(1024)

    print(str(recv_data,encoding='utf8'))

    #掛電話

s.close()

 六、socket之IO多路復用

  Python中有一個select模塊,其中提供了:select、poll、epoll三個方法,分別調用系統的 select,poll,epoll 從而實現IO多路復用

  rlist,wlist,elist=select.select(inputs,outputs,[sk,],1)

  select()方法提供四個參數 並提取返回值,rlist監聽socket對象的連接變化,wlist監聽服務端和客戶端的收發消息變化,elist監聽socket對象的操作正確與否,最後一個參數表明該服務端沒個多少秒去監聽一次;

  socket單進程單線程只提供了一個服務端,一個客戶端的交互,採用了select的IO多路復用後,避免了客戶端連接及收發消息時的阻塞,從而達到了偽多線程的效果,以下是簡單代碼:

  服務端:

# -*- coding: utf-8 -*-

__author__ = 'pxb'

import socket,select

sk=socket.socket()

sk.bind(('127.0.0.1',9999),)

sk.listen(5)

inputs=[sk,]

outputs=[]

#按用戶存放收到的客戶端消息

messages={}

while True:

    #IO多路復用,將每個變化的socket對象存到rlist列表中

    rlist,wlist,elist=select.select(inputs,outputs,[sk,],1)

    print(len(inputs),len(rlist),len(wlist),len(outputs))

    for r in rlist:

        if r==sk:

            conn,address=r.accept()

            inputs.append(conn)

            messages[conn]=[]

            conn.sendall(bytes('hello',encoding='utf-8'))

            print('hello')

        else:

            print("=====================")

            try:

                ret=r.recv(1024)

                if not ret:

                    raise Exception('斷開連接')

                else:

                    outputs.append(r)

                    messages(r).append(ret)

            except Exception as e:

                inputs.remove(r)

                del messages[r]

    for w in wlist:

        #w.sendall(bytes('response',encoding='utf-8'))

        msg=messages[w].pop()

        resp=msg+bytes('response',encoding='utf-8')

        w.sendall(resp)

        outputs.remove(w)

客戶端:

# -*- coding: utf-8 -*-

__author__ = 'pxb'

import socket

sk=socket.socket()

sk.connect(('127.0.0.1',9999),)

data=sk.recv(1024)

print(data)

while True:

    inp=input('>>>>')

    sk.sendall(bytes(inp,encoding='utf-8'))

    print(sk.recv(1024))

sk.close()

七、作用域

1、python中無塊級作用域

if 1==1:

    name='alex'

print('無塊級作用域測試',name)

for i in range(10):

    name1=i

print('無塊級作用域測試',name1)

2、python以函數為作用域(函數以外調用局部變量會報錯)

def fun():

    name2='alex'

fun()

print('函數作用域測試',name2)

3、python的作用域在執行之前已經確定(作用域不關乎程序的執行順序,基於以上兩條進行)

name='mqq'

def f1():

    print(name)

def f2():

    name='pxb'

    return  f1

ret=f2()

ret()

八、多線程

多線程是為了更好地利用資源及節省時間而形成的一套獨有的編碼方式,結合alex甄嬛西遊傳,python的多線程有一定的局限性,即每個進程同時只能派出一個線程去執行,io操作不佔用cpu資源,計算代碼會消耗cpu

多線程演示小代碼:

import time

def f1(arg):

    time.sleep(1)

    print(arg)

#單進程,單線程的應用程序

import  threading

t=threading.Thread(target=f1,args=(123,))

t.setDaemon(True)#true表示主線程不在此等子線程

t.start()#不代表當前線程會被立刻執行,取決於操作系統

t.join(2)#表示主線程在此等待,直到子線程完畢

        #參數,表示主線程在此最多等待n秒

print('end')

print('end')

print('end')

f1(888)

九、socket源碼查看

以下面集成關係為例,class A,class B(A),class C(A),class D(B),class E(C)

以最底層 方法開始集成,不同版本的python集成順序是不同的

python2.7:D、B、A、E、C

python3.5:D、B、E、C、A

Python Socket模塊中包含一些有用IP轉換函數,說明如下:

socket.ntohl(x)   把32位正整數從網絡序轉換成主機位元組序。

socket.ntohs(x)   把16位正整數從網絡序轉換成主機位元組序。

socket.htonl(x)   把32位正整數從主機位元組序轉換成網絡序。

socket.htons(x)   把16位正整數從主機位元組序轉換成網絡序。

socket.inet_aton(ip_string) 轉換IPV4地址字符串(192.168.10.8)成為32位打包的二進制格式(長度為4個位元組的二進制字符串),它不支持IPV6。inet_pton()支持IPV4/IPV6地址格式。

socket.inet_ntoa(packed_ip)  轉換32位打包的IPV4地址為IP地址的標準點號分隔字符串表示。

socket.inet_pton(address_family,ip_string)

轉換IP地址字符串為打包二進制格式。地址家族為AF_INET和AF_INET6,它們分別表示IPV4和IPV6。

socket.inet_ntop(address_family,packed_ip)

轉換一個打包IP地址為標準字符串表達式,例如:「5aef:2b::8」或「127.0.0.1」。

>>>import socket

>>>import struct

>>>socket.ntohl(struct.unpack("i",socket.inet_aton("10.10.58.64"))[0])

168442432L

>>>socket.inet_ntoa(struct.pack("i", socket.htonl(168442432L)))

'10.10.58.64'

>>>struct.unpack("=I", socket.inet_aton("190.10.58.64"))

(1077545662,)

>>>socket.inet_ntoa(struct.pack("=I", 1077545662))

'190.10.58.64'

注意

socket.inet_ntoa(struct.pack('I',int(row[27]))) 和

socket.inet_ntoa(struct.pack('I',socket.htonl(int(row[27])))) 區別

# 從IP地址字符串轉換為整數值

defIp2Int(ip_string):

         return struct.unpack(「!I」,socket.inet_aton(ip))[0]

# 從網絡位元組序的數字轉換為IP地址(點號分隔)

def Int2Ip(ip):

         return socket.inet_ntoa(struct.pack(「!I」,ip))

將數據在網絡位元組序和主機位元組序之間相互轉化。

通過調用ntohl和htonl函數,l代表長整型32bit,s代表短整型16bit。

import socket  

def convert_integer():  

    data = 1234  

    # 32-bit  

    print "Original: %s => Long  host byte order: %s, Network byte order: %s" %(data, socket.ntohl(data), socket.htonl(data))  

    # 16-bit  

    print "Original: %s => Short  host byte order: %s, Network byte order: %s" %(data, socket.ntohs(data), socket.htons(data))  

if __name__ == '__main__':  

    convert_integer()  

可以調用gettimeout()獲取默認的超時時間,而調用settimeout()可以設置一個超時時間。

AF = Address Family 

PF = Protocol Family

意思就是 AF_INET 主要是用於互聯網地址,而 PF_INET 是協議相關,通常是sockets和端口,AF_INET address即使用IP。

import socket  

def socket_timeout():  

[python] view plain copy

    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 

    print "Default socket timeout:%s"%s.gettimeout()#獲取套接字超時時間  

    s.settimeout(100)#設置套接字超時時間  

    print "Current socket timeout:%s"%s.gettimeout()<span style="font-family: Arial, Helvetica, sans-serif;">#獲取套接字超時時間</span>  

if __name__ == '__main__':  

    socket_timeout()  

網絡位元組順序NBO(Network Byte Order): 按從高到低的順序存儲,在網絡上使用統一的網絡位元組順序,可以避免兼容性問題。

主機位元組順序(HBO,Host Byte Order): 不同的機器HBO不相同,與CPU設計有關,數據的順序是由cpu決定的,而與操作系統無關。

如 Intel x86結構下, short型數0x1234表示為34 12, int型數0x12345678表示為78 56 34 12

如 IBM power PC結構下, short型數0x1234表示為12 34, int型數0x12345678表示為12 34 56 78

1、Socket的地址表示單獨的字符串,用於AF_UNIX地址族(host,port)對,用於AF_INET地址族。其中host是一字符串,可以是『www.google.com』 域名形式或是『203.208.46.180』這種ipv4地址的形式;port是一整數。

(host,port,flowinfo,scopeid)四元組(4-tuple),用於AF_INET6地址族

2、錯誤與異常

    所有的錯誤都將引發異常。一般的異常有如 invalidargument type 或是 out-of-memoryconditions。與socket或地址語義相關的錯誤將引發socket.error異常。與Socket相關的異常有:

socket.error:由Socket相關錯誤引發

socket.herror:由地址相關錯誤引發

socket.gaierror:由地址相關錯誤,如getaddrinfo()或getnameinfo()引發

socket.timeout:當socket出現超時時引發。超時時間由settimeout()提前設定

    (與異常相伴的錯誤信息請查閱API說明)

3、常用函數

socket.has_ipv6:判斷平台是否支持IPV6

socket.create_connection(address[,timeout[, source_address]]):

創建一個正在監聽的地址,並返回Socket對象

socket.getaddrinfo(host,port, family=0, socktype=0, proto=0, flags=0):

返回一個包含5元組的list,用來獲得host的地址信息

socket.gethostbyname(hostname):

將host主機名轉換為ipv4地址

socket.gethostbyname_ex(hostname):根據hostname獲取一個主機關於IP和名稱的全面的信息。

功能擴展的gethostbyname函數,返回主機名、主機別名列表、主機IP地址列表

socket.gethostname():返回python解釋器運行的機器hostname,返回當前主機名

socket.gethostbyaddr(ip_address):

通過ip地址,返回包括主機名的三元組:(hostname, aliaslist, ipaddrlist)

socket.getnameinfo(sockaddr,flags):

socket.getprotobyname(protocolname):

socket.getservbyname(servicename[,protocolname]):

通過給定的服務名,協議名,返回該服務所在的端口號

socket.getservbyport(port[,protocolname]):

返回該端口號的服務名,如『80』:『http』

socket.socket([family[,type[, proto]]]):

通過給定的地址族,socket類型和端口號,創建新的Socket。默認地址族為AF_INET,socket類型為SOCK_STREAM,端口號為0或省略

socket.socketpair([family[,type[, proto]]]):可用平台,unix

socket.fromfd(fd, family,type[, proto]):可用平台,unix

socket.ntohl(x):將32位正整數從網絡位元組序轉換為機器位元組序

socket.ntohs(x):將16為正整數從網絡位元組序轉換為機器位元組序

socket.htonl(x):將32為正整數從機器位元組序轉換為網絡位元組序

socket.htons(x):將16位正整數從機器位元組序轉換為網絡位元組序

socket.inet_aton(ip_string):

將點分十進制字符串ipv4地址形式轉化為32位二進制形式,即一個4個字符的字符串,一般用於標準C庫函數中的struct_in_addr

socket.inet_ntoa(packed_ip):上個函數的反操作

socket.inet_pton(address_family,ip_string):類似上述操作,可用平台,部分unix

socket.inet_ntop(address_family,packed_ip):類似上述操縱,可用平台,部分unix

socket.getdefaulttimeout():返回socket默認超時時間,以秒計(float)

socket.setdefaulttimeout(timeout):設置Socket默認超時時間,以秒計(float)

socket.SocketType:這是python的類型對象,表示socket的類型

4、Socket對象方法

socket.accept():返回(conn,address)對,其中conn是新的socket對象,在其上可以發送和接收數據;address是另一端的socket地址

socket.bind(address):將socket綁定到地址上,該socket必須之前沒有做過綁定操作

socket.close():關閉socket,該socket之後對該socket所做的的所有操作將失敗,遠端連接將不會收到數據。當虛擬機進行垃圾回收時,該socket將被自動關閉

socket.connect(address):連接該地址上的遠端Socket

socket.connect_ex(address):類似上面操作,但出錯時返回錯誤代碼而非觸發異常,可以很好的支持異步連接

socket.fileno():

socket.getpeername():

socket.getsockname():返回Socket自己的地址,對查找端口號有用

socket.getsockopt(level,optname[, buflen]):

socket.ioctl(control,option):可用平台 windows

socket.listen(backlog):監聽socket連接,參數表示最大連接隊列數。該參數與系統有關。通常是5,最小為0

socket.makefile([mode[,bufsize]]):返回與socket相關的file對象

socket.recv(bufsize[,flags]):接收數據,返回表示接收到數據的String。buffersize表示一次接收到的數據的最大量

socket.recvfrom(bufsize[,flags]):接收數據,返回(String ,address)對。

socket.recvfrom_into(buffer[,nbytes[,flags]]):接收數據,將其寫入參數buffer而非生成字符串。返回(nbyte,address),其中nbyte是接收到的數據量,address是發送端的地址。

socket.recv_into(buffer[,nbytes[, flags]]):接收數據,區別於上的是,僅返回nbyte——接收數量,沒有地址

socket.send(string[,flags]):發送數據,該socket必須與遠端socket連接。返回發送的數據量。程序自己負責檢查是否所有的數據均已發送,並自己處理未發送數據。

socket.sendall(string[,flags]):發送數據。與上函數不同的是,該函數會持續發送數據直到數據發送完畢或出現錯誤為止。若成功發送,返回none,但當錯誤發生時,將無法判斷發送了多少數據。

socket.sendto(string[,flags],address):向socket發送數據,該socket不應該連接遠端socket,因為目的socket使用地址表示的。該函數返回發送的數據量

socket.setblocking(flag):設置阻塞或非阻塞模式。flag=0時被設置為非阻塞模式,其他為阻塞模式。新建的socket均為阻塞模式。當在非阻塞模式下,如果recv()函數執行中,沒有收到任何數據,或是send()函數沒有立即處理掉數據,error異常將會被觸發;在阻塞模式下,執行函數將被阻塞直到其可以執行。s.setblocking(0)等同於 s.settimeout(0.0),s.setblocking(1)等同於s.settimeout(None)

socket.settimeout(value):設置阻塞模式下socket的超時時間,其值可以是非負數的float類型,以秒計,或是None。若給定float,socket的後續操作若在給定超時時間內沒有完成,將觸發timeout異常;若給定None,則使超時設置失效

socket.gettimeout():返回超時時間(float,以秒計)或None

socket.setsockopt(level,optname, value):

socket.shutdown(how):

socket.family:python類型,socket族

socket.type:python類型,socket類型

socket.proto:python類型,socket協議