Python3網絡編程--socket編程之Tcp編程

目錄

1:Python3中socket編程介紹

2:簡單的點對點

3:併發服務端

3.1:創建線程處理

3.2:socketserver模塊處理

3.3:使用select模塊

4:socket實現web服務器

4.1:簡單web服務器

4.2:簡單web服務器:函數版本

4.3:簡單web服務器:返回動態頁面

4.4:併發web服務器


1:Python3中socket編程介紹

這裏就不介紹網絡編程的基礎知識了,比如TCP/IP協議,OSI模型,TCP的三次握手等。下面直接介紹python中socket編程;

2:簡單的點對點

只接受單個連接的服務端:
01_TcpServer.py: 

# -*- coding: utf-8 -*-

import socket
import time

server = socket.socket()
ip_port = ('127.0.0.1',8000)
server.bind(ip_port)  # 綁定ip+端口
server.listen(10)  # 監聽

# print(help(server.listen))

print('啓動服務:等待客戶端的連接......')
conn,addr = server.accept()
print('客戶端已連接:')
print(conn)
print(addr)

while True:
    print('等待客戶端的數據:')
    client_data = conn.recv(1024)  # 接收客戶端的數據,接收的數據是當前時間的秒數,下面處理是把秒轉換爲字符串
    client_data = client_data.decode('utf-8')
    stru_t = time.localtime(float(client_data))
    strTime = time.strftime('%Y-%m-%d %H:%M:%S',stru_t)
    print(f'接收來自{addr}的數據:{strTime}' )

    conn.send(str(time.time()).encode('utf-8'))  # 給客戶端發送當前時間的秒數

conn.close()
server.close()

01_TcpClient.py

# -*- coding: utf-8 -*-

import socket
import  time
client = socket.socket()
ip_port = ('127.0.0.1',8000)
client.connect(ip_port)

while True:
    client.send(str(time.time()).encode('utf-8'))
    print('等待服務端的數據:')

    server_data = client.recv(1024)
    server_data = server_data.decode('utf-8')
    stru_t = time.localtime(float(server_data))
    strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
    print(f'來自服務端的數據:{strTime}')
    time.sleep(10)

client.close()

3:併發服務端

3.1:創建線程處理

對於每個客戶端連接都創建一個線程來處理:

02_TcpServer.py:

# -*- coding: utf-8 -*-

import socket
from socket import SOL_SOCKET,SO_REUSEADDR
import time
import threading  # 線程模塊
import traceback  # 打印異常信息

# 處理每個鏈接
def handle_client(conn, addr):
    print(conn, addr)
    while True:
        try:
            print(f'等待客戶端{addr}的數據:')
            client_data = conn.recv(1024)  # 接收客戶端的數據,接收的數據是當前時間的秒數,下面處理是把秒轉換爲字符串
            client_data = client_data.decode('utf-8')
            stru_t = time.localtime(float(client_data))
            strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
            print(f'接收來自{addr}的數據:{strTime}')

            conn.send(str(time.time()).encode('utf-8'))  # 給客戶端發送當前時間的秒數
        except ConnectionResetError:
            val = traceback.format_exc()
            print(val)
            break
        except Exception:
            val = traceback.format_exc()
            print(val)

    print(f'關閉鏈接{addr}')
    conn.close()


def StartTcpServer(ip,port):
    server = socket.socket()
    server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)  # 在bind前加,允許地址重用
    server.bind((ip,port))  # 綁定ip+端口
    server.listen(10)  # 監聽

    # print(help(server.accept))

    while True:
        try:
            print('等待客戶端的連接......')
            conn, addr = server.accept()  # 一直會阻塞在這裏,直到有新的連接進來
            # 創建一個線程來處理每個鏈接
            threading.Thread(target=handle_client, args=(conn, addr)).start()
        except Exception:
            val = traceback.format_exc()
            print(val)
            break  # 有異常退出循環

    server.close()

