1. 程式人生 > >萬物互聯之~網路程式設計上篇

萬物互聯之~網路程式設計上篇

3.TCP

TCP是一種面向連線的、可靠的協議,TCP傳輸的雙方需要首先建立連線,之後由TCP協議保證資料收發的可靠性,丟失的資料包自動重發,上層應用程式收到的總是可靠的資料流,通訊之後關閉連線(有點像打電話)

用過下載軟體的可能遇到過一種‘Bug’ ==> 很多人為了防止自己本地檔案納入共享大軍,一般都是直接把網路上傳給禁了,然後發現檔案經常出問題?

其實這個就是TCP的一個應用,檔案一般都很大,所以進行分割後批量下載,那少量的網路上傳其實是為了校驗一下檔案 ==> 正確做法是限制上傳速度而不是禁止(學生時代那會還經常蛋疼這個問題,現在想想還挺好玩的O(∩_∩)O

大多數連線都是可靠的TCP連線。建立TCP連線時,主動發起連線的叫客戶端,被動響應連線的叫伺服器

上面那個例子裡,我們的下載工具就是客戶端,每一小段檔案接收完畢後都會向伺服器傳送一個完成的指令來保證檔案的完整性

3.1.TCP客戶端

來看一個簡單的入門案例:

from socket import socket

def main():
    # 預設就是建立TCP Socket
    with socket() as tcp_socket:
        # 連線伺服器(沒有返回值)
        tcp_socket.connect(("192.168.36.235", 8080))
        # 傳送訊息(返回傳送的位元組數)
        tcp_socket
.send("小張生日快樂~".encode("utf-8")) # 接收訊息 msg = tcp_socket.recv(1024) print(f"伺服器:{msg.decode('utf-8')}") if __name__ == '__main__': main()

輸出:(socket()預設就是建立TCP Socket3.tcp_client.gif

概括來說:

  1. TCP,有點像打電話,先撥號連通了(connect)才能通訊(sendrecv),之後的通訊不用再撥號連通了
  2. UDP,有點像寄信封,每次寄過去都不確定能不能收到,每次通訊都得寫地址(ip
    +port)

程式碼四步走:(TCP客戶端其實建立Socket之後connect一下伺服器就OK了)

  1. 建立:tcp_sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  2. 連線:tcp_sock.connect((IP, Port))
  3. 傳送:tcp_sock.send(Bytes內容) 接收:tcp_sock.recv(count)
  4. 關閉:tcp_sock.close()

模擬HTTP

from socket import socket

def get_buffer(tcp_socket):
    buffers = b''
    while True:
        b = tcp_socket.recv(1024)
        if b:
            buffers += b
        else:
            break
    # 返回bytes
    return buffers

def main():
    with socket() as tcp_socket:
        # 連線伺服器
        tcp_socket.connect(("dotnetcrazy.cnblogs.com", 80))
        # 傳送訊息(模擬HTTP)
        tcp_socket.send(
            b'GET / HTTP/1.1\r\nHost: dotnetcrazy.cnblogs.com\r\nConnection: close\r\n\r\n'
        )
        # 以"\r\n\r\n"分割一次
        header, data = get_buffer(tcp_socket).split(b"\r\n\r\n", 1)
        print(header.decode("utf-8"))
        with open("test.html", "wb") as f:
            f.write(data)
    print("over")

if __name__ == '__main__':
    main()

輸出:(test.html就是頁面原始碼)

HTTP/1.1 200 OK
Date: Thu, 01 Nov 2018 03:10:48 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 20059
Connection: close
Vary: Accept-Encoding
Cache-Control: private, max-age=10
Expires: Thu, 01 Nov 2018 03:10:58 GMT
Last-Modified: Thu, 01 Nov 2018 03:10:48 GMT
X-UA-Compatible: IE=10
X-Frame-Options: SAMEORIGIN
over

注意\r\nConnection:closesplit("",分割次數)

3.2.TCP服務端

服務端程式碼相比於UDP,多了一個監聽和等待客戶端,其他基本上一樣:

客戶端Code:(如果你想固定埠也可以繫結一下Port)

from socket import socket

def main():
    # 預設就是建立TCP Socket
    with socket() as tcp_socket:
        # 連線伺服器(沒有返回值)
        tcp_socket.connect(("192.168.36.235", 8080))

        print("Connected TCP Server...")  # 連線提示

        # 傳送訊息(返回傳送的位元組數)
        tcp_socket.send("小張生日快樂~\n".encode("utf-8"))
        # 接收訊息
        msg = tcp_socket.recv(1024)
        print(f"伺服器:{msg.decode('utf-8')}")

if __name__ == '__main__':
    main()

服務端Code:

from socket import socket

def main():
    with socket() as tcp_socket:
        # 繫結埠(便於客戶端找到)
        tcp_socket.bind(('', 8080))
        # 變成被動接收訊息(監聽)
        tcp_socket.listen()  # 不指定連線最大數則會設定預設值

        print("TCP Server is Running...")  # 執行後提示

        # 等待客戶端發信息
        client_socket, client_addr = tcp_socket.accept()

        with client_socket:
            # 客戶端連線提示
            print(f"[來自{client_addr[0]}:{client_addr[1]}的訊息]\n")

            # 接收客戶端訊息
            data = client_socket.recv(1024)
            print(data.decode("utf-8"))

            # 回覆客戶端
            client_socket.send("知道了".encode("utf-8"))

if __name__ == '__main__':
    main()

輸出:(先執行服務端,再執行客戶端。客戶端發了一個生日快樂的祝福,服務端回覆了一句) 3.tcp_server.gif

3.2.TCP服務端除錯助手

如果像上面那般,並不能多客戶端通訊 3.bug.png

這時候可以稍微改造一下:

客戶端:

from time import sleep
from socket import socket
from multiprocessing.dummy import Pool

def send_msg(tcp_socket):
    with tcp_socket:
        while True:
            try:
                tcp_socket.send("小明同志\n".encode("utf-8"))
                sleep(2)  # send是非阻塞的
                print("向伺服器問候了一下")
            except Exception as ex:
                print("服務端連線已斷開:", ex)
                break

def recv_msg(tcp_socket):
    with tcp_socket:
        while True:
            # 這邊可以不捕獲異常:
            #    服務端關閉時,send_msg會關閉,然後這邊也就關閉了
            try:
                data = tcp_socket.recv(1024)
                if data:
                    print("服務端回覆:", data.decode("utf-8"))
            except Exception as ex:
                print("tcp_socket已斷開:", ex)
                break

def main():
    with socket() as tcp_socket:
        # 連線TCP Server
        tcp_socket.connect(("192.168.36.235", 8080))
        print("Connected TCP Server...")  # 連線提示

        pool = Pool()
        pool.apply_async(send_msg, args=(tcp_socket,))
        pool.apply_async(recv_msg, args=(tcp_socket,))
        pool.close()
        pool.join()

if __name__ == '__main__':
    main()

服務端

伺服器需要同時響應多個客戶端的請求,那麼每個連線都需要一個新的程序或者執行緒來處理

from socket import socket
from multiprocessing.dummy import Pool

def wait_client(client_socket, ip_port):
    with client_socket:
        while True:
            data = client_socket.recv(1024)
            print(f"[來自{ip_port}的訊息]:\n{data.decode('utf-8')}")
            client_socket.send(b"I Know")  # bytes型別

def main():
    with socket() as tcp_socket:
        # 繫結埠
        tcp_socket.bind(('', 8080))
        # 伺服器監聽
        tcp_socket.listen()

        print("TCP Server is Running...")  # 執行後提示

        p = Pool()
        while True:
            # 等待客戶端連線
            client_socket, client_addr = tcp_socket.accept()
            ip_port = f"{client_addr[0]}:{client_addr[1]}"
            print(f"客戶端{ip_port}已連線")
            # 響應多個客戶端則需要多個執行緒來處理
            p.apply_async(wait_client, args=(client_socket, ip_port))

if __name__ == '__main__':
    main()

演示:(死迴圈,Pool都不用管了) 3.正常流程.gif

伺服器掛了客戶端也會自動退出: 3.自動退出.gif

用TCP協議進行Socket程式設計在Python中十分簡單:

  1. 客戶端:主動連線伺服器的IP和指定埠
  2. 伺服器:先監聽指定埠,然後對每一個新的連線建立一個執行緒或程序來處理

3.3.NetCore版

Server版

大體流程和Python一樣:

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace _2_TCP
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var tcp_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
            {
                var ip_addr = IPAddress.Parse("192.168.36.235");
                // 伺服器端繫結Port
                tcp_socket.Bind(new IPEndPoint(ip_addr, 8080));
                // 伺服器監聽
                tcp_socket.Listen(5);
                while (true)
                {
                    // 等待客戶端連線
                    var client_socket = tcp_socket.Accept();
                    // 遠端埠
                    var client_point = client_socket.RemoteEndPoint;
                    Task.Run(() =>
                    {
                        while (true)
                        {
                            byte[] buffer = new byte[1024];
                            int count = client_socket.Receive(buffer);
                            Console.WriteLine($"來自{client_socket.RemoteEndPoint.ToString()}的訊息:\n{Encoding.UTF8.GetString(buffer, 0, count)}");
                            client_socket.Send(Encoding.UTF8.GetBytes("知道了~"));
                        }
                    });
                }
            }
        }
    }
}

