1. 程式人生 > >week7:網路程式設計之基礎

week7:網路程式設計之基礎

一、OSI模型

二、網路通訊要素

2.1、IP地址

2.2、埠號

2.3、傳輸協議

三、socket程式設計

socket即是一種特殊的檔案,一些socket函式就是對其進行的操作(讀/寫IO、開啟、關閉)。

Socket 是任何一種計算機網路通訊中最基礎的內容。例如當你在瀏覽器位址列中輸入 http://www.cnblogs.com/ 時,你會開啟一個套接字,然後連線到 http://www.cnblogs.com/ 並讀取響應的頁面然後顯示出來。而其他一些聊天客戶端如 gtalk 和 skype 也是類似。任何網路通訊都是通過 Socket 來完成的。Python 官方關於 Socket 的函式請看 

http://docs.python.org/library/socket.html

socket和file的區別:

  1、file模組是針對某個指定檔案進行【開啟】【讀寫】【關閉】

  2、socket模組是針對 伺服器端 和 客戶端Socket 進行【開啟】【讀寫】【關閉】

Python 提供了兩個基本的 socket 模組。

   第一個是 Socket,它提供了標準的 BSD Sockets API。

   第二個是 SocketServer, 它提供了伺服器中心類,可以簡化網路伺服器的開發。

3.1、Socket 型別

套接字格式:

socket(family,type[,protocal]) 使用給定的地址族、套接字型別、協議編號(預設為0)來建立套接字。

 

socket型別

描述

socket.AF_UNIX

只能夠用於單一的Unix系統程序間通訊

socket.AF_INET

伺服器之間網路通訊

socket.AF_INET6

IPv6

socket.SOCK_STREAM

流式socket , for TCP

socket.SOCK_DGRAM

資料報式socket , for UDP

socket.SOCK_RAW

原始套接字,普通的套接字無法處理ICMP、IGMP等網路報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由使用者構造IP頭。

socket.SOCK_SEQPACKET

可靠的連續資料包服務

建立TCP Socket:

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

建立UDP Socket:

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

3.2、Socket 函式

注意點:

1)TCP傳送資料時,已建立好TCP連線,所以不需要指定地址。UDP是面向無連線的,每次傳送要指定是發給誰。

2)服務端與客戶端不能直接傳送列表,元組,字典。需要字串化repr(data)。

 

socket函式

描述

服務端socket函式

s.bind(address)

將套接字繫結到地址, 在AF_INET下,以元組(host,port)的形式表示地址.

s.listen(backlog)

開始監聽TCP傳入連線。backlog指定在拒絕連線之前,作業系統可以掛起的最大連線數量。該值至少為1,大部分應用程式設為5就可以了。

s.accept()

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

客戶端socket函式

s.connect(address)

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

s.connect_ex(adddress)

功能與connect(address)相同,但是成功返回0,失敗返回errno的值。

公共socket函式

s.recv(bufsize[,flag])

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

s.send(string[,flag])

傳送TCP資料。將string中的資料傳送到連線的套接字。返回值是要傳送的位元組數量,該數量可能小於string的位元組大小。

s.sendall(string[,flag])

完整發送TCP資料。將string中的資料傳送到連線的套接字,但在返回之前會嘗試傳送所有資料。成功返回None,失敗則丟擲異常。

s.recvfrom(bufsize[.flag])

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

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

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

s.close()

關閉套接字。

s.getpeername()

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

s.getsockname()

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

s.setsockopt(level,optname,value)

設定給定套接字選項的值。

s.getsockopt(level,optname[.buflen])

返回套接字選項的值。

s.settimeout(timeout)

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

s.gettimeout()

返回當前超時期的值,單位是秒,如果沒有設定超時期,則返回None。

s.fileno()

返回套接字的檔案描述符。

s.setblocking(flag)

如果flag為0,則將套接字設為非阻塞模式,否則將套接字設為阻塞模式(預設值)。非阻塞模式下,如果呼叫recv()沒有發現任何資料,或send()呼叫無法立即傳送資料,那麼將引起socket.error異常。

s.makefile()

建立一個與該套接字相關連的檔案

 