if __name__ == '__main__':
    StartTcpServer('127.0.0.1', 8000)

02_TcpClient.py:

# -*- coding: utf-8 -*-

import socket
import  time
import traceback

def StartTcpClient(ip_port):
    # 開3個客戶端
    client_list = [socket.socket() for i in range(3)]
    # ip_port = ('127.0.0.1',8000)

    for client in client_list:
        print(client)
        client.connect(ip_port)

    while True:
        for index,client in enumerate(client_list):
            try:
                client.send(str(time.time()).encode('utf-8'))
                print(f'clirnt[{index}],等待服務端的數據:')

                server_data = client.recv(1024)
                server_data = server_data.decode('utf-8')
                stru_t = time.localtime(float(server_data))
                strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
                print(f'來自服務端的數據:{strTime}')
                time.sleep(2)

            except Exception:
                val = traceback.format_exc()
                print(val)
                client.close()
                client_list.remove(client)


if __name__ == '__main__':
    StartTcpClient(('127.0.0.1',8000))

3.2:socketserver模塊處理

在上面使用每來一個連接,就創建一個線程的方式來處理,如果連接的數量過多,創建線程就會出現問題。

在Python中提供了socketserver模塊,socketserver在內部使用IO多路複用以及多線程/進程機制,實現了併發處理多個客戶端請求的socket服務端。

03_TcpServer.py

# -*- coding: utf-8 -*-

import socketserver
import time
import traceback  # 打印異常信息

def handle_client_data(data,addr):
    try:
        data = data.decode('utf-8')
        # print(type(data),data)
        stru_t = time.localtime(int(data))
        strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
        print(f"來自{addr}的客戶端向你發來信息:{data},轉換之後:{strTime}"  )
    except Exception:
        pass

# 每個鏈接都使用一個 TcpServer 實例對象來處理,並且會自動調用handle方法,退出handle方法鏈接就會自動斷開。
class TcpServer(socketserver.BaseRequestHandler):
    """
    必須繼承socketserver.BaseRequestHandler類
    """
    def handle(self):
        """
        必須實現這個方法!
        :return:
        """
        conn = self.request         # request裏封裝了所有請求的數據
        conn.sendall(str(time.time()).encode('utf-8'))
        print(f"1111:{self.client_address}")    # 打日誌驗證每個鏈接是不是都會進來一次
        while True:
            try:
                data = conn.recv(1024)
                handle_client_data(data,self.client_address)
                conn.sendall(str(int(time.time())).encode('utf-8'))
            except Exception:
                val = traceback.format_exc()
                print(val)
                break
        print(f'退出客戶端{self.client_address}的處理。')

def StartTcpServer(ip,port):
    # 創建一個多線程TCP服務器
    server = socketserver.ThreadingTCPServer((ip,port), TcpServer)
    print("啓動socketserver服務器!")
    # 啓動服務器,服務器將一直保持運行狀態
    server.serve_forever()

if __name__ == '__main__':
    StartTcpServer('127.0.0.1', 8000)
03_TcpClient.py:
# -*- coding: utf-8 -*-

import socket
import  time
import traceback

def StartTcpClient(ip_port,nums):
    client_list = [socket.socket() for i in range(nums)]
    for client in client_list:
        print(client)
        client.connect(ip_port)

    while True:
        try:
            for index,client in enumerate(client_list):
                try:
                    # client.send(str( int(time.time()) ).encode('utf-8'))
                    client.sendall(str( int(time.time()) ).encode('utf-8'))
                    print(f'clirnt[{index}],等待服務端的數據:')

                    server_data = client.recv(1024)
                    # print(f'來自服務端的數據:{server_data}')
                    try:
                        server_data = server_data.decode('utf-8')
                        stru_t = time.localtime(float(server_data))
                        strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
                        print(f'來自服務端的數據:{server_data} 轉換之後:{strTime}')
                    except Exception:
                        pass
                    time.sleep(1)


                except (ConnectionResetError,ConnectionAbortedError):
                    val = traceback.format_exc()
                    print(val)
                    client.close()
                    client_list.remove(client)

                    if len(client_list) < 1:
                        raise

                except Exception:
                    val = traceback.format_exc()
                    print(val)

        except Exception:
            val = traceback.format_exc()
            print(val)
            print('退出')
            break

