Python之路,Day8 - Socket網絡編程


本節內容

  1. Socket介紹
  2. Socket參數介紹
  3. 基本Socket實例
  4. Socket實現多連接處理
  5. 通過Socket實現簡單SSH
  6. 通過Socket實現文件傳送
  7. 作業:開發一個支持多用戶在線的FTP程序

 

 

1. Socket介紹

概念

network socket is an endpoint of a connection across a computer network. Today, most communication between computers is based on the Internet Protocol; therefore most network sockets are Internet sockets. More precisely, a socket is a handle (abstract reference) that a local program can pass to the networking application programming interface (API) to use the connection, for example "send this data on this socket".

For example, to send "Hello, world!" via TCP to port 80 of the host with address 1.2.3.4, one might get a socket, connect it to the remote host, send the string, then close the socket.

實現一個socket至少要分以下幾步,(偽代碼)

Socket socket = getSocket(type = "TCP")  #設定好協議類型
connect(socket, address = "1.2.3.4", port = "80") #連接遠程機器
send(socket, "Hello, world!") #發送消息
close(socket) #關閉連接

socket API is an application programming interface (API), usually provided by the operating system, that allows application programs to control and use network sockets. Internet socket APIs are usually based on the Berkeley sockets standard. In the Berkeley sockets standard, sockets are a form of file descriptor (a file handle), due to the Unix philosophy that "everything is a file", and the analogies between sockets and files: you can read, write, open, and close both.   

socket address is the combination of an IP address and a port number, much like one end of a telephone connection is the combination of a phone number and a particular extension. Sockets need not have an address (for example for only sending data), but if a program binds a socket to an address, the socket can be used to receive data sent to that address. Based on this address, internet sockets deliver incoming data packets to the appropriate application process or thread.

Socket Families(地址簇)

socket.AF_UNIX unix本機進程間通信 

socket.AF_INET IPV4 

socket.AF_INET6  IPV6

These constants represent the address (and protocol) families, used for the first argument to socket(). If the AF_UNIX constant is not defined then this protocol is unsupported. More constants may be available depending on the system.

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 #廢棄了

These constants represent the socket types, used for the second argument to socket(). More constants may be available depending on the system. (Only SOCK_STREAM and SOCK_DGRAM appear to be generally useful.)

 

2. Socket 參數介紹

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

Create a new socket using the given address family, socket type and protocol number. The address family should be AF_INET (the default), AF_INET6AF_UNIXAF_CAN or AF_RDS. The socket type should beSOCK_STREAM (the default), SOCK_DGRAMSOCK_RAW or perhaps one of the other SOCK_ constants. The protocol number is usually zero and may be omitted or in the case where the address family is AF_CAN the protocol should be one of CAN_RAW or CAN_BCM. If fileno is specified, the other arguments are ignored, causing the socket with the specified file descriptor to return. Unlike socket.fromfd()fileno will return the same socket and not a duplicate. This may help close a detached socket using socket.close().

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

Build a pair of connected socket objects using the given address family, socket type, and protocol number. Address family, socket type, and protocol number are as for the socket() function above. The default family is AF_UNIX if defined on the platform; otherwise, the default is AF_INET.

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

Connect to a TCP service listening on the Internet address (a 2-tuple (host, port)), and return the socket object. This is a higher-level function than socket.connect(): if host is a non-numeric hostname, it will try to resolve it for both AF_INET and AF_INET6, and then try to connect to all possible addresses in turn until a connection succeeds. This makes it easy to write clients that are compatible to both IPv4 and IPv6.

Passing the optional timeout parameter will set the timeout on the socket instance before attempting to connect. If no timeout is supplied, the global default timeout setting returned by getdefaulttimeout() is used.

If supplied, source_address must be a 2-tuple (host, port) for the socket to bind to as its source address before connecting. If host or port are ‘’ or 0 respectively the OS default behavior will be used.

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

sk.bind(address) 必會

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

sk.listen(backlog) 必會

  開始監聽傳入連接。backlog指定在拒絕連接之前,可以掛起的最大連接數量。

      backlog等於5,表示內核已經接到了連接請求,但服務器還沒有調用accept進行處理的連接個數最大為5
      這個值不能無限大,因為要在內核中維護連接隊列

sk.setblocking(bool) 必會

  是否阻塞(默認True),如果設置False,那么accept和recv時一旦無數據,則報錯。

sk.accept() 必會

  接受連接並返回(conn,address),其中conn是新的套接字對象,可以用來接收和發送數據。address是連接客戶端的地址。

  接收TCP 客戶的連接(阻塞式)等待連接的到來

sk.connect(address) 必會

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

sk.connect_ex(address)

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

sk.close() 必會

  關閉套接字

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

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

sk.recvfrom(bufsize[.flag])

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

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

  將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容全部發送。

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

  將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。

      內部通過遞歸調用send,將所有內容發送出去。

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

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

sk.settimeout(timeout) 必會

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

sk.getpeername()  必會

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

sk.getsockname() 

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

sk.fileno() 

  套接字的文件描述符

socket.sendfile(fileoffset=0count=None)

     發送文件 ,但目前多數情況下並無什么卵用。

 

3. 基本Socket實例

前面講了這么多,到底咋么用呢?

 1 import socket
 2 
 3 server = socket.socket() #獲得socket實例
 4 
 5 server.bind(("localhost",9998)) #綁定ip port
 6 server.listen()  #開始監聽
 7 print("等待客戶端的連接...")
 8 conn,addr = server.accept() #接受並建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來...
 9 print("新連接:",addr )
10 
11 data = conn.recv(1024)
12 print("收到消息:",data)
13 
14 
15 server.close()
SocketServer.py
1 import socket
2 
3 client = socket.socket()
4 
5 client.connect(("localhost",9998))
6 
7 client.send(b"hey")
8 
9 client.close()
SocketClient.py

上面的代碼的有一個問題, 就是SocketServer.py運行起來后, 接收了一次客戶端的data就退出了。。。, 但實際場景中,一個連接建立起來后,可能要進行多次往返的通信。

 

多次的數據交互怎么實現呢?

 1 import socket
 2 
 3 server = socket.socket() #獲得socket實例
 4 
 5 server.bind(("localhost",9998)) #綁定ip port
 6 server.listen()  #開始監聽
 7 print("等待客戶端的連接...")
 8 conn,addr = server.accept() #接受並建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來...
 9 print("新連接:",addr )
10 while True:
11 
12     data = conn.recv(1024)
13 
14     print("收到消息:",data)
15     conn.send(data.upper())
16 
17 server.close()
socketserver 支持多次交互
 1 import socket
 2 
 3 client = socket.socket()
 4 
 5 client.connect(("localhost",9998))
 6 
 7 while True:
 8     msg = input(">>:").strip()
 9     if len(msg) == 0:continue
10     client.send( msg.encode("utf-8") )
11 
12     data = client.recv(1024)
13     print("來自服務器:",data)
14 
15 client.close()
socket客戶端支持多交互

實現了多次交互, 棒棒的, 但你會發現一個小問題, 就是客戶端一斷開,服務器端就進入了死循環,為啥呢?

看客戶端斷開時服務器端的輸出

等待客戶端的連接...
新連接: ('127.0.0.1', 62722)
收到消息: b'hey'
收到消息: b'you'
收到消息: b''  #客戶端一斷開,服務器端就收不到數據了,但是不會報錯,就進入了死循環模式。。。
收到消息: b''
收到消息: b''
收到消息: b''
收到消息: b''

知道了原因就好解決了,只需要加個判斷服務器接到的數據是否為空就好了,為空就代表斷了。。。

 1 import socket
 2 
 3 server = socket.socket() #獲得socket實例
 4 
 5 server.bind(("localhost",9998)) #綁定ip port
 6 server.listen()  #開始監聽
 7 print("等待客戶端的連接...")
 8 conn,addr = server.accept() #接受並建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來...
 9 print("新連接:",addr )
10 while True:
11 
12     data = conn.recv(1024)
13     if not data:
14         print("客戶端斷開了...")
15         break
16     print("收到消息:",data)
17     conn.send(data.upper())
18 
19 server.close()
加了判斷客戶端是否斷開的代碼

 

4.Socket實現多連接處理

