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