def test():
    print(str(time.time() ),time.time(),int(time.time()))

if __name__ == '__main__':
    StartTcpClient(('127.0.0.1',8000),1)
    test()

3.3:使用select模塊

Python中的select模塊專注於I/O多路複用,提供了select  poll  epoll三個方法(其中後兩個在Linux中可用,windows僅支持select),另外也提供了kqueue方法(freeBSD系統)

select方法:
進程指定內核監聽哪些文件描述符(最多監聽1024個fd)的哪些事件,當沒有文件描述符事件發生時,進程被阻塞;當一個或者多個文件描述符事件發生時,進程被喚醒。
04_TcpServer.py:

# -*- coding: utf-8 -*-

import socket
import select
from socket import SOL_SOCKET,SO_REUSEADDR
import time
import traceback  # 打印異常信息


def handle_client_data(data,addr):
    try:
        data = data.decode('utf-8')
        # print(type(data),data)
        stru_t = time.localtime(int(data))
        strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
        print(f"來自{addr.getpeername()}的客戶端向你發來信息:{data},轉換之後:{strTime}")
    except Exception:
        pass


def StartTcpServer(ip,port):
    server = socket.socket()
    server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)  # 在bind前加,允許地址重用
    server.bind((ip,port))  # 綁定ip+端口
    server.listen(10)  # 監聽

    read_fd_list = [server,]

    while True:
        try:
            r_list, w_list, error_list = select.select(read_fd_list, [], [], 1) # 超時設置爲1秒

            stru_t = time.localtime(time.time())
            strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
            print(f'select之後:len(r_list)={len(r_list)},時間:{strTime}')

            for fd in r_list:  # r_list 爲可讀的文件描述符列表
                if fd == server: #   可讀的文件描述符 爲 server(上面起服務創建的),說明有鏈接請求過來。
                    conn, addr = fd.accept()  #  接收客戶端鏈接
                    print(addr)
                    read_fd_list.append(conn)  # 把客戶端,加入讀輪詢列表
                    data = conn.recv(1024)
                    print(f'接收數據:msg={data}')
                    handle_client_data(data, conn)
                    conn.sendall(str(time.time()).encode('utf-8'))  # sendall 會循環調用send,把所有數據都發出去。
                    # conn.send(str(time.time()).encode('utf-8'))     # send 發送出去的數據可能是部分數據
                else: # 客戶端可讀,就是有客戶端發送數據過來。
                    try:
                        data = fd.recv(1024)
                        # print(help(fd))
                        handle_client_data(data,fd)
                        fd.sendall(str(int(time.time())).encode('utf-8'))
                    except (ConnectionResetError,ConnectionAbortedError,ConnectionRefusedError):
                        val = traceback.format_exc()
                        print(val)
                        fd.close()
                        read_fd_list.remove(fd)

        except Exception:
            val = traceback.format_exc()
            print(val)
            break  # 有異常退出循環

    server.close()

if __name__ == '__main__':
    StartTcpServer('127.0.0.1', 8000)

04_TcpClient.py:

# -*- coding: utf-8 -*-

import socket
import  time
import traceback