上面的代碼雖然實現了服務端與客戶端的多次交互,但是你會發現,如果客戶端斷開了, 服務器端也會跟着立刻斷開,因為服務器只有一個while 循環,客戶端一斷開,服務端收不到數據 ,就會直接break跳出循環,然后程序就退出了,這顯然不是我們想要的結果 ,我們想要的是,客戶端如果斷開了,我們這個服務端還可以為下一個客戶端服務,它不能斷,她接完一個客,擦完嘴角的遺留物,就要接下來勇敢的去接待下一個客人。 在這里如何實現呢?

conn,addr = server.accept() #接受並建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來...

我們知道上面這句話負責等待並接收新連接,對於上面那個程序,其實在while break之后,只要讓程序再次回到上面這句代碼這,就可以讓服務端繼續接下一個客戶啦。 

import socket

server = socket.socket() #獲得socket實例

server.bind(("localhost",9998)) #綁定ip port
server.listen()  #開始監聽

while True: #第一層loop
    print("等待客戶端的連接...")
    conn,addr = server.accept() #接受並建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來...
    print("新連接:",addr )
    while True:

        data = conn.recv(1024)
        if not data:
            print("客戶端斷開了...")
            break #這里斷開就會再次回到第一次外層的loop
        print("收到消息:",data)
        conn.send(data.upper())

server.close()

注意了, 此時服務器端依然只能同時為一個客戶服務,其客戶來了,得排隊(連接掛起),不能玩 three some. 這時你說想,我就想玩3p,就想就想嘛,其實也可以,多交錢嘛,繼續往下看,后面開啟新姿勢后就可以玩啦。。。

 

5.通過socket實現簡單的ssh

光只是簡單的發消息、收消息沒意思,干點正事,可以做一個極簡版的ssh,就是客戶端連接上服務器后,讓服務器執行命令,並返回結果給客戶端。

import socket
import os


server = socket.socket() #獲得socket實例
#server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

server.bind(("localhost",9998)) #綁定ip port
server.listen()  #開始監聽

while True: #第一層loop
    print("等待客戶端的連接...")
    conn,addr = server.accept() #接受並建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來...
    print("新連接:",addr )
    while True:

        data = conn.recv(1024)
        if not data:
            print("客戶端斷開了...")
            break #這里斷開就會再次回到第一次外層的loop
        print("收到命令:",data)
        res = os.popen(data.decode()).read() #py3 里socket發送的只有bytes,os.popen又只能接受str,所以要decode一下
        print(len(res))
        conn.send(res.encode("utf-8"))

server.close()
socket ssh server
import socket

client = socket.socket()

client.connect(("localhost",9998))

while True:
    msg = input(">>:").strip()
    if len(msg) == 0:continue
    client.send( msg.encode("utf-8") )

    data = client.recv(1024)
    print(data.decode()) #命令執行結果

client.close()
socket ssh client

very cool , 這樣我們就做了一個簡單的ssh , 但多試幾條命令你就會發現,上面的程序有以下2個問題。 

  1. 不能執行top等類似的 會持續輸出的命令,這是因為,服務器端在收到客戶端指令后,會一次性通過os.popen執行,並得到結果后返回給客戶,但top這樣的命令用os.popen執行你會發現永遠都不會結束,所以客戶端也永遠拿不到返回。(真正的ssh是通過select 異步等模塊實現的,我們以后會涉及)
  2. 不能執行像cd這種沒有返回的指令, 因為客戶端每發送一條指令,就會通過client.recv(1024)等待接收服務器端的返回結果,但是cd命令沒有結果 ,服務器端調用conn.send(data)時是不會發送數據給客戶端的。 所以客戶端就會一直等着,等到天荒地老,結果就卡死了。解決的辦法是,在服務器端判斷命令的執行返回結果的長度,如果結果為空,就自己加個結果返回給客戶端,如寫上"cmd exec success, has no output."
  3. 如果執行的命令返回結果的數據量比較大,會發現,結果返回不全,在客戶端上再執行一條命令,結果返回的還是上一條命令的后半段的執行結果,這是為什么呢?這是因為,我們的客戶寫client.recv(1024), 即客戶端一次最多只接收1024個字節,如果服務器端返回的數據是2000字節,那有至少9百多字節是客戶端第一次接收不了的,那怎么辦呢,服務器端此時不能把數據直接扔了呀,so它會暫時存在服務器的io發送緩沖區里,等客戶端下次再接收數據的時候再發送給客戶端。 這就是為什么客戶端執行第2條命令時,卻接收到了第一條命令的結果的原因。 這時有同學說了, 那我直接在客戶端把client.recv(1024)改大一點不就好了么, 改成一次接收個100mb,哈哈,這是不行的,因為socket每次接收和發送都有最大數據量限制的,畢竟網絡帶寬也是有限的呀,不能一次發太多,發送的數據最大量的限制 就是緩沖區能緩存的數據的最大量,這個緩沖區的最大值在不同的系統上是不一樣的, 我實在查不到一個具體的數字,但測試的結果是,在linux上最大一次可接收10mb左右的數據,不過官方的建議是不超過8k,也就是8192,並且數據要可以被2整除,不要問為什么 。anyway , 如果一次只能接收最多不超過8192的數據 ,那服務端返回的數據超過了這個數字怎么辦呢?比如讓服務器端打開一個5mb的文件並返回,客戶端怎么才能完整的接受到呢?那就只能循環收取啦。 

 

