python之路 socket、socket server


一、socket

 

socket的英文原義是“孔”或“插座”。作為BSD UNIX的進程通信機制,取后一種意思。通常也
稱作"套接字",用於描述IP地址和端口,是一個通信鏈的句柄,可以用來實現不同虛擬機或不同計算機之間的通信。在Internet上的主機一 般運行了多個服務軟件,同時提供幾種服務。每種服務都打開一個Socket,並綁定到一個端口上,不同的端口對應於不同的服務。Socket正如其英文原 意那樣,像一個多孔插座。一台主機猶如布滿各種插座的房間,每個插座有一個編號,有的插座提供220伏交流電, 有的提供110伏交流電,有的則提供有線電視節目。 客戶軟件將插頭插到不同編號的插座,就可以得到不同的服務

2、連接原理
根據連接啟動的方式以及本地套接字要連接的目標,套接字之間的連接過程可以分為三個步驟:服務器監聽,客戶端請求,連接確認。

(1)服務器監聽:是服務器端套接字並不定位具體的客戶端套接字,而是處於等待連接的狀態,實時監控網絡狀態。

(2)客戶端請求:是指由客戶端的套接字提出連接請求,要連接的目標是服務器端的套接字。為此,客戶端的套接字必須首先描述它要連接的服務器的套接字,指出服務器端套接字的地址和端口號,然后就向服務器端套接字提出連接請求。

(3)連接確認:是指當服務器端套接字監聽到或者說接收到客戶端套接字的連接請求,它就響應客戶端套接字的請求,建立一個新的線程,把服務器端套接 字的描述發給客戶端,一旦客戶端確認了此描述,連接就建立好了。而服務器端套接字繼續處於監聽狀態,繼續接收其他客戶端套接字的連接請求。

案例

1、最簡單的web服務器

#!/usr/bin/env python
#coding:utf-8
#導入socket模塊
import socket  
#開啟ip和端口
ip_port = ('127.0.0.1',8080)
#生成句柄
web = socket.socket()
#綁定端口
web.bind(ip_port)
#最多連接數
web.listen(5)
#等待信息
print ('nginx waiting...')
#開啟死循環
while True:
    #阻塞
    conn,addr = web.accept()
    #獲取客戶端請求數據
    data = conn.recv(1024)
    #打印接受數據 注:當瀏覽器訪問的時候,接受的數據的瀏覽器的信息等。
    print(data)
    #向對方發送數據
    conn.send(bytes('<h1>welcome nginx</h1>','utf8'))
    #關閉鏈接    
    conn.close()

2、簡單的聊天工具

(1)service端

#!/usr/bin/env python    
#coding:utf-8
 import socket
#開啟ip和端口
ip_port = ('127.0.0.1',9999)
#生成一個句柄
sk = socket.socket()
#綁定ip端口
sk.bind(ip_port)
#最多連接數
sk.listen(5)
#開啟死循環
while True:

    print ('server waiting...')
    #等待鏈接,阻塞,直到渠道鏈接 conn打開一個新的對象 專門給當前鏈接的客戶端 addr是ip地址
    conn,addr = sk.accept()
    #獲取客戶端請求數據
    client_data = conn.recv(1024)
    #打印對方的數據
    print (str(client_data,'utf8'))
    #向對方發送數據
    conn.sendall(bytes('不要回答,不要回答,不要回答','utf8'))
    #關閉鏈接
    conn.close()

 (2)client端

#!/usr/bin/env python
#coding:utf-8
import socket
#鏈接服務端ip和端口
ip_port = ('127.0.0.1',9999)
#生成一個句柄
sk = socket.socket()
#請求連接服務端
sk.connect(ip_port)
#發送數據
sk.sendall(bytes('yaoyao','utf8'))
#接受數據
server_reply = sk.recv(1024)
#打印接受的數據
print (str(server_reply,'utf8'))
#關閉連接
sk.close()

 3、更多功能

更多功能
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)
參數一:地址簇
    socket.AF_INET IPv4(默認)
    socket.AF_INET6 IPv6
    socket.AF_UNIX 只能夠用於單一的Unix系統進程間通信
參數二:類型
    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_RDM 是一種可靠的UDP形式,即保證交付數據報但不保證順序。
    SOCK_RAM用來提供對原始協議的低級訪問,在需要執行某些特殊操作時使用,
    如發送ICMP報文。SOCK_RAM通常僅限於高級用戶或管理員運行的程序使用。
  socket.SOCK_SEQPACKET 可靠的連續數據包服務
參數三:協議
    0  (默認)與特定的地址家族相關的協議,如果是 0 ,
    則系統就會根據地址格式和套接類別,自動選擇一個合適的協議

sk.bind(address)
s.bind(address) 將套接字綁定到地址。address地址的格式取決於地址族。
在AF_INET下,以元組(host,port)的形式表示地址。

