1. 程式人生 > >網絡編程基礎

網絡編程基礎

recvfrom 獨立 ror 可能 cep cmd 本機 tcpip 上一個

TCP/IP五層模型講解

物理層

物理層功能:主要是基於電器特性發送高低電壓(電信號),高電壓對應數字1,低電壓對應數字0

數據鏈路層

數據鏈路層的功能:定義了電信號的分組方式

以太網協議

早期的時候各個公司都有自己的分組方式,後來形成了統一的標準,即以太網協議ethernet

ethernet規定:

一組電信號構成一個數據包,叫做‘幀’

每一數據幀分成:報頭head和數據data兩部分
head包含:(固定18個字節)

發送者/源地址,6個字節
接收者/目標地址,6個字節
數據類型,6個字節
data包含:(最短46字節,最長1500字節)

數據包的具體內容
head長度+data長度=最短64字節,最長1518字節,超過最大限制就分片發送

mac地址

每塊網卡出廠時都被燒制上一個世界唯一的mac地址,長度為48位2進制,通常由12位16進制數表示(前六位是廠商編號,後六位是流水線號)

廣播

有了mac地址,同一網絡內的兩臺主機就可以通信了(一臺主機通過arp協議獲取另外一臺主機的mac地址)

ethernet采用最原始的方式,廣播的方式進行通信,即計算機通信基本靠吼

網絡層

網絡層功能:引入一套新的地址用來區分不同的廣播域/子網,這套地址即網絡地址

ARP協議

作用: ARP協議其主要用作將IP地址翻譯為以太網的MAC地址

傳輸層

傳輸層功能:建立端口到端口的通信

技術分享圖片

TCP協議(傳輸控制協議)

tcp協議是面向鏈接的,面向流的,提供可靠性服務的,服務端和客戶端都要有成對的socket存在,必須先啟動服務端。

粘包

粘包是TCP協議是獨有的,發送端將間隔時間小而且數據量小的數據,合並到一起發送給接收端。即面向流的通信是無消息保護邊界的。

三次握手,四次揮手

技術分享圖片

3次握手

1、客戶端發送請求報文(SYN=1,並選擇一個seq=x)

2、服務端接收該報文,發送確認報文(SYN=1,ACK=1,並選擇一個seq = y,)

3、客戶端收到服務器的同步確認後,對服務器發送確認的確認(將ACK=1,確認號為y+1,而報文首部的序號為x+1,)

4次揮手

假設客戶端和服務端處於連接狀態,客戶端主動斷開連接

1、客戶端向服務端發送FIN報文:(FIN=1,序號seq=上一個最後傳輸的字節序號+1=u,發送後,客戶端進入FIN-WAIT-1狀態。)

2、服務端接收這個報文,發送一個確認報文:(令ACK=1,確認序號ack = u+1,自己的報文序號seq=v,發送後,服務器進入CLOSE-WAIT狀態。)

此時TCP連接進入連接半關閉狀態,服務器可能還會向客戶端發送一些數據。

3、如果服務端已經沒有要發送的數據,則發送釋放信號的報文

4、客戶端收到報文,發出確認報文,進入close狀態

服務端收到客戶端發來的確認報文後,也進入close狀態。

UDP(用戶數據報協議)

UDP協議是無連接的,面向消息的,由於UDP支持的是一對多的模式,所以UDP協議不存在粘包現象,即面向消息的通信是有消息保護邊界的。

總結

TCP協議雖然安全性很高,但是網絡開銷大,而UDP協議雖然沒有提供安全機制,但是網絡開銷小。

socket

Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來說,一組簡單的接口就是全部。

family(socket家族)

socket.AF_UNIX:用於本機進程間通訊,為了保證程序安全,兩個獨立的程序(進程)間是不能互相訪問彼此的內存的,但為了實現進程間的通訊,可以通過創建一個本地的socket來完成

socket.AF_INET:(還有AF_INET6被用於ipv6,還有一些其他的地址家族,不過,他們要麽是只用於某個平臺,要麽就是已經被廢棄,或者是很少被使用,或者是根本沒有實現,所有地址家族中,AF_INET是使用最廣泛的一個,python支持很多種地址家族,但是由於我們只關心網絡編程,所以大部分時候我麽只使用AF_INET)

import socket
socket.socket(socket_family,socket_type,protocal=0)
socket_family  # 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默認值為 0。

#獲取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

#獲取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

服務端套接字

s.bind()  # 綁定(主機,端口號)到套接字
s.listen()  # 開始TCP監聽
s.accept()  # 被動接受TCP客戶的連接,(阻塞式)等待連接的到來

客戶端套接字

s.connect() # 主動初始化TCP服務器連接
s.connect_ex()  # connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常

公共用途的套接字函數

s.recv()            接收TCP數據
s.send()            發送TCP數據(send在待發送數據量大於己端緩存區剩余空間時,數據丟失,不會發完)
s.sendall()         發送完整的TCP數據(本質就是循環調用send,sendall在待發送數據量大於己端緩存區剩余空間時,數據不丟失,循環調用send直到發完)
s.recvfrom()        接收UDP數據
s.sendto()          發送UDP數據
s.getpeername()     連接到當前套接字的遠端的地址
s.getsockname()     當前套接字的地址
s.getsockopt()      返回指定套接字的參數
s.setsockopt()      設置指定套接字的參數
s.close()           關閉套接字

面向鎖的套接字方法

s.setblocking()     設置套接字的阻塞與非阻塞模式
s.settimeout()      設置阻塞套接字操作的超時時間
s.gettimeout()      得到阻塞套接字操作的超時時間

面向文件的套接字的函數

s.fileno()          套接字的文件描述符
s.makefile()        創建一個與該套接字相關的文件

地址重用

server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

簡單版FTP(實現遠程執行系統命令)

服務端

import socket
import subprocess

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind((‘127.0.0.1‘, 9901)) #0-65535:0-1024給操作系統使用
phone.listen(5)

print(‘starting...‘)
while True:  # 鏈接循環
    conn, client_addr = phone.accept()
    print(client_addr)

    while True:  # 通信循環
        try:
            # 1、收命令
            cmd = conn.recv(1024)
            if not cmd:
                break  # 適用於linux操作系統

            # 、執行命令,拿到結果
            obj = subprocess.Popen(cmd.decode(‘utf-8‘), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)

            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            print("stdout:%s, stderr:%s" % (stdout, stderr))
            # 3、把命令的結果返回給客戶端
            print(len(stdout)+len(stderr))
            conn.send(stdout+stderr)  # +是一個可以優化的點

        except ConnectionResetError:  # 適用於windows操作系統
            break
    conn.close()

phone.close()

客戶端

from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect((‘127.0.0.1‘,8080))

while True:
    msg=input(‘>>: ‘).strip()
    if not msg:continue

    client.send(msg.encode(‘utf-8‘))
    data=client.recv(1024)
    print(data.decode(‘utf-8‘))

FTP(實現文件傳輸並解決粘包)

服務端

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# __author__:JasonLIN
import os
import sys
import json
import struct
import socket
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR)
family_ip = "localhost"
port = 9991
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.bind((family_ip, port))

s.listen(5)


def cmd_put(conn, file_name, file_size):
    file_path = os.path.join(BASE_DIR, "server_home", file_name)
    data_recv = 0
    with open(file_path, "wb") as f:
        while file_size - data_recv > 1024:
            data = conn.recv(1024)
            f.write(data)
            data_recv += len(data)
        else:
            data = conn.recv(file_size - data_recv)
            f.write(data)
            data_recv += len(data)
            
    print("文件接收完成,一共", data_recv)
    

def cmd_get():
    pass


def run():
    while True:
        try:
            print("start.....")
            conn, addr = s.accept()
            head = conn.recv(4)
            header_len = struct.unpack("i", head)[0]
            data = json.loads(conn.recv(header_len))
            print(data, type(data))
            file_name = data["file_name"]
            func = data["func"]
            if func == "put":
                file_size = data["file_size"]
                cmd_put(conn, file_name, file_size)
            elif func == "get":
                cmd_get()

        except Exception as e:
            print(e)
            print("客戶端%s斷開" % conn)

        
if __name__ == ‘__main__‘:
    run()

客戶端

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# __author__:JasonLIN
import os,sys
import json
import socket
import struct
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR)
ip_port = ("localhost", 9991)
c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
c.connect(ip_port)


def put(name):
    file_dir = os.path.join(BASE_DIR, "client_home", name)
    file_size = os.path.getsize(file_dir)
    data_header = {
        "func": "put",
        "file_name": name,
        "file_size": file_size
    }
    header_bytes = bytes(json.dumps(data_header), encoding="utf-8")
    head_len_bytes = struct.pack("i", len(header_bytes))  # 4個字節
    c.send(head_len_bytes)
    c.send(header_bytes)
    
    i = 0
    with open(file_dir, "rb")as f:
        for line in f:
            c.send(line)
            i += len(line)
    print("文件傳輸完成,一共:", i)
    

def get():
    pass


def run():
    while True:
        cmd = input(">>").strip()  # put test.mp4
        if cmd is None:
            continue
        if cmd == "q":
            break
        cmd_list = cmd.split(" ")
        func = cmd_list[0]
        file_name = cmd_list[1]
        if func == "put":
            put(file_name)
        elif func == "get":
            get(file_name)
        else:
            print("輸入有誤")
            continue
     
        