四、socket程式設計思路

TCP服務端:

1 建立套接字,繫結套接字到本地IP與埠

   # socket.socket(socket.AF_INET,socket.SOCK_STREAM) , s.bind()

2 開始監聽連線                   #s.listen()

3 進入迴圈,不斷接受客戶端的連線請求              #s.accept()

4 然後接收傳來的資料,併發送給對方資料         #s.recv() , s.sendall()

5 傳輸完畢後,關閉套接字                     #s.close()

 

TCP客戶端:

1 建立套接字,連線遠端地址

       # socket.socket(socket.AF_INET,socket.SOCK_STREAM) , s.connect()

2 連線後傳送資料和接收資料          # s.sendall(), s.recv()

3 傳輸完畢後,關閉套接字          #s.close()

故事1:一次會話

######服務端##########
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')

conn, addr = sk.accept()
client_data = conn.recv(1024)
print(str(client_data, "utf8"))

inp = input('>>>')
conn.send(bytes(inp, 'utf8'))

sk.close()

 

#######客戶端##############
import socket

sk = socket.socket()
print(sk)


address = ('127.0.0.1', 8000)
sk.connect(address)

inp = input('>>>')
sk.send(bytes(inp, 'utf8'))

data = sk.recv(1024)
print(str(data, 'utf8'))

sk.close()

故事2:持久會話

##############服務端##############
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')

conn, addr = sk.accept()
while 1:
    client_data = conn.recv(1024)
    print(str(client_data, "utf8"))

    inp = input('>>>')
    conn.send(bytes(inp, 'utf8'))

sk.close()
#############客戶端##############
import socket

sk = socket.socket()
print(sk)


address = ('127.0.0.1', 8000)
sk.connect(address)

while True:
    inp = input('>>>')
    sk.send(bytes(inp, 'utf8'))

    data = sk.recv(1024)
    print(str(data, 'utf8'))

sk.close()

故事3:持久會話,然後再結束

##############服務端##############
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')

conn, addr = sk.accept()
while 1:
    client_data = conn.recv(1024)
    if not client_data: break
    print('.........', str(client_data, "utf8"))

    inp = input('>>>')
    conn.send(bytes(inp, 'utf8'))

sk.close()
import socket

sk = socket.socket()
print(sk)


address = ('127.0.0.1', 8000)
sk.connect(address)

while True:
    inp = input('>>>')
    if inp == 'exit':
        break
    sk.send(bytes(inp, 'utf8'))

    data = sk.recv(1024)
    print(str(data, 'utf8'))

sk.close()

故事4:持久會話,然後再結束,客戶端只發空,然後會出現什麼情況

##############服務端##############
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')

conn, addr = sk.accept()
while 1:
    client_data = conn.recv(1024)
    if not client_data: break
    print('.........', str(client_data, "utf8"))

    inp = input('>>>')
    conn.send(bytes(inp, 'utf8'))

sk.close()
import socket

sk = socket.socket()
print(sk)


address = ('127.0.0.1', 8000)
sk.connect(address)

while True:
    inp = input('>>>')
    if inp == 'exit':
        break
    sk.send(bytes(inp, 'utf8'))

    data = sk.recv(1024)
    print(str(data, 'utf8'))

sk.close()

這樣如果客戶端發的資料就是空資料的話豈不是也意外退出,那不就bug啦?

其實不用擔心,如果客戶端send了一個空資料後客戶端繼續向下執行,而server端的recv方法會繼續阻塞,直到接收到一個非空資料才會繼續向下執行。

 故事5:持久會話,然後再結束,服務多個客戶端

##############服務端##############
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')


while 1:
    conn, addr = sk.accept()
    while 1:
        client_data = conn.recv(1024)
        print('.........', str(client_data, "utf8"))
        if not client_data:break

        inp = input('>>>')
        conn.send(bytes(inp, 'utf8'))

sk.close()
#############客戶端##############
import socket

sk = socket.socket()
print(sk)


address = ('127.0.0.1', 8000)
sk.connect(address)

while True:
    inp = input('>>>')
    if inp == 'exit':
        break
    sk.send(bytes(inp, 'utf8'))

    data = sk.recv(1024)
    print(str(data, 'utf8'))

