1. 程式人生 > 其它 >Python中Socket程式設計

Python中Socket程式設計

技術標籤:安全python網路

目錄

Python中Socket程式設計 1

一、Socket概述 1

二、python中socket模組使用 1

三、socket之聊天室 3

四、socket之埠探測 7

五、scapy之tcp埠探測 11

Python中Socket程式設計

一、Socket概述

socket通常也稱作"套接字",用於描述IP地址和埠,是一個通訊鏈的控制代碼,應用程式通常通過"套接字"向網路發出請求或者應答網路請求。

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

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

二、python中socket模組使用

通給指定地址簇和socket型別來進行建立

Socket地址簇描述
socket.AF_UNIX只能夠用於單一的Unix系統程序間通訊
socket.AF_INET伺服器之間網路通訊IPv4
socket.AF_INET6IPv6
socket型別描述
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)
Socket常用函式描述
sk.bind(address)將socket繫結到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。
sk.listen(backlog)開始監聽傳入連線。backlog指定在拒絕連線之前,可以掛起的最大連線數量。
sk.connect(address)連線到address處的socket。一般,address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。
sk.connect_ex(address)同上,只不過會有返回值,連線成功時返回 0 ,連線失敗時候返回編碼,例如:10061
sk.close()關閉socket
sk.recv(bufsize)接受socket的資料。資料以字串形式返回,bufsize指定最多可以接收的數量。
sk.send(string)將string中的資料傳送到連線的socket。
sk.sendall(string)將string中的資料傳送到連線的socket,但在返回之前會嘗試傳送所有資料。成功返回None,失敗則丟擲異常。
sk.settimeout(timeout)設定socket操作的超時期,timeout是一個浮點數,單位是秒。超時期應該在剛建立socket時設定.
sk.accept()接受連線並返回(conn,address),其中conn是新的socket物件,可以用來接收和傳送資料。address是連線客戶端的地址。

三、socket之聊天室

簡單收發

程式設計思路

服務端:

1 建立socket,因為socket是特殊的檔案(需要close),所以通過with
as來自動釋放檔案資源

with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as sk:

2 繫結套接字到本地IP與埠,開始監聽連線

s.bind((HOST,PORT))
s.listen()

3接受客戶端的連線請求,

conn, addr = s.accept()

4 通過accept建立的新socket物件接收資料併發送給對方資料

為了保證能收到所有傳送來的資訊,通過while迴圈來一直接受資料,直到資料為空

伺服器端程式碼

import socket

HOST = '127.0.0.1'
PORT = 33333