Client版

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace client
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var tcp_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
            {
                // 連線伺服器
                tcp_socket.Connect(new IPEndPoint(IPAddress.Parse("192.168.36.235"), 8080));

                while (true)
                {
                    // 傳送訊息
                    tcp_socket.Send(Encoding.UTF8.GetBytes("伺服器你好"));
                    // 接收伺服器訊息
                    byte[] buffer = new byte[1024];
                    int count = tcp_socket.Receive(buffer);
                    Console.WriteLine($"來自伺服器的訊息:{Encoding.UTF8.GetString(buffer, 0, count)}");
                }
            }
        }
    }
}

圖示: 3.netcore.gif

擴充套件

上面忘記說了,Socket是可以設定超時時間的,eg:tcp_socket.settimeout(3)

探一探localhost

程式碼不變,如果把TCP客戶端的連線伺服器IP空著或者改成127.0.0.1,咱們再看看效果:tcp_socket.connect(('', 8080))

圖示:(怎麼樣,這回知道本機問啥可以不寫IP了吧) 3.localhost.png

手寫一個埠掃描工具

埠掃描大家不陌生,自己實現一個簡單的TCP埠掃描工具:

from socket import socket
from multiprocessing.dummy import Pool

ip = "127.0.0.1"

def tcp_port(port):
    """IP:服務端IP,Port:服務端Port"""
    with socket() as tcp_socket:
        try:
            tcp_socket.connect((ip, port))
            print(f"[TCP Port:{port} is open]")
        except Exception:
            pass

