27 网络通信协议 udp tcp
四 網(wǎng)絡(luò)通信協(xié)議(互聯(lián)網(wǎng)協(xié)議)
第二天再講這里,大家第二天再看這里把~~~
網(wǎng)絡(luò)通信協(xié)議是網(wǎng)絡(luò)傳輸?shù)撵`魂,非常重要,協(xié)議即準(zhǔn)則,準(zhǔn)則是傳輸消息的格式要求,那么我們從電腦上發(fā)出一個消息,到底是以什么樣的消息格式發(fā)到了對方的手上呢,來看一看這里>>>,網(wǎng)絡(luò)通信協(xié)議
五 osi七層模型
互聯(lián)網(wǎng)的核心就是由一堆協(xié)議組成,協(xié)議就是標(biāo)準(zhǔn),標(biāo)準(zhǔn)就是大家都認(rèn)可的,所有人都按照這個來,這樣大家都能夠互相了解,互相深入了~~~比如全世界人通信的標(biāo)準(zhǔn)是英語
五層通信流程:
六 socket
結(jié)合上圖來看,socket在哪一層呢,我們繼續(xù)看下圖
socket在內(nèi)的五層通訊流程:
Socket又稱為套接字,它是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層,它是一組接口。在設(shè)計(jì)模式中,Socket其實(shí)就是一個門面模式,它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議。當(dāng)我們使用不同的協(xié)議進(jìn)行通信時就得使用不同的接口,還得處理不同協(xié)議的各種細(xì)節(jié),這就增加了開發(fā)的難度,軟件也不易于擴(kuò)展(就像我們開發(fā)一套公司管理系統(tǒng)一樣,報(bào)賬、會議預(yù)定、請假等功能不需要單獨(dú)寫系統(tǒng),而是一個系統(tǒng)上多個功能接口,不需要知道每個功能如何去實(shí)現(xiàn)的)。于是UNIX BSD就發(fā)明了socket這種東西,socket屏蔽了各個協(xié)議的通信細(xì)節(jié),使得程序員無需關(guān)注協(xié)議本身,直接使用socket提供的接口來進(jìn)行互聯(lián)的不同主機(jī)間的進(jìn)程的通信。這就好比操作系統(tǒng)給我們提供了使用底層硬件功能的系統(tǒng)調(diào)用,通過系統(tǒng)調(diào)用我們可以方便的使用磁盤(文件操作),使用內(nèi)存,而無需自己去進(jìn)行磁盤讀寫,內(nèi)存管理。socket其實(shí)也是一樣的東西,就是提供了tcp/ip協(xié)議的抽象,對外提供了一套接口,同過這個接口就可以統(tǒng)一、方便的使用tcp/ip協(xié)議的功能了。
其實(shí)站在你的角度上看,socket就是一個模塊。我們通過調(diào)用模塊中已經(jīng)實(shí)現(xiàn)的方法建立兩個進(jìn)程之間的連接和通信。也有人將socket說成ip+port,因?yàn)閕p是用來標(biāo)識互聯(lián)網(wǎng)中的一臺主機(jī)的位置,而port是用來標(biāo)識這臺機(jī)器上的一個應(yīng)用程序。 所以我們只要確立了ip和port就能找到一個應(yīng)用程序,并且使用socket模塊來與之通信
。
七 套接字socket的發(fā)展史及分類
套接字起源于 20 世紀(jì) 70 年代加利福尼亞大學(xué)伯克利分校版本的 Unix,即人們所說的 BSD Unix。 因此,有時人們也把套接字稱為“伯克利套接字”或“BSD 套接字”。一開始,套接字被設(shè)計(jì)用在同 一臺主機(jī)上多個應(yīng)用程序之間的通訊。這也被稱進(jìn)程間通訊,或 IPC。套接字有兩種(或者稱為有兩個種族),分別是基于文件型的和基于網(wǎng)絡(luò)型的。
基于文件類型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字調(diào)用的就是底層的文件系統(tǒng)來取數(shù)據(jù),兩個套接字進(jìn)程運(yùn)行在同一機(jī)器,可以通過訪問同一個文件系統(tǒng)間接完成通信
基于網(wǎng)絡(luò)類型的套接字家族
套接字家族的名字:AF_INET
(還有AF_INET6被用于ipv6,還有一些其他的地址家族,不過,他們要么是只用于某個平臺,要么就是已經(jīng)被廢棄,或者是很少被使用,或者是根本沒有實(shí)現(xiàn),所有地址家族中,AF_INET是使用最廣泛的一個,python支持很多種地址家族,但是由于我們只關(guān)心網(wǎng)絡(luò)編程,所以大部分時候我們只使用AF_INET)
八 基于TCP和UDP兩個協(xié)議下socket的通訊流程
1.TCP和UDP對比
TCP(Transmission Control Protocol)可靠的、面向連接的協(xié)議(eg:打電話)、傳輸效率低全雙工通信(發(fā)送緩存&接收緩存)、面向字節(jié)流。使用TCP的應(yīng)用:Web瀏覽器;文件傳輸程序。
UDP(User Datagram Protocol)不可靠的、無連接的服務(wù),傳輸效率高(發(fā)送前時延?。粚σ?、一對多、多對一、多對多、面向報(bào)文(數(shù)據(jù)包),盡最大努力服務(wù),無擁塞控制。使用UDP的應(yīng)用:域名系統(tǒng) (DNS);視頻流;IP語音(VoIP)。
直接看圖對比其中差異
繼續(xù)往下看
TCP和UDP下socket差異對比圖:
上面的圖只是讓大家感受一下TCP和UDP協(xié)議下,socket工作流程的不同,兩者之間的差異是tcp需要連接,udp不需要,有些同學(xué)是不是有些迷糊,老師,這里面的bind、listen啥的都是什么東西啊,我感覺人生是迷茫的!calm down!下面我們就分開兩者,細(xì)細(xì)學(xué)習(xí)!
2.TCP協(xié)議下的socket
來吧!先上圖!
基于TCP的socket通訊流程圖片:
雖然上圖將通訊流程中的大致描述了一下socket各個方法的作用,但是還是要總結(jié)一下通訊流程(下面一段內(nèi)容)
先從服務(wù)器端說起。服務(wù)器端先初始化Socket,然后與端口綁定(bind),對端口進(jìn)行監(jiān)聽(listen),調(diào)用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然后連接服務(wù)器(connect),如果連接成功,這時客戶端與服務(wù)器端的連接就建立了??蛻舳税l(fā)送數(shù)據(jù)請求,服務(wù)器端接收請求并處理請求,然后把回應(yīng)數(shù)據(jù)發(fā)送給客戶端,客戶端讀取數(shù)據(jù),最后關(guān)閉連接,一次交互結(jié)束
上代碼感受一下,需要創(chuàng)建兩個文件,文件名稱隨便起,為了方便看,我的兩個文件名稱為tcp_server.py(服務(wù)端)和tcp_client.py(客戶端),將下面的server端的代碼拷貝到tcp_server.py文件中,將下面client端的代碼拷貝到tcp_client.py的文件中,然后先運(yùn)行tcp_server.py文件中的代碼,再運(yùn)行tcp_client.py文件中的代碼,然后在pycharm下面的輸出窗口看一下效果。
server端代碼示例(如果比喻成打電話)
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898)) #把地址綁定到套接字
sk.listen() #監(jiān)聽鏈接
conn,addr = sk.accept() #接受客戶端鏈接
ret = conn.recv(1024) #接收客戶端信息
print(ret) #打印客戶端信息
conn.send(b'hi') #向客戶端發(fā)送信息
conn.close() #關(guān)閉客戶端套接字
sk.close() #關(guān)閉服務(wù)器套接字(可選)
tcp_server.py
View Code
client端代碼示例
import socket
sk = socket.socket() # 創(chuàng)建客戶套接字
sk.connect(('127.0.0.1',8898)) # 嘗試連接服務(wù)器
sk.send(b'hello!')
ret = sk.recv(1024) # 對話(發(fā)送/接收)
print(ret)
sk.close() # 關(guān)閉客戶套接字
tcp_client.py
View Code
socket綁定IP和端口時可能出現(xiàn)下面的問題:
解決辦法:
#加入一條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() #監(jiān)聽鏈接
conn,addr = sk.accept() #接受客戶端鏈接
ret = conn.recv(1024) #接收客戶端信息
print(ret) #打印客戶端信息
conn.send(b'hi') #向客戶端發(fā)送信息
conn.close() #關(guān)閉客戶端套接字
sk.close() #關(guān)閉服務(wù)器套接字(可選)
解決辦法
View Code
但是如果你加上了上面的代碼之后還是出現(xiàn)這個問題:OSError: [WinError 10013] 以一種訪問權(quán)限不允許的方式做了一個訪問套接字的嘗試。那么只能換端口了,因?yàn)槟愕碾娔X不支持端口重用。
記住一點(diǎn),用socket進(jìn)行通信,必須是一收一發(fā)對應(yīng)好。
關(guān)于setsockopt可以看這篇文章。關(guān)于setsockopt的使用
提一下:網(wǎng)絡(luò)相關(guān)或者需要和電腦上其他程序通信的程序才需要開一個端口。
在看UDP協(xié)議下的socket之前,我們還需要加一些內(nèi)容來講:看代碼
server端
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()
# sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8090))
sk.listen()
conn,addr = sk.accept() #在這阻塞,等待客戶端過來連接
while True:
ret = conn.recv(1024) #接收消息 在這還是要阻塞,等待收消息
ret = ret.decode('utf-8') #字節(jié)類型轉(zhuǎn)換為字符串中文
print(ret)
if ret == 'bye': #如果接到的消息為bye,退出
break
msg = input('服務(wù)端>>') #服務(wù)端發(fā)消息
conn.send(msg.encode('utf-8'))
if msg == 'bye':
break
conn.close()
sk.close()
只能與第一個客戶端通信server端代碼
View Code
client端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8090)) #連接服務(wù)端
while True:
msg = input('客戶端>>>') #input阻塞,等待輸入內(nèi)容
sk.send(msg.encode('utf-8'))
if msg == 'bye':
break
ret = sk.recv(1024)
ret = ret.decode('utf-8')
print(ret)
if ret == 'bye':
break
sk.close()
只能與第一個客戶端通信client端代碼
View Code
你會發(fā)現(xiàn),第一個連接的客戶端可以和服務(wù)端收發(fā)消息,但是第二個連接的客戶端發(fā)消息服務(wù)端是收不到的
原因解釋:
tcp屬于長連接,長連接就是一直占用著這個鏈接,這個連接的端口被占用了,第二個客戶端過來連接的時候,他是可以連接的,但是處于一個占線的狀態(tài),就只能等著去跟服務(wù)端建立連接,除非一個客戶端斷開了(優(yōu)雅的斷開可以,如果是強(qiáng)制斷開就會報(bào)錯,因?yàn)榉?wù)端的程序還在第一個循環(huán)里面),然后就可以進(jìn)行和服務(wù)端的通信了。什么是優(yōu)雅的斷開呢?看代碼。
server端代碼:
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()
# sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #允許地址重用,這個東西都說能解決問題,我非常不建議大家這么做,容易出問題
sk.bind(('127.0.0.1',8090))
sk.listen()
# 第二步演示,再加一層while循環(huán)
while True: #下面的代碼全部縮進(jìn)進(jìn)去,也就是循環(huán)建立連接,但是不管怎么聊,只能和一個聊,也就是另外一個優(yōu)雅的斷了之后才能和另外一個聊
#它不能同時和好多人聊,還是長連接的原因,一直占用著這個端口的連接,udp是可以的,然后我們學(xué)習(xí)udp
conn,addr = sk.accept() #在這阻塞,等待客戶端過來連接
while True:
ret = conn.recv(1024) #接收消息 在這還是要阻塞,等待收消息
ret = ret.decode('utf-8') #字節(jié)類型轉(zhuǎn)換為字符串中文
print(ret)
if ret == 'bye': #如果接到的消息為bye,退出
break
msg = input('服務(wù)端>>') #服務(wù)端發(fā)消息
conn.send(msg.encode('utf-8'))
if msg == 'bye':
break
conn.close()
優(yōu)雅的斷開一個client端之后另一個client端就可以通信的代碼
View Code
client端代碼
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8090)) #連接服務(wù)端
while True:
msg = input('客戶端>>>') #input阻塞,等待輸入內(nèi)容
sk.send(msg.encode('utf-8'))
if msg == 'bye':
break
ret = sk.recv(1024)
ret = ret.decode('utf-8')
print(ret)
if ret == 'bye':
break
# sk.close()
client端代碼
View Code
強(qiáng)制斷開連接之后的報(bào)錯信息:
3.UDP協(xié)議下的socket
老樣子!先上圖!
基于UDP的socket通訊流程:
總結(jié)一下UDP下的socket通訊流程
先從服務(wù)器端說起。服務(wù)器端先初始化Socket,然后與端口綁定(bind),recvform接收消息,這個消息有兩項(xiàng),消息內(nèi)容和對方客戶端的地址,然后回復(fù)消息時也要帶著你收到的這個客戶端的地址,發(fā)送回去,最后關(guān)閉連接,一次交互結(jié)束
上代碼感受一下,需要創(chuàng)建兩個文件,文件名稱隨便起,為了方便看,我的兩個文件名稱為udp_server.py(服務(wù)端)和udp_client.py(客戶端),將下面的server端的代碼拷貝到udp_server.py文件中,將下面cliet端的代碼拷貝到udp_client.py的文件中,然后先運(yùn)行udp_server.py文件中的代碼,再運(yùn)行udp_client.py文件中的代碼,然后在pycharm下面的輸出窗口看一下效果。
server端代碼示例
import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM) #創(chuàng)建一個服務(wù)器的套接字
udp_sk.bind(('127.0.0.1',9000)) #綁定服務(wù)器套接字
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr) # 對話(接收與發(fā)送)
udp_sk.close() # 關(guān)閉服務(wù)器套接字
udp_server.py
View Code
client端代碼示例
import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)
View Code
類似于qq聊天的代碼示例:
#_*_coding:utf-8_*_
import socket
ip_port=('127.0.0.1',8081)
udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #DGRAM:datagram 數(shù)據(jù)報(bào)文的意思,象征著UDP協(xié)議的通信方式
udp_server_sock.bind(ip_port)#你對外提供服務(wù)的端口就是這一個,所有的客戶端都是通過這個端口和你進(jìn)行通信的
while True:
qq_msg,addr=udp_server_sock.recvfrom(1024)# 阻塞狀態(tài),等待接收消息
print('來自[%s:%s]的一條消息:33[1;44m%s33[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
back_msg=input('回復(fù)消息: ').strip()
udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
server端
View Code
#_*_coding:utf-8_*_
import socket
BUFSIZE=1024
udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
qq_name_dic={
'taibai':('127.0.0.1',8081),
'Jedan':('127.0.0.1',8081),
'Jack':('127.0.0.1',8081),
'John':('127.0.0.1',8081),
}
while True:
qq_name=input('請選擇聊天對象: ').strip()
while True:
msg=input('請輸入消息,回車發(fā)送,輸入q結(jié)束和他的聊天: ').strip()
if msg == 'q':break
if not msg or not qq_name or qq_name not in qq_name_dic:continue
udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])# 必須帶著自己的地址,這就是UDP不一樣的地方,不需要建立連接,但是要帶著自己的地址給服務(wù)端,否則服務(wù)端無法判斷是誰給我發(fā)的消息,并且不知道該把消息回復(fù)到什么地方,因?yàn)槲覀冎g沒有建立連接通道
back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)# 同樣也是阻塞狀態(tài),等待接收消息
print('來自[%s:%s]的一條消息:33[1;44m%s33[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))
udp_client_socket.close()
client端
View Code
接下來,給大家說一個真實(shí)的例子,也就是實(shí)際當(dāng)中應(yīng)用的,那么這是個什么例子呢?就是我們電腦系統(tǒng)上的時間,windows系統(tǒng)的時間是和微軟的時間服務(wù)器上的時間同步的,而mac本是和蘋果服務(wù)商的時間服務(wù)器同步的,這是怎么做的呢,首先他們的時間服務(wù)器上的時間是和國家同步的,你們用我的系統(tǒng),那么你們的時間只要和我時間服務(wù)器上的時間同步就行了,對吧,我時間服務(wù)器是不是提供服務(wù)的啊,相當(dāng)于一個服務(wù)端,我們的電腦就相當(dāng)于客戶端,就是通過UDP來搞的。
我們自制一個時間服務(wù)器的代碼示例:
from socket import *
from time import strftime
import time
ip_port = ('127.0.0.1', 9000)
bufsize = 1024
tcp_server = socket(AF_INET, SOCK_DGRAM)
tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcp_server.bind(ip_port)
while True:
msg, addr = tcp_server.recvfrom(bufsize)
print('===>', msg)
stru_time = time.localtime() #當(dāng)前的結(jié)構(gòu)化時間
if not msg:
time_fmt = '%Y-%m-%d %X'
else:
time_fmt = msg.decode('utf-8')
back_msg = strftime(time_fmt,stru_time)
print(back_msg,type(back_msg))
tcp_server.sendto(back_msg.encode('utf-8'), addr)
tcp_server.close()
server端
View Code
from socket import *
ip_port=('127.0.0.1',9000)
bufsize=1024
tcp_client=socket(AF_INET,SOCK_DGRAM)
while True:
msg=input('請輸入時間格式(例%Y %m %d)>>: ').strip()
tcp_client.sendto(msg.encode('utf-8'),ip_port)
data=tcp_client.recv(bufsize)
print('當(dāng)前日期:',str(data,encoding='utf-8'))
client端
View Code
UDP來個小練習(xí)吧:
練習(xí)的需求是這樣的:1、服務(wù)端需要提供的服務(wù)有:接收消息(時間格式的字符串)、將我的本地的時間轉(zhuǎn)換成接收到的消息的格式(也就是個時間格式的字符串)、發(fā)回給客戶端。2、客戶端自行想一下怎么寫。
TCP協(xié)議和UDP協(xié)議下socket的基本使用ok了,那我們來深入分析一下socket。(這一塊的內(nèi)容初學(xué)者不要看,對socket有些了解的同學(xué)可以研究一下,切記看不懂很正常,不要深究,現(xiàn)階段你們就是學(xué)習(xí)應(yīng)用為主!)>>>>看這里>>>>socket原理剖析,里面包含socket中各個方法的作用和方法中的參數(shù)。
這里我列出兩個簡易描述socket各個參數(shù)和方法的圖,共大家參考:
socket類型:
socket各個方法的解釋:
筆記
總結(jié)
以上是生活随笔為你收集整理的27 网络通信协议 udp tcp的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: verilog 条件编译命令`ifdef
- 下一篇: 将List<Map>中的da