def StartTcpClient(ip_port,nums):
    client_list = [socket.socket() for i in range(nums)]
    for client in client_list:
        print(client)
        client.connect(ip_port)

    while True:
        try:
            for index,client in enumerate(client_list):
                try:
                    client.sendall(str( int(time.time()) ).encode('utf-8'))
                    # client.send(str( int(time.time()) ).encode('utf-8'))
                    print(f'clirnt[{index}],等待服務端的數據:')

                    server_data = client.recv(1024)
                    # print(f'來自服務端的數據:{server_data}')
                    server_data = server_data.decode('utf-8')
                    stru_t = time.localtime(float(server_data))
                    strTime = time.strftime('%Y-%m-%d %H:%M:%S', stru_t)
                    print(f'來自服務端的數據:{server_data} 轉換之後:{strTime}')
                    # time.sleep(1)

                except (ConnectionResetError, ConnectionAbortedError, ConnectionRefusedError):
                    val = traceback.format_exc()
                    print(val)
                    client.close()
                    client_list.remove(client)

                    if len(client_list) < 1:
                        raise

                except Exception:
                    val = traceback.format_exc()
                    print(val)

        except Exception:
            val = traceback.format_exc()
            print(val)
            print('退出')
            break

if __name__ == '__main__':
    StartTcpClient(('127.0.0.1',8000),1)
    # StartTcpClient(('127.0.0.1',8000),2000)  

4:socket實現web服務器

4.1:簡單web服務器

在瀏覽器中訪問  http://127.0.0.1:8000/home 等

# -*- coding: utf-8 -*-
import socket

def StartWebServer(ip,port):
    # 創建socket對象
    sk = socket.socket()

    # 綁定IP和端口
    sk.bind((ip,port))

    # 監聽
    sk.listen()

    while True:
        # 等待連接
        conn, addr = sk.accept()
        # 接收數據
        data = conn.recv(8096)
        print(data)

        # 返回狀態行
        conn.send(b'HTTP/1.1 200 OK\r\n\r\n')

        # 返回數據
        try:
            data = data.decode('utf-8')
            url = data.split()[1]
            ret_data = url
            if url == "/test":
                ret_data = "test"
            elif url == "/home":
                ret_data = "home"
            elif url == "/index":
                ret_data = "index"
            else:
                ret_data = "404"
            conn.send(f'<h1>{ret_data}</h1>'.encode('utf-8'))
        except Exception:
            pass

        # 關閉連接
        conn.close()


if __name__ == '__main__':
    StartWebServer('127.0.0.1', 8000)

4.2:簡單web服務器:函數版本

# -*- coding: utf-8 -*-
import socket
import time

def test(url):
    ret = f'test: {url}'
    return ret.encode('utf-8')

def index(url):
    ret = f'index: {url}'
    return ret.encode('utf-8')

def home(url):
    ret = f'home: {url}'
    return ret.encode('utf-8')

def gettime(url):
    now = time.strftime('%Y-%m-%d %H:%M:%S')
    ret = f'time: {url} {now}'
    return ret.encode('utf-8')

url_map = {'/test':test,'/index':index,'/home':home,'/time':gettime}


def handle_client_data(data):
    data = data.decode('utf-8')
    url = data.split()[1]

    ret_data = url
    if url in url_map:
        func = url_map[url]
        ret_data = func(url)
    else:
        ret_data = b"404"
    return ret_data

def StartWebServer(ip,port):
    # 創建socket對象
    sk = socket.socket()

    # 綁定IP和端口
    sk.bind((ip,port))

    # 監聽
    sk.listen()

    while True:
        # 等待連接
        conn, addr = sk.accept()
        # 接收數據
        data = conn.recv(8096)
        print(data)

        # 返回狀態行
        conn.send(b'HTTP/1.1 200 OK\r\n\r\n')

        # 返回數據
        try:
            ret_data = handle_client_data(data)
            conn.send(ret_data)
        except Exception:
            pass

        # 關閉連接
        conn.close()

if __name__ == '__main__':
    StartWebServer('127.0.0.1', 8000)

在瀏覽器中訪問:http://127.0.0.1:8000/time

4.3:簡單web服務器:返回動態頁面

# -*- coding: utf-8 -*-
import socket
import time

def test(url):
    ret = f'test: {url}'
    return ret.encode('utf-8')

def index(url):
    with open('index.html', 'rb') as f:
        ret = f.read()
        return ret