def main():
    # 檢視系統本地可用埠極限值 cat /proc/sys/net/ipv4/ip_local_port_range
    max_port = 60999
    global ip
    ip = input("請輸入要掃描的IP地址:")
    print(f"正在對IP:{ip}進行埠掃描...")

    pool = Pool()
    pool.map_async(tcp_port, range(max_port))
    pool.close()
    pool.join()

if __name__ == '__main__':
    main()

輸出:(你把埠換成常用埠列表就知道伺服器開了哪些服務了

[email protected]:~/桌面/work/BaseCode/python/6.net/3.Ext python3 1.port_scan.py 
請輸入要掃描的IP地址:192.168.36.235
正在對IP:192.168.36.235進行埠掃描...
[TCP Port:22 is open]
[TCP Port:41004 is open]
[email protected]:~/桌面/work/BaseCode/python/6.net/3.Ext sudo nmap -sT 192.168.36.235 -Pn -p-

Starting Nmap 7.60 ( https://nmap.org ) at 2018-11-02 18:15 CST
Nmap scan report for MZY-PC (192.168.36.235)
Host is up (0.000086s latency).
Not shown: 65534 closed ports
PORT   STATE SERVICE
22/tcp open  ssh

Nmap done: 1 IP address (1 host up) scanned in 2.07 seconds

課後思考

可以自行研究拓展:

  1. 為啥傳送(sendsendto)和接收(recvrecvfrom)都是兩個方法?(提示:方法名阻塞
  2. sendsendall有啥區別?
  3. 有沒有更方便的方式來實現服務端?
  4. 結合內網對映或者ShellCode實現一個遠控

課外拓展:

官方Socket程式設計文件【推薦】
https://docs.python.org/3/library/socket.html

Python核心程式設計之~網路程式設計【推薦】
https://wizardforcel.gitbooks.io/core-python-2e/content/19.html

TCP程式設計知識
https://dwz.cn/dDkXzqcV

網路程式設計-基礎
https://www.jianshu.com/p/55c171ebe5f1

網路程式設計-UDP
https://www.jianshu.com/p/594870b1634b

網路程式設計-TCP
https://www.jianshu.com/p/be36d4db5618

Python總結之 recv與recv_from
https://www.jianshu.com/p/5643e810123f
https://blog.csdn.net/xvd217/article/details/38902081
https://blog.csdn.net/pengluer/article/details/8812333

埠掃描擴充套件:(Python2)
https://thief.one/2018/05/17/1

Python socket藉助ngrok建立外網TCP連線
https://www.jianshu.com/p/913b2013a38f

TCP協議知識:
https://www.cnblogs.com/wcd144140/category/1313090.html

相關推薦

萬物互聯網路程式設計

3.TCP¶ TCP是一種面向連線的、可靠的協議,TCP傳輸的雙方需要首先建立連線,之後由TCP協議保證資料收發的可靠性,丟失的資料包自動重發,上層應用程式收到的總是可靠的資料流,通訊之後關閉連線(有點像打電話) 用過下載軟體的可能遇到過一種‘Bug’ ==> 很多人為了防止自己本地檔案納入

萬物互聯網路程式設計中篇

加強篇¶ 1.引入¶ ShellCode¶ 上節寫了個埠掃描器,這次寫個ShellCode回顧下上節內容 肉雞端: #!/usr/bin/env python3 import sys import subprocess from socket import socket de

Python3 與 C# 併發程式設計 執行緒

2.2.加強篇¶ 其實以前的Linux中是沒有執行緒這個概念的,Windows程式設計師經常使用執行緒,這一看~方便啊,然後可能是當時程式設計師偷懶了,就把程序模組改了改(這就是為什麼之前說Linux下的多程序程式設計其實沒有Win下那麼“重量級”),弄了個精簡版程序==>執行緒(核心是分不出程序

Python3 與 C# 網路程式設計 網路基礎

最新版本檢視:https://www.cnblogs.com/dotnetcrazy/p/9919202.html 入門篇 官方文件:https://docs.python.org/3/library/ipc.html(程序間通訊和網路) 例項程式碼:https://github.com/lotapp/

12學通C#網路程式設計——第一 基礎程序執行緒

   在C#的網路程式設計中,程序和執行緒是必備的基礎知識,同時也是一個重點,所以我們要好好的掌握一下。 一:概念           首先我們要知道什麼是”程序”,什麼是“執行緒”,好,查一下baike。   程序:是一個具有一定獨立功能的程式關於某個資料集合的一次

鈴木敏文《零售的哲學》品讀哲學的力量

由於即將進入新零售行業做一名演算法工程師,故提前買了相關書籍讀了讀,作為一個標準的理工男--屌絲程式設計師,有不足之處,還請指教。 作者是鈴木敏文,現任7-Eleven 的會長兼CEO,全世界擁有33000家店鋪、總銷售額達8兆日元----這並不是最新資料。當然相對於吃瓜群眾喜歡看看人家如何牛逼

python摸爬滾打day26----網路程式設計socket

1、網路通訊原理   網際網路的本質就是一系列的網路協議, 統稱為網際網路協議.   網際網路協議的功能:定義計算機如何接入internet,以及接入internet的計算機通訊的標準。   網際網路協議按照功能不同分為osi七層或tcp/ip五層或tcp/ip四層.    對於tcp\

python3裝飾器實現

裝飾器是python中一個強大的功能,利用裝飾器可以在不必改動原有函式的前提下增加一些功能,可以被用於日誌記錄、許可權驗證、除錯測試、事務處理等。裝飾器是閉包的一種形式,閉包是python函式程式設計的高階用法,學習閉包之前讓我們先了解python函式的概念理解

Java網路程式設計基礎

一、前言 網路通訊在系統互動中是必不可少的一部分,無論是面試還是工作中都是繞不過去的一部分,本節我們來談談Java網路程式設計中的一些知識,本chat內容如下: 網路通訊基礎知識,剖析網路通訊的本質和需要注意的點 使用Java BIO阻塞套接字 實現簡單TCP

菜鳥先飛JAVA_網路程式設計

網路程式設計概述 計算機網路,是指將地理位置不同的具有獨立功能的多臺計算機及其外部裝置,通過通訊線路連線起來,在網路作業系統,網路管理軟體及網路通訊協議的管理和協調下,實現資源共享和資訊傳遞的計算機系統。 網路程式設計,就是用來實現網路互連的不同計算機上執行

黑馬程式設計師 【】java學習路——網路程式設計 UDP 鍵盤錄入傳輸

import java.net.*; import java.io.*; class UdpRece2  {public static void main(String[] args) throws Exception{DatagramSocket ds = new DatagramSocket(10001)

kafka原始碼解析十二KafkaController()

class KafkaController(val config : KafkaConfig, zkClient: ZkClient, val brokerState: BrokerState) extends Logging with KafkaMetricsGroup { …… private val c

伺服器開發linux網路程式設計---學習章節(一)

前言:    近期學習了伺服器相關的開發,平常主要擼c的程式,所以就下定決心研究了c的伺服器開發,目的也在與鞏固c的基本知識。詳細分享如下,若有錯誤請指正,希望與大家探討,共同學習進步。                                             

萬物互聯~RPC專欄

通話 防火 而已 練習 res utf 開放 man 簡化 其他專欄最新篇:協程加強之~兼容答疑篇 | 聊聊數據庫~SQL環境篇 上篇回顧:萬物互聯之~深入篇 3.RPC引入 Code:https://github.com/lotapp/BaseCode/tree/mast

網路程式設計第一:IP地址結構sin_addr的定義解析。

IP地址結構:    struct   in_addr {                      union {                             struct {                                         un

C/C++ socket網路程式設計掃盲

引言 socket 是“套接字”的意思,是計算機之間進行通訊的一種約定,也可以認為是一種技術。通過 socket 這種約定,一臺計算機可以接收其他計算機的資料,也可以向其他計算機發送資料。 socket 的典型應用就是 Web 伺服器和瀏覽器:瀏覽器獲取使用者輸入的URL

socket網路程式設計基礎

         首先列舉一下socket網路通訊的例子:使用區域網打遊戲,用瀏覽器連線外網看視訊,使用QQ與好友通訊,手機連線wifi傳資料等等。socket是底層抽象給應用層所使用的一套介面函式,本篇講解這些函式的使用。 物件:1、伺服器server(等待客戶端連線)

socket網路程式設計-高階

7. Netty實現檔案伺服器(基於HTTP協議) 8. 最佳實踐 9. Mina入門基礎 *******************************************************

黑馬程式設計師--Java學習日記GUI&網路程式設計

------- android培訓、java培訓、期待與您交流! ---------- GUI 如何建立一個視窗並顯示  Graphical User Interface(圖形使用者介面)。   

Linux IO多路複用epoll網路程式設計,高併發的使用例子 (含原始碼)

#include <unistd.h> #include <sys/types.h> /* basic system data types */ #include <sys/socket.h> /* basic socket definiti