在開始解決上面問題3之前,我們要考慮,客戶端要循環接收服務器端的大量數據返回直到一條命令的結果全部返回為止, 但問題是客戶端知道服務器端返回的數據有多大么?答案是不知道,那既然不知道服務器的要返回多大的數據,那客戶端怎么知道要循環接收多少次呢?答案是不知道,擦,那咋辦? 總不能靠猜吧?呵呵。。。 當然不能,那只能讓服務器在發送數據之前主動告訴客戶端,要發送多少數據給客戶端,然后再開始發送數據,yes, 機智如我,搞起。

先簡單測試接收數據量大小

import socket
import os,subprocess


server = socket.socket() #獲得socket實例
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

server.bind(("localhost",9998)) #綁定ip port
server.listen()  #開始監聽

while True: #第一層loop
    print("等待客戶端的連接...")
    conn,addr = server.accept() #接受並建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來...
    print("新連接:",addr )
    while True:

        data = conn.recv(1024)
        if not data:
            print("客戶端斷開了...")
            break #這里斷開就會再次回到第一次外層的loop
        print("收到命令:",data)
        #res = os.popen(data.decode()).read() #py3 里socket發送的只有bytes,os.popen又只能接受str,所以要decode一下
        res = subprocess.Popen(data,shell=True,stdout=subprocess.PIPE).stdout.read() #跟上面那條命令的效果是一樣的
        if len(res) == 0:
            res = "cmd exec success,has not output!"
        conn.send(str(len(res)).endcode("utf-8")) #發送數據之前,先告訴客戶端要發多少數據給它
        conn.sendall(res.encode("utf-8")) #發送端也有最大數據量限制,所以這里用sendall,相當於重復循環調用conn.send,直至數據發送完畢

server.close()
ssh server 返回執行結果大小
import socket

client = socket.socket()

client.connect(("localhost",9998))

while True:
    msg = input(">>:").strip()
    if len(msg) == 0:continue
    client.send( msg.encode("utf-8") )

    res_return_size  = client.recv(1024) #接收這條命令執行結果的大小
    print("getting cmd result , ", res_return_size)
    total_rece_size = int(res_return_size)
    print(total_rece_size)

    #print(data.decode()) #命令執行結果

client.close()
ssh client 接收執行結果的大小
結果輸出:
/Library/Frameworks/Python.framework/Versions/3.5/bin/python3.5 /Users/jieli/PycharmProjects/python基礎/自動化day8socket/sock_client.py >>:cat /var/log/system.log getting cmd result , b'3472816Sep 9 09:06:37 Jies-MacBook-Air kernel[0]: hibernate image path: /var/vm/sleepimage\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: efi pagecount 65\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: hibernate_page_list_setall(preflight 1) start\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: hibernate_page_list_setall time: 211 ms\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: pages 1211271, wire 225934, act 399265, inact 4, cleaned 0 spec 97, zf 3925, throt 0, compr 218191, xpmapped 40000\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: could discard act 94063 inact 129292 purgeable 58712 spec 81788 cleaned 0\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: WARNING: hibernate_page_list_setall skipped 47782 xpmapped pages\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: hibernate_page_list_setall preflight pageCount 225934 est comp 41 setfile 421527552 min 1073741824\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: kern_open_file_for_direct_io(0)\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: kern_open_file_for_direct_io took 181 ms\nSep 9 ' Traceback (most recent call last): File "/Users/jieli/PycharmProjects/python基礎/自動化day8socket/sock_client.py", line 17, in <module> total_rece_size = int(res_return_size) ValueError: invalid literal for int() with base 10: b'3472816Sep 9 09:06:37 Jies-MacBook-Air kernel[0]: hibernate image path: /var/vm/sleepimage\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: efi pagecount 65\nSep 9 09:06:37 Jies-MacBook-Air kernel[0]: Process finished with exit code 1