if __name__ == ‘__main__‘:
    run()

 面向對象版本FTP

服務端

import os
import sys
import socket
import json
import struct
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR)
print(BASE_DIR)


class MyFtpServer:
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    server_dir = os.path.join(BASE_DIR, "server_home")
    max_packet_size = 8196
    request_queue_size = 5
    coding = "utf-8"
    allow_reuse_address = False
    
    def __init__(self, address, bind_and_active=True):
        self.server_address = address
        self.socket = socket.socket(self.address_family, self.socket_type)
        
        if bind_and_active:
            try:
                self.connect()
            except:
                self.server_close()
                raise
    
    def connect(self):
        """建立連接"""
        if self.allow_reuse_address:
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            
        self.socket.bind(self.server_address)
        self.server_address = self.socket.getsockname()
        self.socket.listen(self.request_queue_size)
     
    def get_request(self):
        return self.socket.accept()
    
    def server_close(self):
        self.socket.close()
        
    def close_request(self):
        self.conn.close()
        
    def run(self):
        while True:
            try:
                self.conn, self.addr = self.get_request()
                print(self.conn,  self.addr)
                while True:
                        head_strut = self.conn.recv(4)
                        if not head_strut:
                            break
                        head_len = struct.unpack("i", head_strut)[0]
                        head_dic = json.loads(self.conn.recv(head_len))
                        print(head_dic, type(head_dic))
                        func = head_dic["func"]
                        if hasattr(self, func):
                            func = getattr(self, func)
                            func(head_dic)
            except Exception:
                self.close_request()
                break
                        
    def put(self, head_dic):
        file_name = head_dic["file_name"]
        file_size = head_dic["file_size"]
        file_path = os.path.join(self.server_dir, file_name)
        data_recv = 0
        with open(file_path, "wb") as f:
            while file_size - data_recv > 1024:
                data = self.conn.recv(1024)
                f.write(data)
                data_recv += len(data)
            else:
                data = self.conn.recv(file_size - data_recv)
                f.write(data)
                data_recv += len(data)
    
        print("文件接收完成,一共", data_recv)
        
        
s = MyFtpServer(("127.0.0.1", 9999))
s.run()

客戶端

import os
import sys
import socket
import json
import struct
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR)
print(BASE_DIR)


class Myclient:
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    client_dir = os.path.join(BASE_DIR, "client_home")
    max_packet_size = 8196
    request_queue_size = 5
    coding = "utf-8"
    allow_reuse_address = False
    
    def __init__(self, server_address, connect=True):
        self.server_address = server_address
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
        if connect:
            try:
                self.client_connect()
            except:
                self.client_close()
                raise
    
    def client_connect(self):
        self.socket.connect(self.server_address)
        
    def client_close(self):
        self.socket.close()
    
    def run(self):
        while True:
            cmd = input(">>").strip()  # put test.mp3
            if cmd is None:
                continue
            if cmd == "q":
                break
            cmd_list = cmd.split(" ")
            func = cmd_list[0]
            if hasattr(self, func):
                func = getattr(self, func)
                func(cmd_list)
    
    def put(self, cmd_list):
        file_name = cmd_list[1]
        file_dir = os.path.join(self.client_dir, file_name)
        file_size = os.path.getsize(file_dir)
        data_header = {
            "func": "put",
            "file_name": file_name,
            "file_size": file_size
        }
        header_bytes = bytes(json.dumps(data_header), encoding="utf-8")
        head_len_bytes = struct.pack("i", len(header_bytes))  # 4個字節
        self.socket.send(head_len_bytes)
        self.socket.send(header_bytes)
    
        i = 0
        with open(file_dir, "rb")as f:
            for line in f:
                self.socket.send(line)
                i += len(line)
        print("文件傳輸完成,一共:", i)


client = Myclient(("127.0.0.1", 9999))
client.run()

socketserver實現多並發版本

https://github.com/Jasonlincoln/FTP

多進程版本

服務端

from multiprocessing import Process
import socket


def task(conn):
    while True:
        try:
            data = conn.recv(1024).decode(‘utf-8‘)
            print(data)
            conn.send(data.upper().encode(‘utf-8‘))
        except ConnectionRefusedError as e:
            print(e)
            
            
def server(ip, port):
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind((ip, port))
    server.listen(5)
    while True:
        conn, addr = server.accept()
        p = Process(target=task, args=(conn,))
        p.start()
    server.close()
    

if __name__ == ‘__main__‘:
    server("127.0.0.1", 9998)

客戶端

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 9998))
while True:
    msg = input(">>").strip()
    if not msg:
        continue
    client.send(msg.encode("utf-8"))
    data = client.recv(1024)
    print(data.decode("utf-8"))

網絡編程基礎