Socket網路程式設計

2020-08-12 21:53:10

前言

ISO七層模型

參考:ISO七層模型

這裏,我們着重瞭解下TCP,UDP協定

UDP&TCP

UDP:User Datagram Protocol用戶數據報協定
特點:
1.面向無連線:傳輸數據之前源端和目的端不需要建立連線。
2.每個數據報的大小都限制在64K(8個位元組)以內。
3.面向報文的不可靠協定。(即:發送出去的數據不一定會接收得到)
4.傳輸速率快,效率高。
5.現實生活範例:郵局寄件、實時線上聊天、視訊會議…等。

TCP:Transmission Control Protocol傳輸控制協定

特點:
1.面向連接:傳輸數據之前需要建立連線。
2.在連線過程中進行大量數據傳輸。
3.通過「三次握手」的方式完成連線,是安全可靠協定。
4.傳輸速度慢,效率低。

注意
在TCP/IP協定中,TCP協定通過三次握手建立一個可靠的連線

C:我喜歡你
S:我也喜歡你
C:那我們談物件吧

TCP/IP四層協定

TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協定/網間協定,定義了主機如何連入因特網及數據如何再它們之間傳輸的標準,

從字面意思來看TCP/IP是TCP和IP協定的合稱,但實際上TCP/IP協定是指因特網整個TCP/IP協定族。不同於ISO模型的七個分層,TCP/IP協定參考模型把所有的TCP/IP系列協定歸類到四個抽象層中

應用層:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等
傳輸層:TCP,UDP
網路層:IP,ICMP,OSPF,EIGRP,IGMP
數據鏈路層:SLIP,CSLIP,PPP,MTU

每一抽象層建立在低一層提供的服務上,並且爲高一層提供服務,看起來大概是這樣子的

我們可以利用ip地址+協定+埠號唯一標示網路中的一個進程。能夠唯一標示網路中的進程後,它們就可以利用socket進行通訊了,我們經常把socket翻譯爲通訊端,socket是在應用層和傳輸層(TCP/IP協定族通訊)之間的一個抽象層,是一組介面,它把TCP/IP層複雜的操作抽象爲幾個簡單的介面供應用層呼叫已實現進程在網路中通訊。

應用程式兩端通過「通訊端」向網路發出請求或者應答網路請求。可以把socket理解爲通訊的把手(hand)

socket起源於UNIX,在Unix一切皆檔案哲學的思想下,socket是一種"開啓—讀/寫—關閉"模式的實現,伺服器和用戶端各自維護一個"檔案",在建立連線開啓後,可以向自己檔案寫入內容供對方讀取或者讀取對方內容,通訊結束時關閉檔案。socket的英文原義是「插槽」或「插座」,就像我們家裏座機一樣,如果沒有網線的那個插口,電話是無法通訊的。Socket是實現TCP,UDP協定的介面,便於使用TCP,UDP。

socket

socket通訊流程

在这里插入图片描述
流程描述

# 
# 1 伺服器根據地址型別(ipv4,ipv6)、socket型別、協定建立socket
# 
# 2 伺服器爲socket系結ip地址和埠號
# 
# 3 伺服器socket監聽埠號請求,隨時準備接收用戶端發來的連線,這時候伺服器的socket並沒有被開啓
# 
# 4 用戶端建立socket
# 
# 5 用戶端開啓socket,根據伺服器ip地址和埠號試圖連線伺服器socket
# 
# 6 伺服器socket接收到用戶端socket請求,被動開啓,開始接收用戶端請求,直到用戶端返回連線資訊。這時候socket進入阻塞狀態,所謂阻塞即accept()方法一直等到用戶端返回連線資訊後才返回,開始接收下一個用戶端連線請求
# 
# 7 用戶端連線成功,向伺服器發送連線狀態資訊
# 
# 8 伺服器accept方法返回,連線成功
# 
# 9 用戶端向socket寫入資訊(或伺服器端向socket寫入資訊)
# 
# 10 伺服器讀取資訊(用戶端讀取資訊)
# 
# 11 用戶端關閉
# 
# 12 伺服器端關閉

python庫 socket方法介紹

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()

  #通訊端的檔案描述符

程式碼

1.基礎樣例

#################server
import socket
ip_port = ('127.0.0.1',9997)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(5)

print ('server waiting...')