sk.close()

 另外一種方法:

##############服務端##############
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')

conn, addr = sk.accept()
while 1:
    client_data = conn.recv(1024)
    print('.........', str(client_data, "utf8"))
    if not client_data:
        conn,addr = sk.accept()
        continue


    inp = input('>>>')
    conn.send(bytes(inp, 'utf8'))

sk.close()

 

#############客戶端##############
import socket

sk = socket.socket()
print(sk)


address = ('127.0.0.1', 8000)
sk.connect(address)

while True:
    inp = input('>>>')
    if inp == 'exit':
        break
    sk.send(bytes(inp, 'utf8'))

    data = sk.recv(1024)
    print(str(data, 'utf8'))

sk.close()

 故事6:持久會話,然後再結束,服務多個客戶端,如果按住紅色stop_button按鈕的退出處理

##############服務端##############
import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')


while 1:
    conn, addr = sk.accept()
    print(addr)
    while 1:
        try:
            client_data = conn.recv(1024)
        except Exception:
            break
        print('.........', str(client_data, "utf8"))
        if not client_data:break

        inp = input('>>>')
        conn.send(bytes(inp, 'utf8'))

sk.close()

 

#############客戶端##############
import socket

sk = socket.socket()
print(sk)


address = ('127.0.0.1', 8000)
sk.connect(address)

while True:
    inp = input('>>>')
    if inp == 'exit':
        break
    sk.send(bytes(inp, 'utf8'))

    data = sk.recv(1024)
    print(str(data, 'utf8'))

sk.close()

 五、遠端執行

#############客戶端##############
import socket

sk = socket.socket()
print(sk)


address = ('127.0.0.1', 8000)
sk.connect(address)

while True:
    inp = input('>>>')
    if inp == 'exit':
        break
    sk.send(bytes(inp, 'utf8'))
    result_len = int(str(sk.recv(1024)), 'utf8')
    print(result_len)
    data = bytes()

    while len(data) != result_len:
        recv = sk.recv(1024)
        data += recv
    print(str(data, 'gbk'))

sk.close()
##############服務端##############
import subprocess

# subprocess.Popen()


import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')


while 1:
    conn, addr = sk.accept()
    print(addr)
    while 1:
        try:
            client_data = conn.recv(1024)
        except Exception:
            break
        print('.........', str(client_data, "utf8"))
        if not client_data:break

        obj = subprocess.Popen(str(client_data,'utf8'), shell=True, stdout=subprocess.PIPE)
        cmd_result = obj.stdout.read()
        result_len = bytes(str(len(cmd_result)), 'utf8')
        print('>>>>>>>', result_len)
        conn.sendall(result_len)

        conn.sendall(cmd_result)

sk.close()

conclusion:

              sendall會把資料直接全部發送到客戶端,客戶端將所有的資料都放到緩衝區,每次recv多少位元組取決於recv內的引數,理論不應該超過8k。

所以,並不能一次recv()無限大資料,所以這裡我們應該通過迴圈去接收。

六、解決大資料傳送和粘包問題

##############服務端##############
import subprocess

# subprocess.Popen()


import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')


while 1:
    conn, addr = sk.accept()
    print(addr)
    while 1:
        try:
            client_data = conn.recv(1024)
        except Exception:
            break
        print('.........', str(client_data, "utf8"))
        if not client_data:break

        obj = subprocess.Popen(str(client_data, 'utf8'), shell=True, stdout=subprocess.PIPE)
        cmd_result = obj.stdout.read()

        result_len = bytes(str(len(cmd_result)), 'utf8')
        print('>>>>>>>', result_len)
        conn.sendall(result_len) #粘包現象

        conn.recv(1024)#解決粘包
        # import time
        # time.sleep(1)

        conn.sendall(cmd_result)

sk.close()
#############客戶端##############
import socket

sk = socket.socket()
print(sk)


address = ('127.0.0.1', 8000)
sk.connect(address)