看程序執行報錯了, 我在客戶端本想只接服務器端命令的執行結果,但實際上卻連命令結果也跟着接收了一部分。 這是為什么呢???服務器不是只send了結果的大小么?不應該只是個數字么?尼瑪命令結果不是第2次send的時候才發送的么??,擦,擦,擦,價值觀都要崩潰了啊。。。。

哈哈,這里就引入了一個重要的概念,“粘包”, 即服務器端你調用時send 2次,但你send調用時,數據其實並沒有立刻被發送給客戶端,而是放到了系統的socket發送緩沖區里,等緩沖區滿了、或者數據等待超時了,數據才會被send到客戶端,這樣就把好幾次的小數據拼成一個大數據,統一發送到客戶端了,這么做的目地是為了提高io利用效率,一次性發送總比連發好幾次效率高嘛。 但也帶來一個問題,就是“粘包”,即2次或多次的數據粘在了一起統一發送了。就是我們上面看到的情況 。 

我們在這里必須要想辦法把粘包分開, 因為不分開,你就沒辦法取出來服務器端返回的命令執行結果的大小呀。so ,那怎么分開呢?首先你是沒辦法讓緩沖區強制刷新把數據發給客戶端的。 你能做的,只有一個。就是,讓緩沖區超時,超時了,系統就不會等緩沖區滿了,會直接把數據發走,因為不能一個勁的等后面的數據呀,等太久,會造成數據延遲了,那可是極不好的。so如果讓緩沖區超時呢?

答案就是:

  1. time.sleep(0.5),經多次測試,讓服務器程序sleep 至少0.5就會造成緩沖區超時。哈哈哈, 你會說,擦,這么玩不會被老板開除么,雖然我們覺得0.5s不多,但是對數據實時要求高的業務場景,比如股票交易,過了0.5s 股票價格可以就漲跌很多,搞毛線呀。但沒辦法,我剛學socket的時候 找不到更好的辦法,就是這么玩的,現在想想也真是low呀
  2. 但現在我是有Tesla的男人了,不能再這么low了, 所以推出nb新姿勢就是, 不用sleep,服務器端每發送一個數據給客戶端,就立刻等待客戶端進行回應,即調用 conn.recv(1024), 由於recv在接收不到數據時是阻塞的,這樣就會造成,服務器端接收不到客戶端的響應,就不會執行后面的conn.sendall(命令結果)的指令,收到客戶端響應后,再發送命令結果時,緩沖區就已經被清空了,因為上一次的數據已經被強制發到客戶端了。 好機智 , 看下面代碼實現。
#_*_coding:utf-8_*_
__author__ = 'Alex Li'


import socket
import os,subprocess


server = socket.socket() #獲得socket實例
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

server.bind(("localhost",9999)) #綁定ip port
server.listen()  #開始監聽

while True: #第一層loop
    print("等待客戶端的連接...")
    conn,addr = server.accept() #接受並建立與客戶端的連接,程序在此處開始阻塞,只到有客戶端連接進來...
    print("新連接:",addr )
    while True:

        data = conn.recv(1024)
        if not data:
            print("客戶端斷開了...")
            break #這里斷開就會再次回到第一次外層的loop
        print("收到命令:",data)
        #res = os.popen(data.decode()).read() #py3 里socket發送的只有bytes,os.popen又只能接受str,所以要decode一下
        res = subprocess.Popen(data,shell=True,stdout=subprocess.PIPE).stdout.read() #跟上面那條命令的效果是一樣的
        if len(res) == 0:
            res = "cmd exec success,has not output!".encode("utf-8")
        conn.send(str(len(res)).encode("utf-8")) #發送數據之前,先告訴客戶端要發多少數據給它
        print("等待客戶ack應答...")
        client_final_ack = conn.recv(1024) #等待客戶端響應
        print("客戶應答:",client_final_ack.decode())
        print(type(res))
        conn.sendall(res) #發送端也有最大數據量限制,所以這里用sendall,相當於重復循環調用conn.send,直至數據發送完畢