conn,addr = sk.accept()
client_data = conn.recv(1024)
print (str(client_data,"utf8"))
conn.sendall(bytes('滾蛋!',encoding="utf-8"))

sk.close()

################client
import socket
ip_port = ('127.0.0.1',9997)

sk = socket.socket()
sk.connect(ip_port)


sk.sendall(bytes('俺喜歡你',encoding="utf8"))

server_reply = sk.recv(1024)
print (str(server_reply,"utf8"))

2.不間斷聊天

#-------------------------------------------------server.py
#-------------------------------------------------
import socket
ip_port = ('127.0.0.1',8888)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(2)
print ("伺服器端啓動...")
conn,address = sk.accept()
while True:
    client_data=conn.recv(1024)
    if str(client_data,"utf8")=='exit':
        break
    print (str(client_data,"utf8"))
    server_response=input(">>>")
    conn.sendall(bytes(server_response,"utf8"))

conn.close()

#-------------------------------------------------client.py
#-------------------------------------------------

import socket
ip_port = ('127.0.0.1',8888)
sk = socket.socket()
sk.connect(ip_port)
print ("用戶端啓動:")

while True:
    inp = input('>>>')
    sk.sendall(bytes(inp,"utf8"))
    if inp == 'exit':
        break
    server_response=sk.recv(1024)
    print (str(server_response,"utf8"))
sk.close()

3.多個用戶端同時聊天

import socket

ip_port = ('127.0.0.1',8870)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(2)
print ("伺服器端啓動...")

while True:
    conn,address = sk.accept()
    print(address)
    while True:
        try:
            client_data=conn.recv(1024)
        except:
            print("意外中斷")
            break
        print (str(client_data,"utf8"))

        server_response=input(">>>")
        conn.sendall(bytes(server_response,"utf8"))

    conn.close()
    
##################
import socket
ip_port = ('127.0.0.1',8870)
sk = socket.socket()
sk.connect(ip_port)
print ("用戶端啓動:")

while True:
    inp = input('>>>')
    if inp == 'exit':
        break
    sk.sendall(bytes(inp,"utf8"))
    server_response=sk.recv(1024)
    print (str(server_response,"utf8"))
sk.close()

4.簡單併發

#-----------------------------------------------------server.py
#-----------------------------------------------------
import socketserver

class MyServer(socketserver.BaseRequestHandler):

    def handle(self):
        print ("伺服器端啓動...")
        while True:
            conn = self.request
            print (self.client_address)
            while True:
                client_data=conn.recv(1024)
                print (str(client_data,"utf8"))
                print ("waiting...")
                conn.sendall(client_data)
            conn.close()

if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8091),MyServer)
    server.serve_forever()

#-----------------------------------------------------client.py
#-----------------------------------------------------
import socket

ip_port = ('127.0.0.1',8091)
sk = socket.socket()
sk.connect(ip_port)
print ("用戶端啓動:")
while True:
    inp = input('>>>')
    sk.sendall(bytes(inp,"utf8"))
    if inp == 'exit':
        break
    server_response=sk.recv(1024)
    print (str(server_response,"utf8"))
sk.close()

5.併發聊天範例

import socketserver

class MyServer(socketserver.BaseRequestHandler):

    def handle(self):
        print ("伺服器端啓動...")
        while True:
            conn = self.request
            print (self.client_address)
            while True:

                client_data=conn.recv(1024)

                print (str(client_data,"utf8"))
                print ("waiting...")
                server_response=input(">>>")
                conn.sendall(bytes(server_response,"utf8"))
                # conn.sendall(client_data)

            conn.close()
            # print self.request,self.client_address,self.server


if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8098),MyServer)
    server.serve_forever()


##########################################
import socket


ip_port = ('127.0.0.1',8098)
sk = socket.socket()
sk.connect(ip_port)
print ("用戶端啓動:")
while True:
    inp = input('>>>')
    sk.sendall(bytes(inp,"utf8"))
    server_response=sk.recv(1024)
    print (str(server_response,"utf8"))
    if inp == 'exit':
        break
sk.close()

其他應用

1.命令傳送

