Python成長之路 socket網絡編程

Socket網絡編程

socket一般被稱做"套接字",應用程序經過"套接字"向網絡發出請求或者應答網絡請求,是主機或一臺計算機上的進程能夠通訊。python

socket起源於Unix,而Unix/Linux基本哲學之一就是「一切皆文件」,對於文件用【打開】【讀寫】【關閉】模式來操做。socket就是該模式的一個實現,服務器和客戶端各自維護一個"文件",在創建鏈接打開後,各自的"文件"能夠被對方讀取和能夠向本身的"文件"寫入內容 。通信結束時,關閉各自的"文件"。socket是實現TCP,UDP協議的接口,便於使用TCP、UDP。編程

socket簡單的例子服務器

服務器(server)網絡

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket

server = socket.socket()  # 聲明socket類型,同時建立socket實例
server.bind(('localhost', 8888))  # 爲socket實例綁定IP地址和端口號。

server.listen(5)  # 啓動監聽,等待客戶端的接入請求

while True:
    print('等待客戶端接入...')
    conn, addr = server.accept() # Accept阻塞,直到有客戶端鏈接進來
client_data = conn.recv(1024).decode('utf-8')  #  接收客戶端的信息
print('客戶端的消息:',client_data) 
conn.send('已鏈接'.encode('utf-8'))  # 向客戶端發送消息
server.close() # 關閉鏈接

 

客戶端 (client)併發

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket

client = socket.socket()  # 建立一個實例
client.connect(('localhost', 8888))  # 鏈接到服務器


client.send('請求鏈接'.encode())  # 發送信息給服務器

client_data = client.recv(1024).decode('utf-8')  # 接受服務器反饋的信息
print(client_data)
 client.close()  # 關閉鏈接

 

Socket對象經常使用的方法

服務器端的方法:socket

  • s.bind() #綁定IP地址和端口(host,port)到套接字,在AF_INET下以元組(host,port)的方式表示
  • s.listen() #開啓TCP監聽,backlog指定在拒絕鏈接以前,操做系統的最大掛起數量,至少1通常都爲5
  • s.accept() # 被動等待TCP客戶端鏈接,默認爲阻塞,等待有客戶端鏈接,程序纔會向下走

客戶端的方法:函數

  • s.connect() #主動初始化TCP服務器鏈接,通常address的格式化爲元組(host,port),若是鏈接錯誤,返回socket.error錯誤
  • s.connect() # connect()函數的擴展版本,出錯時犯會錯誤代碼,不會拋出異常

公用的方法:大數據

  • s.recv()  #接收TCP數據,數據以字符串形式返回,bufsize指定要接收的最大數據量。flag提供有關消息的其餘信息,一般能夠忽略
  • s.send() #發送TCP數據,將string中的數據發送到鏈接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。
  • s.sendall() #完整發送TCP數據,完整發送TCP數據。將string中的數據發送到鏈接的套接字,但在返回以前會嘗試發送全部數據。成功返回None,失敗則拋出異常。
  • s.recvfrom() #接收UDP數據,與recv()相似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址
  • s.sendto() #發送UDP數據,將數據發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。
  • s.close() #關閉套接字
  • s.getpeername() # 返回鏈接套接字的遠程地址。返回值一般是元組(ipaddr,port) 這個方法以及下面的方法我是沒用因此就不詳細說了,瞭解一下就好
  • s.getsockname() #返回套接字本身的地址。一般是一個元組(ipaddr,port)   
  • s.setsockopt(level,optname,value ) #設置給定套接字選項的值。  
  • s.getsockopt(level,optname,value ) # 返回套接字選項的值   
  • s.settimeout(timeout)  # 設置套接字操做的超時期,timeout是一個浮點數,單位是秒。值爲None表示沒有超時期。通常,超時期應該在剛建立套接字時設置,由於它們可能用於鏈接的操做(如connect())
  • s.gettimeout() # 返回當前超時期的值,單位是秒,若是沒有設置超時期,則返回None。
  • s.fileno() #返回套接字的文件描述符。
  • s.setblocking(flag) #若是flag爲0,則將套接字設爲非阻塞模式,不然將套接字設爲阻塞模式(默認值)。非阻塞模式下,若是調用recv()沒有發現任何數據,或send()調用沒法當即發送數據,那麼將引發socket.error異常。
  • s.makefile() #建立一個與該套接字相關連的文件

使用socket實現簡單的遠程輸入系統命令返回結果值spa

服務器操作系統

import socket
import os

server = socket.socket()
server.bind(('localhost', 8888))
server.listen()
print('等待客戶端接入')
while True:
    conn, addr = server.accept()
    print('new conn', addr)
    while True:
        data = conn.recv(1024).decode("utf-8")
        if not data:
            print("客戶端斷開")
            break
        print("執行指令:", data)
        cmd_res = os.popen(data).read().encode("utf-8")
        if len(cmd_res) == 0:
            cmd_res = '輸入的命令不合法'.encode('utf-8')
        conn.send(str(len(cmd_res)).encode('utf-8'))  # 向客戶端發送它要接受的數據的大小
        print(str(len(cmd_res)))
        check_res = conn.recv(1024)  # 主要是爲了解決粘包的問題
        conn.send(cmd_res)
server.close()

 

客戶端

#!/usr/bin/env python
#-*-coding:utf-8-*-

import socket