server.close()
接收大數據 server端
#_*_coding:utf-8_*_
__author__ = 'Alex Li'

import socket
import sys

client = socket.socket()

client.connect(("localhost",9999))

while True:
    msg = input(">>:").strip()
    if len(msg) == 0:continue
    client.send( msg.encode("utf-8") )

    res_return_size  = client.recv(1024) #接收這條命令執行結果的大小
    print("getting cmd result , ", res_return_size)
    total_rece_size = int(res_return_size)
    print("total size:",res_return_size)
    client.send("准備好接收了,發吧loser".encode("utf-8"))
    received_size = 0 #已接收到的數據
    cmd_res = b''
    f = open("test_copy.html","wb")#把接收到的結果存下來,一會看看收到的數據 對不對
    while received_size != total_rece_size: #代表還沒收完
        data = client.recv(1024)
        received_size += len(data) #為什么不是直接1024,還判斷len干嘛,注意,實際收到的data有可能比1024少
        cmd_res += data
    else:
        print("數據收完了",received_size)
        #print(cmd_res.decode())
        f.write(cmd_res) #把接收到的結果存下來,一會看看收到的數據 對不對
    #print(data.decode()) #命令執行結果

client.close()
接收大數據客戶端

 

 

6. SocketServer

The socketserver module simplifies the task of writing network servers.

socketserver一共有這么幾種類型

class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True) 

This uses the Internet TCP protocol, which provides for continuous streams of data between the client and server. 

class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)

This uses datagrams, which are discrete packets of information that may arrive out of order or be lost while in transit. The parameters are the same as for TCPServer.

class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)
class socketserver.UnixDatagramServer(server_address, RequestHandlerClass,bind_and_activate=True)

These more infrequently used classes are similar to the TCP and UDP classes, but use Unix domain sockets; they’re not available on non-Unix platforms. The parameters are the same as for TCPServer.

There are five classes in an inheritance diagram, four of which represent synchronous servers of four types:

+------------+ | BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+


創建一個socketserver 至少分以下幾步:

  1. First, you must create a request handler class by subclassing the BaseRequestHandlerclass and overriding its handle() method; this method will process incoming requests.   
  2. Second, you must instantiate one of the server classes, passing it the server’s address and the request handler class.
  3. Then call the handle_request() orserve_forever() method of the server object to process one or many requests.
  4. Finally, call server_close() to close the socket.