#------------------------------------------------server
#------------------------------------------------
import socket
import subprocess
ip_port = ('127.0.0.1',8879)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(5)
print ("伺服器端啓動...")
while True:
    conn,address = sk.accept()
    while True:
        try:

            client_data=conn.recv(1024)
        except Exception:
            break
        print (str(client_data,"utf8"))
        print ("waiting...")
        # server_response=input(">>>")
        # conn.sendall(bytes(server_response,"utf8"))
        cmd=str(client_data,"utf8").strip()
        cmd_call=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE)
        cmd_result=cmd_call.stdout.read()
        if len(cmd_result)==0:
            cmd_result=b"no output!"
        conn.sendall(cmd_result)
        print('send data size',len(cmd_result))
        print('******************')
        print('******************')
        print('******************')

    conn.close()
    
#------------------------------------------------client 
#------------------------------------------------
import socket
ip_port = ('127.0.0.1',8879)
sk = socket.socket()
sk.connect(ip_port)
print ("用戶端啓動:")
while True:
    inp = input('cdm:>>>').strip( )
    if len(inp)==0:
        continue
    if inp=="q":
        break
    sk.sendall(bytes(inp,"utf8"))
    server_response=sk.recv(1024)
    print (str(server_response,"gbk"))
    print('receive data size',len(server_response))
    if inp == 'exit':
        break
sk.close()

命令傳送2:解決大數據傳送和粘包問題

import socketserver
import subprocess

class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            conn=self.request
            conn.sendall(bytes("歡迎登錄","utf8"))
            while True:
                client_bytes=conn.recv(1024)
                if not client_bytes:break
                client_str=str(client_bytes,"utf8")
                print(client_str)
                command=client_str

                result_str=subprocess.getoutput(command)
                result_bytes = bytes(result_str,encoding='utf8')
                info_str="info|%d"%len(result_bytes)
                conn.sendall(bytes(info_str,"utf8"))
                # conn.recv(1024)
                conn.sendall(result_bytes)
            conn.close()

if __name__=="__main__":
    server=socketserver.ThreadingTCPServer(("127.0.0.1",9998),Myserver)
    server.serve_forever()

#####################################client


import socket
ip_port=("127.0.0.1",9998)

sk=socket.socket()
sk.connect(ip_port)
print("用戶端啓動...")

print(str(sk.recv(1024),"utf8"))

while True:
    inp=input("please input:").strip()


    sk.sendall(bytes(inp,"utf8"))
    basic_info_bytes=sk.recv(1024)
    print(str(basic_info_bytes,"utf8"))
    # sk.send(bytes('ok','utf8'))
    result_length=int(str(basic_info_bytes,"utf8").split("|")[1])

    print(result_length)
    has_received=0
    content_bytes=bytes()
    while has_received<result_length:
        fetch_bytes=sk.recv(1024)
        has_received+=len(fetch_bytes)
        content_bytes+=fetch_bytes
    cmd_result=str(content_bytes,"utf8")
    print(cmd_result)

sk.close()

檔案上傳

import socket,os
ip_port=("127.0.0.1",8898)
sk=socket.socket()
sk.bind(ip_port)
sk.listen(5)
BASE_DIR=os.path.dirname(os.path.abspath(__file__))

while True:
    print("waiting connect")
    conn,addr=sk.accept()
    flag = True
    while flag:

            client_bytes=conn.recv(1024)
            client_str=str(client_bytes,"utf8")
            func,file_byte_size,filename=client_str.split("|",2)

            path=os.path.join(BASE_DIR,'yuan',filename)
            has_received=0
            file_byte_size=int(file_byte_size)

            f=open(path,"wb")
            while has_received<file_byte_size:
                data=conn.recv(1024)
                f.write(data)
                has_received+=len(data)
            print("ending")
            f.close()

#----------------------------------------------client
#----------------------------------------------
import socket
import re,os,sys
ip_port=("127.0.0.1",8898)
sk=socket.socket()
sk.connect(ip_port)
BASE_DIR=os.path.dirname(os.path.abspath(__file__))
print("用戶端啓動....")

while True:
    inp=input("please input:")

    if inp.startswith("post"):
        method,local_path=inp.split("|",1)
        local_path=os.path.join(BASE_DIR,local_path)
        file_byte_size=os.stat(local_path).st_size
        file_name=os.path.basename(local_path)
        post_info="post|%s|%s"%(file_byte_size,file_name)
        sk.sendall(bytes(post_info,"utf8"))
        has_sent=0
        file_obj=open(local_path,"rb")
        while has_sent<file_byte_size:
            data=file_obj.read(1024)
            sk.sendall(data)
            has_sent+=len(data)
        file_obj.close()
        print("上傳成功")

原部落格:
Yuan先生