sk.listen(backlog)
開始監聽傳入連接。backlog指定在拒絕連接之前,可以掛起的最大連接數量。
backlog等於5,表示內核已經接到了連接請求,但服務器還沒有調用accept進行處理的
連接個數最大為5,這個值不能無限大,因為要在內核中維護連接隊列

sk.setblocking(bool)
是否阻塞(默認True),如果設置False,那么accept和recv時一旦無數據,則報錯。

sk.accept()
接受連接並返回(conn,address),其中conn是新的套接字對象,可以用來接收
和發送數據。address是連接客戶端的地址。接收TCP 客戶的連接(阻塞式)等待連接的到來

sk.connect(address)
連接到address處的套接字。一般,address的格式為元組(hostname,port),
如果連接出錯,返回socket.error錯誤。

sk.connect_ex(address)
同上,只不過會有返回值,連接成功時返回 0 ,連接失敗時候返回編碼,例如:10061

sk.close()
關閉套接字

sk.recv(bufsize[,flag])
接受套接字的數據。數據以字符串形式返回,bufsize指定最多可以接收的數量。
flag提供有關消息的其他信息,通常可以忽略。

sk.recvfrom(bufsize[.flag])
與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字符串,
address是發送數據的套接字地址。

sk.send(string[,flag])
將string中的數據發送到連接的套接字。返回值是要發送的字節數量,
該數量可能小於string的字節大小。即:可能未將指定內容全部發送。

sk.sendall(string[,flag])
將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。
成功返回None,失敗則拋出異常。
內部通過遞歸調用send,將所有內容發送出去。

sk.sendto(string[,flag],address)
將數據發送到套接字,address是形式為(ipaddr,port)的元組,指定遠程地址。
返回值是發送的字節數。該函數主要用於UDP協議。

sk.settimeout(timeout)
設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。
一般,超時期應該在剛創建套接字時設置,因為它們可能用於連接的操作
(如 client 連接最多等待5s )

sk.getpeername()
返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。

sk.getsockname()
返回套接字自己的地址。通常是一個元組(ipaddr,port)

sk.fileno()
套接字的文件描述符

 二、socket server

SocketServer內部使用 IO多路復用 以及 “多線程” 和 “多進程” ,從而實現並發處理多個客戶端請求的Socket服務端。即:每個客戶端請求連接到服務器時,Socket服務端都會在服務器是創建一個“線程”或者“進 程” 專門負責處理當前客戶端的所有請求。

注:導入模塊的時候 3.x版本是socketserver 2.x版本是SocketServer

1.ThreadingTCPServer

ThreadingTCPServer實現的Soket服務器內部會為每個client創建一個 “線程”,該線程用來和客戶端進行交互。

  1. ThreadingTCPServer基礎
    使用ThreadingTCPServer:

創建一個繼承自 SocketServer.BaseRequestHandler 的類
類中必須定義一個名稱為 handle 的方法
啟動ThreadingTCPServer

服務端

import SocketServer
class MyServer(SocketServer.BaseRequestHandler):
def handle(self):
    conn = self.request
    conn.sendall('我是多線程')
    Flag = True
    while Flag:
        data = conn.recv(1024)
        if data == 'exit':
            Flag = False
        elif data == '0':
            conn.sendall('您輸入的是0')
        else:
            conn.sendall('請重新輸入.')
if __name__ == '__main__':
server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
server.serve_forever()

 客戶端

 #!/usr/bin/env python
 # -*- coding:utf-8 -*-
 import socket
 ip_port = ('127.0.0.1',8009)
 sk = socket.socket()
 sk.connect(ip_port)
 while True:
     data = sk.recv(1024)
     print 'receive:',data
     inp = input('please input:')
     sk.sendall(inp)
     if inp == 'exit':
     break
 sk.close()

 

內部調用流程為:

  • 啟動服務端程序
  • 執行 TCPServer.init 方法,創建服務端Socket對象並綁定 IP 和 端口
  • 執行 BaseServer.init 方法,將自定義的繼承自SocketServer.BaseRequestHandler 的類 - MyRequestHandle賦值給 self.RequestHandlerClass
  • 執行 BaseServer.server_forever 方法,While 循環一直監聽是否有客戶端請求到達 ...
    當客戶端連接到達服務器
  • 執行 ThreadingMixIn.process_request 方法,創建一個 “線程” 用來處理請求
  • 執行 ThreadingMixIn.process_request_thread 方法
  • 執行 BaseServer.finish_request 方法,執行 self.RequestHandlerClass() 即:執行 自定義 MyRequestHandler 的構造方法(自動調用基類BaseRequestHandler的構造方法,在該構造方法中又會調用 MyRequestHandler的handle方法)
ForkingTCPServer

ForkingTCPServer和ThreadingTCPServer的使用和執行流程基本一致,只不過在內部分別為請求者建立 “線程” 和 “進程”。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM