1. 程式人生 > >python第八週學習內容

python第八週學習內容

1.socket網路程式設計

1.1概念:

網路套接字是跨計算機網路的連線的端點。今天,計算機之間的大多數通訊都基於網際網路協議;因此大多數網路套接字都是Internet套接字。更準確地說,套接字是一個控制代碼(抽象引用),本地程式可以將其傳遞給網路應用程式程式設計介面(API)以使用該連線,例如“在此套接字上傳送此資料”。

例如,傳送“Hello,world!”通過TCP到地址為1.2.3.4的主機的埠80,可以獲得一個套接字,將其連線到遠端主機,傳送字串,然後關閉套接字。

實現一個socket至少要分以下幾步,(虛擬碼):

1.Socket socket = getSocket(
type = "TCP" #設定好協議型別 2.connect(socket, address = "1.2.3.4" , port = "80" ) #連線遠端機器 3.send(socket, "Hello, world!" ) #傳送訊息 4.close(socket) #關閉連線

套接字API是一種應用程式程式設計介面(API),通常由作業系統提供,允許應用程式控制和使用網路套接字。 Internet套接字API通常基於Berkeley套接字標準。在Berkeley套接字標準中,套接字是檔案描述符(檔案控制代碼)的一種形式,由於Unix哲學“一切都是檔案”,以及套接字和檔案之間的類比:你可以讀,寫,開啟和關閉

套接字地址是IP地址和埠號的組合,很像電話連線的一端是電話號碼和特定分機的組合。 套接字不需要有地址(例如僅用於傳送資料),但如果程式將套接字繫結到地址,則套接字可用於接收發送到該地址的資料。 基於此地址,Internet套接字將傳入的資料包傳遞到適當的應用程式程序或執行緒

Socket Families(地址簇)

socket.AF_UNIX unix本機程序間通訊

socket.AF_INET IPV4 

socket.AF_INET6  IPV6

這些常量表示用於socket()的第一個引數的地址(和協議)系列。 如果未定義AF_UNIX常量,則不支援此協議。 根據系統的不同,可能會有更多常量可用。

Socket Types

socket.SOCK_STREAM  #for tcp

socket.SOCK_DGRAM  #for udp

socket.SOCK_RAW   #原始套接字,普通的套接字無法處理ICMP、IGMP等網路報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通

          過IP_HDRINCL套接字選項由使用者構造IP頭。

socket.SOCK_RDM  #是一種可靠的UDP形式,即保證交付資料報但不保證順序。SOCK_RAM用來提供對原始協議的低階訪問,在需要執行某些特殊操作時使用,如傳送ICMP報文。

          SOCK_RAM通常僅限於高階使用者或管理員執行的程式使用。

socket.SOCK_SEQPACKET #廢棄了

這些常量表示套接字型別,用於socket()的第二個引數。 根據系統的不同,可能會有更多常量可用。 (只有SOCK_STREAM和SOCK_DGRAM似乎通常很有用。)

1.2 Socket 引數

(1)socket.socket(family=AF_INETtype=SOCK_STREAMproto=0fileno=None)  必會

使用給定的地址系列,套接字型別和協議號建立一個新套接字。 地址族應為AF_INET(預設值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。 套接字型別應該是SOCK_STREAM(預設值),SOCK_DGRAM,SOCK_RAW或者其他SOCK_常量之一。 協議號通常為零並且可以省略,或者在地址族是AF_CAN的情況下,協議應該是CAN_RAW或CAN_BCM之一。 如果指定了fileno,則忽略其他引數,從而返回具有指定檔案描述符的套接字。 與socket.fromfd()不同,fileno將返回相同的套接字而不是重複。 這可能有助於使用socket.close()關閉分離的套接字。

(2)socket.socketpair([family[, type[, proto]]])

使用給定的地址系列,套接字型別和協議編號構建一對連線的套接字物件。 地址族,套接字型別和協議號與上面的socket()函式相同。 如果在平臺上定義,則預設系列為AF_UNIX; 否則,預設為AF_INET。

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

連線到偵聽Internet地址(2元組(主機,埠))的TCP服務,並返回套接字物件。 這是一個比socket.connect()更高階的函式:如果host是非數字主機名,它將嘗試為AF_INET和AF_INET6解析它,然後嘗試依次連線到所有可能的地址,直到連線成功。 這樣可以輕鬆編寫與IPv4和IPv6相容的客戶端。

傳遞可選的timeout引數將在嘗試連線之前設定套接字例項上的超時。 如果未提供超時,則使用getdefaulttimeout()返回的全域性預設超時設定。

如果提供,則source_address必須是要連線的套接字的2元組(主機,埠)作為其源地址才能連線。 如果主機或埠分別為'或0,則將使用OS預設行為。

 

(4) socket.getaddrinfo(hostportfamily=0type=0proto=0flags=0) #獲取要連線的對端主機地址

(5) sk.bind(address) 必會

 

sk.bind(address) 將套接字繫結到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。

(6) sk.listen(backlog) 必會

開始監聽傳入連線。backlog指定在拒絕連線之前,可以掛起的最大連線數量。backlog等於5,表示核心已經接到了連線請求,但伺服器還沒有呼叫accept進行處理的連線個數最大為5。這個值不能無限大,因為要在核心中維護連線佇列

 

(7)sk.setblocking(bool) 必會

 

是否阻塞(預設True),如果設定False,那麼accept和recv時一旦無資料,則報錯。

(8) sk.accept() 必會

接受連線並返回(conn,address),其中conn是新的套接字物件,可以用來接收和傳送資料。address是連線客戶端的地址。接收TCP 客戶的連線(阻塞式)等待連線的到來

(9)sk.connect(address) 必會

連線到address處的套接字。一般,address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。

(10)sk.connect_ex(address)

同上,只不過會有返回值,連線成功時返回 0 ,連線失敗時候返回編碼,例如:10061

(11)sk.close() 必會

關閉套接字

(12)sk.recv(bufsize[,flag]) 必會

接受套接字的資料。資料以字串形式返回,bufsize指定最多可以接收的數量。flag提供有關訊息的其他資訊,通常可以忽略。

(13)sk.recvfrom(bufsize[.flag])

與recv()類似,但返回值是(data,address)。其中data是包含接收資料的字串,address是傳送資料的套接字地址。

(14)sk.send(string[,flag]) 必會

將string中的資料傳送到連線的套接字。返回值是要傳送的位元組數量,該數量可能小於string的位元組大小。即:可能未將指定內容全部發送。

(15)sk.sendall(string[,flag]) 必會

將string中的資料傳送到連線的套接字,但在返回之前會嘗試傳送所有資料。成功返回None,失敗則丟擲異常。內部通過遞迴呼叫send,將所有內容傳送出去。

(16)sk.sendto(string[,flag],address)

將資料傳送到套接字,address是形式為(ipaddr,port)的元組,指定遠端地址。返回值是傳送的位元組數。該函式主要用於UDP協議。

(17)sk.settimeout(timeout) 必會

設定套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛建立套接字時設定,因為它們可能用於連線的操作(如 client 連線最多等待5s )

(18)sk.getpeername()  必會

返回連線套接字的遠端地址。返回值通常是元組(ipaddr,port)。

(19)sk.getsockname()

返回套接字自己的地址。通常是一個元組(ipaddr,port)

(20)sk.fileno() 

套接字的檔案描述符

(21)socket.sendfile(fileoffset=0count=None)

傳送檔案 ,但目前多數情況下並無什麼卵用。

1.3 通過socket實現簡單的ssh

程式流程如下:

 

 程式程式碼:

socket_server端:

import socket,os

server = socket.socket()
server.bind(("localhost",9999))
server.listen()
while True:
    conn,addr = server.accept() #阻塞
    while True:
        print("wait for new cmd:")
        cmd = conn.recv(1024).decode()
        if not cmd:
            print("客戶端已斷開")
            break
        cmd_result = os.popen(cmd).read()
        if len(cmd_result) == 0:
            cmd_result = "這條命令錯誤"
            length = len(cmd_result.encode("utf-8"))
        length = len(cmd_result.encode("utf-8"))
        conn.send(str(length).encode("utf-8"))
        conn.recv(1024)
        conn.send(cmd_result.encode("utf-8"))
server.close()

socket_client端:

 

import socket

client = socket.socket()
client.connect(("localhost",9999))
while True:
    cmd = input("輸入指令:")
    if len(cmd) == 0:
        continue
    client.send(cmd.encode("utf-8"))
    length = client.recv(1024).decode()
    print("命令結果大小:",length)
    receive_size = 0
    receive_data = b""
    client.send(b"OK")
    while receive_size < int(length):
        data = client.recv(1024)
        receive_size += len(data)
        receive_data += data
    print("命令結果實際大小:",receive_size)
    print(receive_data.decode())
client.close()

程式執行結果:

#server端:
wait for new cmd:
ipconfig
wait for new cmd:

#client端:
輸入指令:ipconfig
命令結果大小: 1680
命令結果實際大小: 1680

Windows IP 配置


乙太網介面卡 乙太網:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開連線
   連線特定的 DNS 字尾 . . . . . . . : 

無線區域網介面卡 本地連線* 3:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開連線
   連線特定的 DNS 字尾 . . . . . . . : 

無線區域網介面卡 本地連線* 12:

   媒體狀態  . . . . . . . . . . . . : 媒體已斷開連線
   連線特定的 DNS 字尾 . . . . . . . : 

乙太網介面卡 VMware Network Adapter VMnet1:

   連線特定的 DNS 字尾 . . . . . . . : 
   本地連結 IPv6 地址. . . . . . . . : fe80::7ddd:a3e4:9673:512e%7
   IPv4 地址 . . . . . . . . . . . . : 192.168.74.1
   子網掩碼  . . . . . . . . . . . . : 255.255.255.0
   預設閘道器. . . . . . . . . . . . . : 

乙太網介面卡 VMware Network Adapter VMnet8:

   連線特定的 DNS 字尾 . . . . . . . : 
   本地連結 IPv6 地址. . . . . . . . : fe80::4cc1:5dc2:37f:7e7b%17
   IPv4 地址 . . . . . . . . . . . . : 192.168.43.1
   子網掩碼  . . . . . . . . . . . . : 255.255.255.0
   預設閘道器. . . . . . . . . . . . . : 

無線區域網介面卡 WLAN:

   連線特定的 DNS 字尾 . . . . . . . : 
   IPv6 地址 . . . . . . . . . . . . : 2001:da8:215:8f01:8d1d:db29:3fd2:c6d6
   臨時 IPv6 地址. . . . . . . . . . : 2001:da8:215:8f01:dc75:865e:ccf6:e781
   本地連結 IPv6 地址. . . . . . . . : fe80::8d1d:db29:3fd2:c6d6%10
   IPv4 地址 . . . . . . . . . . . . : 10.122.252.64
   子網掩碼  . . . . . . . . . . . . : 255.255.192.0
   預設閘道器. . . . . . . . . . . . . : fe80::274:9cff:fe7d:fadb%10
                                       10.122.192.1

輸入指令:

1.4 通過socket實現簡單的ftp server

1.讀取檔名
2.檢測檔案是否存在
3.開啟檔案
4.檢測檔案大小
5.傳送檔案大小和md5給客戶端
6.等客戶端確認
7.開始邊讀邊發資料
8.md5
9.關閉檔案

程式程式碼:
客戶端:
# -*- coding:utf-8 -*-
#!/user/bin/env.python
#Author:Mr Wu

'''FTP Server'''
import socket,os,hashlib

server = socket.socket()
server.bind(("localhost",1999))
server.listen()
while True:
    conn,addr = server.accept() #阻塞
    while True:
        data = conn.recv(1024).decode()
        if not data:
            print("客戶端已斷開連線!")
            break
        file_cmd,file_name = data.split()
        if os.path.isfile(file_name):
            file_size = os.stat(file_name).st_size
            conn.send(str(file_size).encode("utf-8"))
            conn.recv(1024) #避免粘包
            m = hashlib.md5()
            f = open(file_name,"rb")
            print("開始傳送檔案.....")
            for line in f:
                m.update(line)
                conn.send(line)
            f.close()
            print("傳送md5.......")
            conn.send(m.hexdigest().encode("utf-8"))
server.close()
服務端:
# -*- coding:utf-8 -*-
#!/user/bin/env.python
#Author:Mr Wu

import socket,os,hashlib

client = socket.socket()
client.connect(("localhost",1999))
while True:
    cmd = input("輸入檔名[格式:get 檔名]>>>:").strip()
    if len(cmd) == 0:
        continue
    if cmd.startswith("get"):
        file_name = cmd.split()[1]
        client.send(cmd.encode("utf-8"))
        data = client.recv(1024)
        file_total_size = int(data.decode())
        client.send(b"OK")
        file_size = 0
        f = open(file_name,"wb")
        m = hashlib.md5()
        while file_size < file_total_size:
            last_size = file_total_size - file_size
            if last_size < 1024:
                size = last_size
            else:
                size = 1024
            '''避免粘包'''
            data = client.recv(size)
            m.update(data)
            f.write(data)
            file_size =+ len(data)
        f.close()
        md5 = m.hexdigest()
        received_md5 = client.recv(1024).decode()
        if md5 == received_md5:
            print("檔案md5一致,傳輸成功!")
        else:
            print("檔案傳輸錯誤!")
    else:
        print("輸入格式錯誤!")
        continue
執行結果:
server端:
開始傳送檔案.....
傳送md5.......
client端:
輸入檔名[格式:get 檔名]>>>:get test.py
檔案md5一致,傳輸成功!

2.socketServer的使用

注:由於其涉及執行緒的相關知識(還未學習),在學習完執行緒之後,再來寫socketServer的相關概念
2.1 使用方法:
(1)你必須自己建立一個處理類,並且這個類要繼承BaseRequestHandler,並且還要重寫父類裡的handle()
(2)你必須例項化TCPServer,並且傳遞server ip和你上面建立的請求處理類給這個TCPServer
(3)server.handle_request() #只處理一個請求
server.serve_forever() #處理多個連線請求,永遠執行
2.2 SocketServer程式示例:
# -*- coding:utf-8 -*-
#!/user/bin/env.python
#Author:Mr Wu

import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
    '''每一個請求過來都會例項化MyTCPHandler'''
    def handle(self):
        '''與客戶端所有的互動都是在handle裡完成的'''
        while True:
            try:
                self.data = self.request.recv(1024).strip()
                print("{} wrote:".format(self.client_address[0]))
                print(self.data)
                #if not self.data:
                #    print("客戶端斷開連線")
                #    break
                #just send the same data,but upper-cased
                self.request.send(self.data.upper())
            except ConnectionResetError as e:
                print(e)
                break
if __name__ == "__main__":
    HOST,PORT = "localhost",9999
    #create the server,binding to localhost on port 9999
    server = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler)
    '''
    activate the server:this will keep running until you
    interrupt the program with Ctrl-C
    '''
    server.serve_forever() #處理多個請求,即可以連線多個客戶端
未完待續。。。。。。。。