def home(url):
    ret = f'home: {url}'
    return ret.encode('utf-8')

def gettime(url):
    now = time.strftime('%Y-%m-%d %H:%M:%S')
    with open('time.html', 'r', encoding='utf-8') as f:
        data = f.read()
        data = data.replace('##time##', now)
        return data.encode('utf-8')

url_map = {'/test':test,'/index':index,'/home':home,'/time':gettime}


def handle_client_data(data):
    data = data.decode('utf-8')
    url = data.split()[1]

    ret_data = url
    if url in url_map:
        func = url_map[url]
        ret_data = func(url)
    else:
        ret_data = b"404"
    return ret_data

def StartWebServer(ip,port):
    # 創建socket對象
    sk = socket.socket()

    # 綁定IP和端口
    sk.bind((ip,port))

    # 監聽
    sk.listen()

    while True:
        # 等待連接
        conn, addr = sk.accept()
        # 接收數據
        data = conn.recv(8096)
        print(data)

        # 返回狀態行
        conn.send(b'HTTP/1.1 200 OK\r\n\r\n')

        # 返回數據
        try:
            ret_data = handle_client_data(data)
            conn.send(ret_data)
        except Exception:
            pass

        # 關閉連接
        conn.close()

if __name__ == '__main__':
    StartWebServer('127.0.0.1', 8000)

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
<div>
    <h1>index</h1>
	<h2>index</h2>
	<h3>index</h3>
	<h4>index</h4>
</div>
</body>
</html>

time.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>time</title>
</head>
<body>
<h1>當前時間是: ##time## </h1>
</body>
</html>

4.4:併發web服務器

使用socketserver實現:

# -*- coding: utf-8 -*-

import socketserver
import time
import traceback  # 打印異常信息


def test(url):
    ret = f'test: {url}'
    return ret.encode('utf-8')

def index(url):
    with open('index.html', 'rb') as f:
        ret = f.read()
        return ret

def home(url):
    ret = f'home: {url}'
    return ret.encode('utf-8')

def gettime(url):
    now = time.strftime('%Y-%m-%d %H:%M:%S')
    with open('time.html', 'r', encoding='utf-8') as f:
        data = f.read()
        data = data.replace('##time##', now)
        return data.encode('utf-8')

url_map = {'/test':test,'/index':index,'/home':home,'/time':gettime}


def handle_client_data(data):
    data = data.decode('utf-8')
    url = data.split()[1]

    ret_data = url
    if url in url_map:
        func = url_map[url]
        ret_data = func(url)
    else:
        ret_data = b"404"
    return ret_data



# 每個鏈接都使用一個 TcpServer 實例對象來處理,並且會自動調用handle方法,退出handle方法鏈接就會自動斷開。
class TcpServer(socketserver.BaseRequestHandler):
    """
    必須繼承socketserver.BaseRequestHandler類
    """
    def handle(self):
        """
        必須實現這個方法!
        :return:
        """
        conn = self.request         # request裏封裝了所有請求的數據
        # conn.sendall(str(time.time()).encode('utf-8'))
        # print(f"1111:{self.client_address}")    # 打日誌驗證每個鏈接是不是都會進來一次
        while True:
            try:
                data = conn.recv(8096)
                print(data)
                conn.send(b'HTTP/1.1 200 OK\r\n\r\n')

                # 返回數據
                try:
                    ret_data = handle_client_data(data)
                    conn.sendall(ret_data)
                except Exception:
                    pass

                break
            except Exception:
                val = traceback.format_exc()
                print(val)
                break
        print(f'退出客戶端{self.client_address}的處理。')

def StartTcpServer(ip,port):
    # 創建一個多線程TCP服務器
    server = socketserver.ThreadingTCPServer((ip,port), TcpServer)
    print("啓動socketserver服務器!")
    # 啓動服務器,服務器將一直保持運行狀態
    server.serve_forever()

if __name__ == '__main__':
    StartTcpServer('127.0.0.1', 8000)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章