client = socket.socket()
client.connect(('localhost', 8888))
while True:
    cmd = input('>>>').strip().encode("utf-8")
    if cmd == 'exit'.encode('utf-8'):
        break
    if len(cmd) == 0:
        continue
    client.send(cmd)
    len_res = client.recv(1024).decode("utf-8")
    client.send(bytes('ok',encoding='utf8'))  # 主要是爲了在數據太多的時候發生粘包的狀況
    # print('返回數據的大小', len_res) 查看要接受多少數據
    received_num = 0
    received_data = ''
    while received_num != int(len_res):  # 主要是實現數據在一次返回中所有接收完畢
        recv_num = client.recv(1024)
        received_num += len(recv_num)
        received_data += str(recv_num)
    print(eval(received_data).decode('utf-8'))
    # cmd_res = client.recv(1024).decode("utf-8")
    # print(cmd_res)

client.close()

 

粘包;主要是爲了解決上面的check_res和下面的返回的數據粘在一塊兒,致使咱們的結果不正確。因此在發送數據大小後立馬實現接收客戶端對數據大小的確認,這樣就會是數據產生粘包。

在上面建立socket實例的時候(socket.socket())後面的括號有三個參數

第一個參數:地址簇

  • socket.AF_INET         表明的是使用IPV4(默認)
  • socket.AF_INET6            表明的是IPv6

第二個參數:類型

  • socket.SOCK_STREAM       使用的是TCP(默認)
  • socket.SOCK_DGRAM         使用的是UDP

第三個參數:協議

  • 0 (默認)與特定的地址家族相關的協議,若是是 0 ,則系統就會根據地址格式和套接類別,自動選擇一個合適的協議

 下面的列子是根據上面的例子實現遠程下載

FTPserver

#!/usr/bin/env python
#-*-coding:utf-8-*-

import socket
import os,subprocess
import hashlib

server = socket.socket()
server.bind(('localhost', 8888))
server.listen()
print('等待客戶端接入')
while True:
    conn, addr = server.accept()
    print('new conn', addr)
    while True:
        data = conn.recv(1024).decode("utf-8")
        if not data:
            print("客戶端{}斷開".format(addr))
            break
        cmd, filename = data.split()
        if os.path.isfile(filename):
            f = open(filename, 'rb')
            m = hashlib.md5()
            file_size = str(os.stat(filename).st_size).encode('utf-8')
            conn.send(file_size)
            ack = conn.recv(1024)  # 粘包
            for line in f:
                m.update(line)
                conn.send(line)
            f.close()
            conn.send(m.hexdigest().encode('utf-8'))
        # cmd_res = subprocess.getoutput(data).encode('utf-8')
        # cmd_len = str(len(cmd_res)).encode('utf-8')
        # conn.send(cmd_len)

        # ack = conn.recv(1024)  # 粘包
        # conn.send(cmd_res)
server.close()
client
#!/usr/bin/env python
#-*-coding:utf-8-*-

import socket, hashlib

client = socket.socket()
client.connect(('192.168.132.66', 8888))
while True:
    cmd = input('>>>').strip().encode("utf-8")
    if cmd == 'exit'.encode('utf-8'):
        break
    if len(cmd) == 0:
        continue
    if cmd.decode('utf-8').startswith('get'):
        client.send(cmd)
        file_size = int(client.recv(1024).decode('utf-8'))
        client.send(b'ok')
        filename = cmd.decode('utf-8').split()[1]
        f = open(filename + '.new','wb')
        m = hashlib.md5()
        received_data = 0
        while received_data != file_size:
            if file_size - received_data > 1024:
                size = 1024
            else:
                size = file_size - received_data
            data = client.recv(size)
            f.write(data)
            received_data += len(data)
            m.update(data)
        else:
            print('file recv done',received_data,file_size)
            received_md5 = client.recv(1024).decode('utf-8')
            tota_md5 = m.hexdigest()
            print('發送',received_md5)
            print("接收",tota_md5)
        f.close()
   

client.close()

以上的實例中,客戶端必須排隊與服務器進行通信,只有當正在通信的客戶端斷開鏈接纔可以到下一個客戶端通信,下面咱們將說道多個客戶端能夠同時和服務器通信,也就是客戶端併發。

多併發服務器

#!/usr/bin/env python
# -*-coding:utf-8-*-
import socketserver  # 實現併發的須要的模塊


class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
        while True:
            try:
                self.data = self.request.recv(1024).strip().decode("utf-8")
                # self.data 爲接收到的信息並轉換爲utf-8的格式
                print("{} wrote:".format(self.client_address[0]))
                # self.client_address[0]表示的是客戶端的IP地址與socket的addr是同樣的
                # self.client_address[1]表示的是客戶端的端口號與socket的port是同樣的
                print(self.data)
                if not self.data:
                    break
                # 上面的判斷是客戶端自動結束程序關閉鏈接通道的判斷
                self.request.send(self.data.upper().encode('utf-8'))
            except ConnectionResetError as e:
                print("erro", e)
                break
            # 上面的錯誤處理是直接斷開程序作的判斷捕獲的異常
if __name__ == "__main__":
    HOST, PORT = "localhost", 8888  # 服務器綁定的IP和端口號
    server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
    # 這個實現多併發的關鍵就在ThreadingTCPServer()的方法實現了能夠同時和多個會話
    # 傳入端口地址和咱們新建的繼承自socketserver模塊下的BaseRequestHandler類 實例化對象
    server.serve_forever()  # 經過調用對象的serve_forever()方法來激活服務端

 客戶端就用簡單的客戶端就好

#!/usr/bin/env python
# -*-coding:utf-8-*-
import socket
client = socket.socket()
client.connect(("localhost", 8888))

while True:
    msg = input(">>>").strip()
    if not msg:
        continue
    if msg == 'exit':
        break
    client.send(msg.encode("utf-8"))
    received = client.recv(1024)
    print(received.decode("utf-8"))

client.close()

 上面的就是一些基礎的socket的應用,能夠在上面的基礎上深刻的使用socket。這些是關於我對socket的簡單的初期認識。

相關文章
相關標籤/搜索