while True:
    inp = input('>>>')
    if inp == 'exit':
        break
    sk.send(bytes(inp, 'utf8'))
    result_len = int(str(sk.recv(1024)), 'utf8')
    sk.sendall('111')
    print(result_len)
    data = bytes()

    while len(data) != result_len:
        recv = sk.recv(1024)
        data += recv
    print(str(data, 'gbk'))

sk.close()

七、檔案傳送

post_serve

import subprocess
import os

# subprocess.Popen()


import socket
address = ('127.0.0.1', 8000)
sk = socket.socket()
sk.bind(address)
sk.listen(3)
print('server waiting...')
BASE_DIR = os.path.dirname(os.path.abspath(__file__))


while 1:
    conn, addr = sk.accept()
    # print(addr)
    while 1:
        data = conn.recv(1024)
        cmd, filename, filesize = str(data, 'utf8').split('|')
        path = os.path.join(BASE_DIR, 'ma', filename)
        filesize = int(filesize)

        f = open(path, 'ab')
        has_receive = 0
        while has_receive != filesize:
            data = conn.recv(1024)
            f.write(data)
            has_receive += len(data)
        f.close()

post_client

import socket
import os

sk = socket.socket()
print(sk)


address = ('127.0.0.1', 8000)
sk.connect(address)

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

while True:
    inp = input('>>>').strip()  # post\11.png

    cmd,path = inp.split('|')

    path = os.path.join(BASE_DIR, path)

    filename = os.path.basename(path)
    filesize = os.stat(path).st_size

    file_info = 'post|%s|%s' % (filename, filesize)

    sk.sendall(bytes(file_info, 'utf8'))

    f = open(path, 'rb')
    has_sent = 0
    while has_sent != filesize:
        data = f.read(1024)
        sk.sendall(data)
        has_sent += len(data)
    f.close()
    print('上傳成功')

八、serve端實現併發聊天

簡單例子

##############服務端##############
import socketserver

class MyServer(socketserver.BaseRequestHandler):

    def handle(self):
        print("服務端啟動...")
        while True:
            conn = self.request
            print(self.client_address)
            while True:
                client_data=conn.recv(1024)
                print(str(client_data,"utf8"))
                print("waiting...")
                conn.sendall(client_data)
            conn.close()

if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1', 8091), MyServer)
    server.serve_forever()


#############客戶端##############
import socket

ip_port = ('127.0.0.1', 8091)
sk = socket.socket()
sk.connect(ip_port)
print("客戶端啟動:")
while True:
    inp = input('>>>')
    sk.sendall(bytes(inp,"utf8"))
    if inp == 'exit':
        break
    server_response=sk.recv(1024)
    print(str(server_response,"utf8"))
sk.close()

聊天併發例項

import socketserver

class MyServer(socketserver.BaseRequestHandler):

    def handle(self):
        print("服務端啟動...")
        while True:
            conn = self.request
            print(self.client_address)
            while True:

                client_data=conn.recv(1024)

                print(str(client_data,"utf8"))
                print("waiting...")
                server_response=input(">>>")
                conn.sendall(bytes(server_response,"utf8"))
                # conn.sendall(client_data)

            conn.close()
            # print self.request,self.client_address,self.server


if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1', 8098), MyServer)
    server.serve_forever()


import socket


ip_port = ('127.0.0.1', 8098)
sk = socket.socket()
sk.connect(ip_port)
print ("客戶端啟動:")
while True:
    inp = input('>>>')
    sk.sendall(bytes(inp,"utf8"))
    server_response=sk.recv(1024)
    print (str(server_response,"utf8"))
    if inp == 'exit':
        break
sk.close()

九、socketserver模組

它包含了種五種server類,BaseServer(不直接對外服務)。TCPServer使用TCP協議,UDPServer使用UDP協議,還有兩個不常使用的,即UnixStreamServer和UnixDatagramServer,這兩個類僅僅在unix環境下有用(AF_unix)。

建立一個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併發起來, 必須選擇使用以下一個多併發的類

1

2

3

4

5

6

7

class socketserver.ForkingTCPServer

 

class socketserver.ForkingUDPServer

 

class socketserver.ThreadingTCPServer

 

class socketserver.ThreadingUDPServer