1、 socket概念python
一、socket層:
socket在傳輸層和應用層之間,就是封裝了一堆TCP/UDP協議的簡單接口編程
二、socket是什麼
socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口,在設計模式中,socket其實就是一個門面模式,他把複雜的TCP/IP協議族隱藏在socket接口後面,對用戶來講,一組簡單的接口就是所有,讓socket去組織數據,以符合指定的協議。小程序
因此,咱們無需深刻理解tcp/udp協議,socket已經爲咱們封裝好了,咱們只須要遵循socket的規定去編程,寫出程序天然就是遵循tcp/udp標準的。設計模式
小知識:服務器
其實站在你的角度上看,socket就是一個模塊。咱們經過調用模塊中已經實現的方法創建兩個進程之間的鏈接和通訊。
也有人將socket說成ip+port,由於ip是用來標識互聯網中的一臺主機的位置,而port是用來標識這臺機器上的一個應用程序。
因此咱們只要確立了ip和port就能找到一個應用程序,而且使用socket模塊來與之通訊。網絡
套接字發展史及分類socket
套接字起源於 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。 所以,有時人們也把套接字稱爲「伯克利套接字」或「BSD 套接字」。一開始,套接字被設計用在同 一臺主機上多個應用程序之間的通信。這也被稱進程間通信,或 IPC。套接字有兩種(或者稱爲有兩個種族),分別是基於文件型的和基於網絡型的。tcp
基於文件類型的套接字家族工具
套接字家族的名字:AF_UNIXspa
unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,能夠經過訪問同一個文件系統間接完成通訊
基於網絡類型的套接字家族
套接字家族的名字:AF_INET
(還有AF_INET6被用於ipv6,還有一些其餘的地址家族,不過,他們要麼是隻用於某個平臺,要麼就是已經被廢棄,或者是不多被使用,或者是根本沒有實現,全部地址家族中,AF_INET是使用最普遍的一個,python支持不少種地址家族,可是因爲咱們只關心網絡編程,因此大部分時候我麼只使用AF_INET)
三、套接字流程
一個生活中的場景,你要打電話給一個朋友,先撥號,朋友聽到電話鈴聲後提起電話,這是你和你的朋友就創建起了鏈接,就能夠講話了。等交流結束,掛斷電話結束這次交談。生活中的場景就解釋了這工做原理,也許TCP/IP協議族就是誕生於生活中,這也不必定。
具體:
先從服務器端提及,服務器端先初始化Socket,而後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端鏈接。在這時若是有個客戶端初始化一個Socket,而後鏈接服務器(connet),若是鏈接成功,這時客戶端與服務器端的鏈接就創建了。客戶端發送數據請求,服務器端接收請求並處理,而後把迴應數據發送給客戶端,客戶端讀取數據,最後關閉鏈接,一次交互結束。代碼以下:
1、基於TCP協議的socket
注 : tcp是基於連接的 ,啓動時應該先啓動 server端 ,再啓動 client端。
sever端:
import socket
sk = socket.socket() # 建立了一個socket對象
sk.bind(('192.168.21.36',8080)) # 綁定一臺機器的(ip,端口)
# 迴環地址 - 指向本身這臺機器
sk.listen() # 創建監聽等待別人鏈接
conn,addr = sk.accept() # 阻塞:在這裏等待直到接到一個鏈接
# conn是鏈接
# addr是對方的地址
print(conn)
print(addr)
conn.send(b'hello') # 和對方打招呼
msg = conn.recv(1024) # 對方和我說的話
# 有發必有收 收發必相等
print(msg)
conn.close() # 掛電話
sk.close() # 關機
client 端
import socket
sk = socket.socket() # 買個手機
sk.connect(('127.0.0.1',8080)) # 撥號
ret = sk.recv(1024)
print(ret)
sk.send(b'byebye!')
sk.close()
問題:在啓動服務端時可能會遇到 端口報錯 這時是由於此端口可能已被佔用,沒法重複使用,那該怎麼解決呢?
如何解決?
#加入一條socket配置,重用ip和端口
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
sk.bind(('127.0.0.1',8898)) #把地址綁定到套接字
sk.listen() #監聽連接
conn,addr = sk.accept() #接受客戶端連接
ret = conn.recv(1024) #接收客戶端信息
print(ret) #打印客戶端信息
conn.send(b'hi') #向客戶端發送信息
conn.close() #關閉客戶端套接字
sk.close() #關閉服務器套接字(可選)
2、基於UDP協議的socket
server端:
import socket
sk = socket.socket(type=socket.SOCK_DGRAM) # 創建一個socket對象,
# 指定以UDP協議的形式來鏈接
sk.bind(('127.0.0.1',8080))
# 指定服務的地址
msg,addr = sk.recvfrom(1024) # 接收消息,發送端的地址
print(msg,addr)
sk.sendto(b'HELLO',addr) # 給發送端回覆消息
sk.close() # 關閉socket鏈接
client端:
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.sendto(b'hello',('127.0.0.1',8080)) # 直接給服務器發送一段消息
msg,addr = sk.recvfrom(1024) # 接收對面的回信
print(msg)
sk.close()
聊天小工具
TCP server端
import socket
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('192.168.21.36',9000))
sk.listen()
while True:
conn,addr = sk.accept() # 接收鏈接 三次握手conn
while True:
inp = input('>>>')
if inp == 'q':
conn.send(inp.encode('utf-8'))
break
conn.send(inp.encode('utf-8'))
msg = conn.recv(1024)
if msg == b'q':break
print(msg.decode('utf-8'))
conn.close() # 四次揮手
sk.close()
TCP client端 :
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
while True:
msg = sk.recv(1024)
print(msg.decode('utf-8'))
if msg == b'q':break
inp = input('>>>')
if inp == 'q':
sk.send(inp.encode('utf-8'))
break
sk.send(inp.encode('utf-8'))
sk.close()
UDP server端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',9090))
while True:
msg,addr = sk.recvfrom(1024)
print('來自[%s:%s]的消息--%s'%(addr[0],addr[1],msg.decode('utf-8')))
inp = input('>>>')
sk.sendto(inp.encode('utf-8'),addr)
sk.close()
UDP CLiENT端:
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
addr = ('127.0.0.1',9090)
while True:
msg = input('>>>')
sk.sendto(msg.encode('utf-8'),addr)
msg_recv,addr = sk.recvfrom(1024)
print(msg_recv.decode('utf-8'))
sk.close()
UDPserver端
# 需求
# 寫一個時間同步的服務器
# 服務端接收請求
# 按照client端發送的時間格式,將服務器時間轉換成對應格式
# 發送給客戶端
import time
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',9000))
while True:
msg,addr = sk.recvfrom(1024)
# msg 客戶端發送給server端的時間格式 "%Y-%m-%d %H:%M-%S"
time_format = msg.decode('utf-8')
time_str = time.strftime(time_format)
sk.sendto(time_str.encode('utf-8'),addr)
sk.close()
UDPclient端
# client端每隔一段時間發送請求到服務端
# 發送時間的格式
import time
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.sendto('%Y-%m-%d %H:%M:%S'.encode('utf-8'),('127.0.0.1',9000))
msg,addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
sk.close()
# 方式一
# 操做系統的定時任務 + python代碼的形式
# 方式二
# while True + time.sleep的形式
練習題:
練習 : 完成一個上傳和下載文件的小程序
需求:
server端: 根據客戶端需求自定義 client端: 客戶端啓動以後 選擇 上傳操做 仍是 下載操做 若是是上傳操做 : 輸入要上傳的文件路徑 基礎需求 :直接將文件上傳到默認目錄 進階需求 :將文件上傳到指定目錄 若是是下載文件 : 輸入要下載的文件路徑 基礎需求 : 直接將文件下載到當前目錄 進階需求 :將文件下載到指定目錄