with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s:
    s.bind((HOST,PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print('conn by',addr)
        while True:
            data = conn.recv(1024)
            if not data:
                break
            print(data)
            conn.sendall(b'hello,client')

客戶端程式碼

import socket

HOST = '127.0.0.1'
PORT = 33333

with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s:
    s.connect((HOST,PORT))
    s.sendall(b'hello,friend')
    data = s.recv(1024)

print('rece',repr(data))

socket聊天室

伺服器

問題1:首先伺服器能同時連線多個客戶端

Socket.listen()後通過while迴圈

sk.accept()不斷接收新的連線並建立socket

問題2:伺服器要連線客戶端後能一直接受資料,直到接受到空資料

多個連線之間肯定是獨立的,並行的

所以通過執行緒的方式來實現

問題3:伺服器要指定客戶端埠和地址後進行回覆資訊

通過while 迴圈來一直接收input資料來指定傳送

但是上面為了處理多個客戶端時,也用了while

所以將連線多個客戶端寫入函式通過執行緒來實現,使之並行

伺服器程式碼

from socket import *
import threading


def text_recv(CliSock,addr):
    while True:
            data = CliSock.recv(1024)
            print(addr,"send to you:",data)
            if not data:
                break


def socket_up():
    SerSock = socket(AF_INET, SOCK_STREAM)
    SerSock.bind(('127.0.0.1',58787))
    SerSock.listen()
    while True:
        CliSock, addr = SerSock.accept()
        print('connnecting from:', addr)
        Host[str(addr[1])] = CliSock
        t = threading.Thread(target=text_recv,args=(CliSock,addr))
        t.start()


if __name__ == '__main__':
    Host = {}
    t = threading.Thread(target=socket_up)
    t.start()
    while True:
        try:
            target_info = input('please input your target : msg,port >\n ').split(',')
            msg = target_info[0]
            port = target_info[1]
            conn = Host[port]
            conn.sendall(msg.encode())
        except Exception as e:
            print(e)

客戶端

同服務端問題3一致

客戶端程式碼

import threading
from socket import *


def text_recv(SerSock):
    while True:
            data = SerSock.recv(1024)
            print("send to you:",data)
            if not data:
                break


if __name__ == '__main__':
    CliSock = socket(AF_INET, SOCK_STREAM)
    CliSock.connect(('127.0.0.1',58787))
    t = threading.Thread(target=text_recv, args=(CliSock,))
    t.start()
    while True:
        data1 = input('please input want send > \n')
        CliSock.send(data1.encode())
        if not data1:
            break

四、socket之埠探測

探測原理:當通過socket連線目標主機未開放埠時會傳出異常

例:ConnectionRefusedError: [WinError 10061]
由於目標計算機積極拒絕,無法連線。

程式設計思路

1 通過執行緒建立socket,對目標主機的範圍埠進行嘗試連線

2.對於沒有丟擲異常的埠嘗試接受資料瞭解埠資訊

問題:有些埠開放但不能接收到資訊,就會將程序卡在接受資料那

解決:通過給socket物件設定settimeout()屬性來限制超時時間,當超過時間時丟擲新的異常:即timeout

所以 except timeout 也是存活埠,但是埠資訊沒有獲取到

3.將存活埠和埠資訊加入到字典中,最後遍歷輸出

實現程式碼

import threading
from socket import *


def port_find(p):
    try:
        with socket(AF_INET, SOCK_STREAM) as sk:
            sk.connect((HOST, p))
            sk.settimeout(3)
            alive[p] = sk.recv(1024).strip().decode()
            print("port[{}] is alive\n".format(p), end='')

    except timeout:
        print("port[{}] is alive\n".format(p), end='')
        alive[p] = 'this not have info'
    except:
        pass


if __name__ == '__main__':
    HOST = '10.30.25.199'
    alive = {}
    threads = []
    for p in range(1, 65535):
        t = threading.Thread(target=port_find, args=(p,))
        threads.append(t)
        t.start()
    for t1 in threads:
        t1.join()
    for k,v in alive.items():
        print(k,'--->',v)

connect_ex探究使用

connect_ex相對於connect來說多了返回值,連線成功時返回 0
,連線失敗時候返回編碼,例如:10061

那麼就可以通過返回值來對是否存活進行判斷

但是還是需要接收那些沒有資料的埠,所以設定settimeout,然後在進一步嘗試執行,成功就記錄收到的資料,失敗就表示埠開放但是沒有資料

實現程式碼

import threading
from socket import *


def port_find(p):
        with socket(AF_INET, SOCK_STREAM) as sk:
            re = sk.connect_ex((HOST, p))
            sk.settimeout(3)
            if re == 0:
                print("port[{}] is alive\n".format(p), end='')
                try:
                    alive[p] = sk.recv(1024).strip().decode()
                except:
                    alive[p] = 'this not have info'
            else:
                print("port[{}] is dead\n".format(p), end='')


if __name__ == '__main__':
    HOST = '10.30.25.199'
    alive = {}
    threads = []
    for p in range(1, 65535):
        t = threading.Thread(target=port_find, args=(p,))
        threads.append(t)
        t.start()
    for t1 in threads:
        t1.join()
    for k,v in alive.items():
        print(k,'--->',v)

探究:因為執行緒開啟太多會損害效能

那麼如何用兩個執行緒去完成socket的埠探測?

這裡用到了python的佇列(queue)

程式設計思想

將掃描的埠一次性put加入到佇列中

呼叫二個子執行緒開始並行

回撥函式中while不斷利用get從佇列取出埠號進行掃描

就實現了兩個執行緒的並行掃描,互不干涉

實現程式碼

from socket import *
from queue import Queue
import threading


def port_find():
    while not port_que.empty():
        p = port_que.get()
        with socket(AF_INET, SOCK_STREAM) as sk:
            re = sk.connect_ex((HOST, p))
            sk.settimeout(3)
            if re == 0:
                print("port[{}] is alive\n".format(p), end='')
                try:
                    alive[p] = sk.recv(1024).strip().decode()
                except:
                    alive[p] = 'this not have info'
            else:
                print("port[{}] is dead\n".format(p), end='')


if __name__ == '__main__':
    HOST = '192.168.84.130'
    alive = {}
    threads = []
    port_que = Queue()
    for i in range(30):
        port_que.put(i)
    t1 = threading.Thread(target=port_find)
    t2 = threading.Thread(target=port_find)
    threads.append(t1)
    threads.append(t2)
    t1.start()
    t2.start()
    for t in threads:
        t.join()

    for k, v in alive.items():
        print(k, '--->', v)

五、scapy之tcp埠探測

抓包分析

失敗流程:1.SYN-> 2.RST,ACK->

image-20210108161148659

成功流程:1.SYN-> 2.SYN,ACK-> 3.ACK->

image-20210108161158565

可知:Socket是通過tcp三次握手進行埠探測

scapy中TCP(flags=‘S’)可以實現第一次握手,傳送SYN包

flags=‘SA’–SYN,ACK包,flags=’RA’—RST,ACK包

程式設計思路

先用scapy構造一個 flags 的值為 S 的報文,S 代表SYN,就是請求建立一個 TCP
連線,在接收到伺服器返回的報文中,如果 flagw
上的值為SA(SYN,ACK),那麼就代表伺服器確定接收到傳送的連線請求並同意建立連線,這時候客戶端再回應一個AR(ACK,RST)報文,確定接收到伺服器的響應,建立連線後又立刻斷開。

實現程式碼

from scapy.all import *
from scapy.layers.inet import IP, TCP


def port_scan(port):
    packet = IP(dst=ip)/TCP(dport=port,flags='S')   # 構造一個 flags 的值為 S 的報文
    result = sr1(packet,timeout=2,verbose=0)
    if result.haslayer('TCP'):
        if result['TCP'].flags == 'SA':   # 判斷目標主機是否返回 SYN+ACK
            send = sr1(IP(dst=ip)/TCP(dport=port,flags='AR'),timeout=2,verbose=0) # 向目標主機發送 ACK+RST
            print('[+] {} is open'.format(port))
        elif result['TCP'].flags == 'RA':
            pass


if __name__ == '__main__':
    ip = '192.168.84.130'
    threads = []
    for p in range(1, 30):
        t = threading.Thread(target=port_scan, args=(p,))
        threads.append(t)
        t.start()
    for t in threads:
        t.join()