基本的socketserver代碼

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    The request handler class for our server.

    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """

    def handle(self):
        # self.request is the TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    # Create the server, binding to localhost on port 9999
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)

    # Activate the server; this will keep running until you
    # interrupt the program with Ctrl-C
    server.serve_forever()
View Code

但你發現,上面的代碼,依然不能同時處理多個連接,擦,那我搞這個干嘛?別急,不是不能處理多並發,如果你想,你還要啟用多線程,多線程我們現在還沒講,但你大體知道,有了多線程,就能同時讓cpu干多件事了就行先。

 

讓你的socketserver並發起來, 必須選擇使用以下一個多並發的類

class socketserver.ForkingTCPServer

class socketserver.ForkingUDPServer

class socketserver.ThreadingTCPServer

class socketserver.ThreadingUDPServer

 

so 只需要把下面這句

    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)

換成下面這個,就可以多並發了,這樣,客戶端每連進一個來,服務器端就會分配一個新的線程來處理這個客戶端的請求

    server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)

 

class socketserver.BaseServer(server_addressRequestHandlerClass) 主要有以下方法

class socketserver.BaseServer(server_address, RequestHandlerClass)
This is the superclass of all Server objects in the module. It defines the interface, given below, but does not implement most of the methods, which is done in subclasses. The two parameters are stored in the respective server_address and RequestHandlerClass attributes.

fileno()
Return an integer file descriptor for the socket on which the server is listening. This function is most commonly passed to selectors, to allow monitoring multiple servers in the same process.

handle_request()
Process a single request. This function calls the following methods in order: get_request(), verify_request(), and process_request(). If the user-provided handle() method of the handler class raises an exception, the server’s handle_error() method will be called. If no request is received within timeout seconds, handle_timeout() will be called and handle_request() will return.

serve_forever(poll_interval=0.5)
Handle requests until an explicit shutdown() request. Poll for shutdown every poll_interval seconds. Ignores the timeout attribute. It also calls service_actions(), which may be used by a subclass or mixin to provide actions specific to a given service. For example, the ForkingMixIn class uses service_actions() to clean up zombie child processes.

Changed in version 3.3: Added service_actions call to the serve_forever method.

service_actions()
This is called in the serve_forever() loop. This method can be overridden by subclasses or mixin classes to perform actions specific to a given service, such as cleanup actions.

New in version 3.3.

shutdown()
Tell the serve_forever() loop to stop and wait until it does.

server_close()
Clean up the server. May be overridden.

address_family
The family of protocols to which the server’s socket belongs. Common examples are socket.AF_INET and socket.AF_UNIX.

RequestHandlerClass
The user-provided request handler class; an instance of this class is created for each request.

server_address
The address on which the server is listening. The format of addresses varies depending on the protocol family; see the documentation for the socket module for details. For Internet protocols, this is a tuple containing a string giving the address, and an integer port number: ('127.0.0.1', 80), for example.

socket
The socket object on which the server will listen for incoming requests.

The server classes support the following class variables:

allow_reuse_address
Whether the server will allow the reuse of an address. This defaults to False, and can be set in subclasses to change the policy.

request_queue_size
The size of the request queue. If it takes a long time to process a single request, any requests that arrive while the server is busy are placed into a queue, up to request_queue_size requests. Once the queue is full, further requests from clients will get a “Connection denied” error. The default value is usually 5, but this can be overridden by subclasses.

socket_type
The type of socket used by the server; socket.SOCK_STREAM and socket.SOCK_DGRAM are two common values.

timeout
Timeout duration, measured in seconds, or None if no timeout is desired. If handle_request() receives no incoming requests within the timeout period, the handle_timeout() method is called.

There are various server methods that can be overridden by subclasses of base server classes like TCPServer; these methods aren’t useful to external users of the server object.

finish_request()
Actually processes the request by instantiating RequestHandlerClass and calling its handle() method.

get_request()
Must accept a request from the socket, and return a 2-tuple containing the new socket object to be used to communicate with the client, and the client’s address.

handle_error(request, client_address)
This function is called if the handle() method of a RequestHandlerClass instance raises an exception. The default action is to print the traceback to standard output and continue handling further requests.

handle_timeout()
This function is called when the timeout attribute has been set to a value other than None and the timeout period has passed with no requests being received. The default action for forking servers is to collect the status of any child processes that have exited, while in threading servers this method does nothing.

process_request(request, client_address)
Calls finish_request() to create an instance of the RequestHandlerClass. If desired, this function can create a new process or thread to handle the request; the ForkingMixIn and ThreadingMixIn classes do this.

server_activate()
Called by the server’s constructor to activate the server. The default behavior for a TCP server just invokes listen() on the server’s socket. May be overridden.

server_bind()
Called by the server’s constructor to bind the socket to the desired address. May be overridden.

verify_request(request, client_address)
Must return a Boolean value; if the value is True, the request will be processed, and if it’s False, the request will be denied. This function can be overridden to implement access controls for a server. The default implementation always returns True.
View Code

 

 

作業:開發一個支持多用戶在線的FTP程序

要求:

  1. 用戶加密認證
  2. 允許同時多用戶登錄
  3. 每個用戶有自己的家目錄 ,且只能訪問自己的家目錄
  4. 對用戶進行磁盤配額,每個用戶的可用空間不同
  5. 允許用戶在ftp server上隨意切換目錄
  6. 允許用戶查看當前目錄下文件
  7. 允許上傳和下載文件,保證文件一致性
  8. 文件傳輸過程中顯示進度條
  9. 附加功能:支持文件的斷點續傳

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM