1排拷、tcp相關(guān)介紹
TCP協(xié)議猎唁,傳輸控制協(xié)議(英語:Transmission Control Protocol误证,縮寫為 TCP)是一種面向連接的倔幼、可靠的盖腿、基于字節(jié)流的傳輸層通信協(xié)議,由IETF的RFC 793定義。
TCP通信需要經(jīng)過創(chuàng)建連接翩腐、數(shù)據(jù)傳送鸟款、終止連接三個(gè)步驟。
TCP通信模型中茂卦,在通信開始之前何什, 一定要先建立相關(guān)的鏈接, 才能發(fā)送數(shù)據(jù)等龙, 類似于生活中处渣, "打電話""
TCP特點(diǎn)
- 面向連接
通信雙方必須先建立連接才能進(jìn)行數(shù)據(jù)的傳輸,雙方都必須為該連接分配必要的系統(tǒng)內(nèi)核資源蛛砰,以管理連接的狀態(tài)和連接上的傳輸霍比。
雙方間的數(shù)據(jù)傳輸都可以通過這一個(gè)連接進(jìn)行。
完成數(shù)據(jù)交換后暴备,雙方必須斷開此連接悠瞬,以釋放系統(tǒng)資源。
這種連接是一對(duì)一的涯捻,因此TCP不適用于廣播的應(yīng)用程序浅妆,基于廣播的應(yīng)用程序請(qǐng)使用UDP協(xié)議。
-
可靠傳輸
- TCP采用發(fā)送應(yīng)答機(jī)制
TCP發(fā)送的每個(gè)報(bào)文段都必須得到接收方的應(yīng)答才認(rèn)為這個(gè)TCP報(bào)文段傳輸成功
- 超時(shí)重傳
發(fā)送端發(fā)出一個(gè)報(bào)文段之后就啟動(dòng)定時(shí)器障癌,如果在定時(shí)時(shí)間內(nèi)沒有收到應(yīng)答就重新發(fā)送這個(gè)報(bào)文段凌外。
TCP為了保證不發(fā)生丟包,就給每個(gè)包一個(gè)序號(hào)涛浙,同時(shí)序號(hào)也保證了傳送到接收端實(shí)體的包的按序接收康辑。然后接收端實(shí)體對(duì)已成功收到的包發(fā)回一個(gè)相應(yīng)的確認(rèn)(ACK);如果發(fā)送端實(shí)體在合理的往返時(shí)延(RTT)內(nèi)未收到確認(rèn)轿亮,那么對(duì)應(yīng)的數(shù)據(jù)包就被假設(shè)為已丟失將會(huì)被進(jìn)行重傳疮薇。
- 錯(cuò)誤校驗(yàn)
TCP用一個(gè)校驗(yàn)和函數(shù)來檢驗(yàn)數(shù)據(jù)是否有錯(cuò)誤;在發(fā)送和接收時(shí)都要計(jì)算校驗(yàn)和我注。
- 流量控制和阻塞管理
流量控制用來避免主機(jī)發(fā)送得過快而使接收方來不及完全收下按咒。
TCP與UDP的不同點(diǎn)
- 面向連接(確認(rèn)有創(chuàng)建三方交握,連接已創(chuàng)建才作傳輸但骨。)
- 有序數(shù)據(jù)傳輸
- 重發(fā)丟失的數(shù)據(jù)包
- 舍棄重復(fù)的數(shù)據(jù)包
- 無差錯(cuò)的數(shù)據(jù)傳輸
- 阻塞/流量控制
2励七、TCP服務(wù)器
實(shí)際生活中:
- 買個(gè)手機(jī)
- 插上手機(jī)卡
- 設(shè)計(jì)手機(jī)為正常接聽狀態(tài)(即能夠響鈴)
- 靜靜的等著別人撥打
在程序中, 如果想要完成一個(gè)TCP服務(wù)器的功能奔缠, 需要的流程如下:
- socket創(chuàng)建一個(gè)套接字
- bind綁定ip和port
- listen使套接字變?yōu)榭梢员粍?dòng)鏈接
- accept等待客戶端的鏈接
- recv/send接收發(fā)送數(shù)據(jù)
一個(gè)很簡單的TCP服務(wù)器如下:
from socket import *
# 1.創(chuàng)建一個(gè)socket套接字(買手機(jī)卡)
tcpSerSocket = socket(AF_INET, SOCK_STREAM)
# 2.bind綁定本機(jī)ip和port(插上手機(jī)卡)
address = (''. 7777)
tcpSerSocket.bind(address)
# 3.listen使套接字變?yōu)榭梢员粍?dòng)鏈接(設(shè)置為手機(jī)正常接聽狀態(tài))
tcpSerSocket.listen(5)
# 4.accept等待客戶端的鏈接(靜靜的等著別人撥打)
# newSocket用來為這個(gè)客戶端服務(wù)
# tcpSerSocket就可以省下來專門等待其他新客戶端的鏈接
newSocket, clientAddr = tcpSerSocket.accept()
# 5.recv接收數(shù)據(jù)掠抬,最大接收1024個(gè)字節(jié)
recvData = newSocket.recv(1024).decode('gbk')
print('接收到的數(shù)據(jù)為:',recvData)
# 6.send發(fā)送一些數(shù)據(jù)到客戶端
newSocket.send("服務(wù)器已經(jīng)收到了!".encode('gbk'))
# 關(guān)閉為這個(gè)客戶端服務(wù)的套接字, 只要關(guān)閉了校哎, 就意味著為不能再為這個(gè)客戶端服務(wù)
newSocket.close()
# 關(guān)閉監(jiān)聽套接字两波, 只要這個(gè)套接字關(guān)閉了, 就意味著整個(gè)程序不能再接收任何新的客戶端的連接
tcpSerSocket.close()
3、TCP客戶器
TCP的客戶端要比服務(wù)器端簡單很多雨女,如果說服務(wù)器端是需要自己買手機(jī)谚攒、查手機(jī)卡、設(shè)置鈴聲氛堕、等待別人打電話流程的話馏臭,那么客戶端就只需要找一個(gè)電話亭,拿起電話撥打即可讼稚,流程要少很多
生活中的電話機(jī)括儒,如果想讓別人能更夠打通咱們的電話獲取相應(yīng)服務(wù)的話,需要做以下幾件事情:
- 買個(gè)手機(jī)
- 插上手機(jī)卡
- 設(shè)計(jì)手機(jī)為正常接聽狀態(tài)(即能夠響鈴)
- 靜靜的等著別人撥打
from socket import *
from time import *
# 1.socket創(chuàng)建一個(gè)套接字
tcpClient = socket(AF_INET,SOCK_STREAM)
# 2.綁定本機(jī)端口號(hào),也可以不綁定
tcpClient.bind(('',8888))
# 3.連接服務(wù)器
tcpClient.connect(('192.168.14.72',5678))
#4.接收消息
data1 = tcpClient.recv(1024)
print(data1.decode('gbk'))
#5.發(fā)消息
while True:
data = input('>>\t').encode('gbk')
tcpClient.send(data)
if data=='886':
sleep(4)
tcpClient.close()
break
4.應(yīng)用:模擬QQ聊天
客戶端參考代碼
from socket import *
# 創(chuàng)建socket
tcpClientSocket = socket(AF_INET, SOCK_STREAM)
# 鏈接服務(wù)器
serAddr = ('192.168.1.102', 7788)
tcpClientSocket.connect(serAddr)
while True:
# 提示用戶輸入數(shù)據(jù)
sendData = input("send:")
if len(sendData)>0:
tcpClientSocket.send(sendData)
else:
break
# 接收對(duì)方發(fā)送過來的數(shù)據(jù)锐想,最大接收1024個(gè)字節(jié)
recvData = tcpClientSocket.recv(1024)
Print('recv:',recvData)
# 關(guān)閉套接字
tcpClientSocket.close()
服務(wù)器端參考代碼
#coding=utf-8
from socket import *
# 創(chuàng)建socket
tcpSerSocket = socket(AF_INET, SOCK_STREAM)
# 綁定本地信息
address = ('', 7788)
tcpSerSocket.bind(address)
# 使用socket創(chuàng)建的套接字默認(rèn)的屬性是主動(dòng)的帮寻,使用listen將其變?yōu)楸粍?dòng)的,這樣就可以接收別人的鏈接了
tcpSerSocket.listen(5)
while True:
# 如果有新的客戶端來鏈接服務(wù)器赠摇,那么就產(chǎn)生一個(gè)信心的套接字專門為這個(gè)客戶端服務(wù)器
# newSocket用來為這個(gè)客戶端服務(wù)
# tcpSerSocket就可以省下來專門等待其他新客戶端的鏈接
newSocket, clientAddr = tcpSerSocket.accept()
while True:
# 接收對(duì)方發(fā)送過來的數(shù)據(jù)固逗,最大接收1024個(gè)字節(jié)
recvData = newSocket.recv(1024)
# 如果接收的數(shù)據(jù)的長度為0,則意味著客戶端關(guān)閉了鏈接
if len(recvData)>0:
print 'recv:',recvData
else:
break
# 發(fā)送一些數(shù)據(jù)到客戶端
sendData = input("send:")
newSocket.send(sendData)
# 關(guān)閉為這個(gè)客戶端服務(wù)的套接字藕帜,只要關(guān)閉了烫罩,就意味著為不能再為這個(gè)客戶端服務(wù)了,如果還需要服務(wù)洽故,只能再次重新連接
newSocket.close()
# 關(guān)閉監(jiān)聽套接字贝攒,只要這個(gè)套接字關(guān)閉了,就意味著整個(gè)程序不能再接收任何新的客戶端的連接
tcpSerSocket.close()
5.應(yīng)用:tcp通信-多線程服務(wù)器端
from socket import *
from threading import *
class Mythread(Thread):
def __init__(self, socketClient, clientAddress):
Thread.__init__(self)
self.socketClient = socketClient
self.clientAddress = clientAddress
def run(self):
data1 = '哈哈时甚。隘弊。』氖剩可以聊天了'.encode('utf_8')
self.socketClient.send(data1)
while True:
try:
# 接收客戶端發(fā)的消息
data = self.socketClient.recv(1024).decode('utf-8')
except:
print('<<\t%s-%s 已經(jīng)斷開了' % (self.clientAddrdess[0], self.clientAddrdess[1]))
break
if data == '886':
break
print('<<\t%s-%s:%s' % (self.clientAddrdess[0], self.clientAddrdess[1], data))
def main():
tcpServer = socket(AF_INET, SOCK_STREAM)
tcpServer.bind(('', 5678))
tcpServer.listen(5)
while True:
print('服務(wù)器等待客戶端連接......')
socketClient, clientAddress = tcpServer.accept()
Mythread(socketClient, clientAddress).start()
print('%s-%s 連接成功......' % (clientAddrdess[0], clientAddrdess[1]))
if __name__ == '__main__':
main()
#tcpServer.close() 通常不用關(guān)服務(wù)器的
6梨熙、tcp三次握手
- 當(dāng)客戶端調(diào)用connect時(shí),發(fā)送syn x(一個(gè)序號(hào))給服務(wù)端吻贿,服務(wù)端有l(wèi)isten和accept串结,服務(wù)端收到后ack x+1給客戶端,同時(shí)會(huì)一并傳遞一個(gè)新的syn y舅列,客戶端收到后,acd y+1給服務(wù)端卧蜓,此時(shí)三次握手完成帐要。
- 生活中的例子理解以上內(nèi)容:好比AB兩人打電話,A發(fā)出hello后弥奸,不清楚B是否收到信息榨惠,B收到后回復(fù)聽到 了,并發(fā)送hello,A收到后確定第一次發(fā)出的信息B可以收到赠橙,但是B發(fā)送信息后耽装,無法確定A是否收到,因此A應(yīng)當(dāng)回復(fù)收到
7期揪、tcp四次揮手
- 通過close關(guān)閉套接字來實(shí)現(xiàn)四次揮手掉奄,一般情況下,服務(wù)器不會(huì)主動(dòng)關(guān)閉凤薛,而是讓客戶端關(guān)閉連接姓建。以下內(nèi)容假定以客戶端關(guān)閉socket來描述,指定A為客戶端缤苫,B為服務(wù)器
- socket是全雙工的速兔,可以同時(shí)接收和發(fā)送
- 當(dāng)A調(diào)用close后,自身關(guān)閉send(socket依然存在)活玲,并告知B(第一次)不會(huì)再發(fā)送內(nèi)容了涣狗,B收到后,返回消息給A(第二次)表明已收到舒憾,就會(huì)去關(guān)閉recv屑柔,此時(shí)的實(shí)現(xiàn)是底層的,為了解除堵塞珍剑,B會(huì)設(shè)定recv得到的內(nèi)容為空字符串掸宛,此時(shí)如果有代碼判定,如果recv收到內(nèi)容為空招拙,就要調(diào)用close來關(guān)閉B的send唧瘾,調(diào)用完成后,發(fā)送消息給A(第三次)别凤,A收到后關(guān)閉recv饰序,并反饋消息給B(第四次),至此四次揮手完成规哪,但是A的socket并未關(guān)閉求豫,因?yàn)樗淮_定最后一次發(fā)送消息B是否收到,假設(shè)B沒有收到诉稍,那么B會(huì)在超時(shí)等待(每次通信過程蝠嘉,都存在超時(shí)等待,超過等待時(shí)間杯巨,就會(huì)重新發(fā)送數(shù)據(jù))后再次發(fā)送數(shù)據(jù)蚤告,此時(shí)如果A不存在了,此次過程就會(huì)失敗服爷,所以A會(huì)等待兩個(gè)MSL(數(shù)據(jù)在網(wǎng)絡(luò)中存儲(chǔ)的最大時(shí)間杜恰,因?yàn)橐l(fā)和收获诈,所以是兩個(gè))的時(shí)間后才會(huì)關(guān)閉,這也就是出現(xiàn)地址占用問題的原因心褐,解決辦法見Address already in use
- 誰先調(diào)用close舔涎,誰就會(huì)在最后等待兩分鐘左右,所以會(huì)有端口占用問題逗爹,因?yàn)榉?wù)器會(huì)綁定端口亡嫌,客戶端不綁定端口,所以一般先讓客戶端close桶至,客戶端如果再次運(yùn)行昼伴,又會(huì)隨機(jī)分配端口
- 與三次握手,將服務(wù)器端的確認(rèn)信息及反饋信息同時(shí)發(fā)送不同镣屹,四次揮手第二次和第三次無法合并圃郊,因?yàn)槭遣煌那闆r下發(fā)生的,第三次是需要服務(wù)器調(diào)用close才會(huì)發(fā)生的
8女蜈、tcp十種狀態(tài)
注意:
- 當(dāng)一端收到一個(gè)FIN持舆,內(nèi)核讓read返回0來通知應(yīng)用層另一端已經(jīng)終止了向本端的數(shù)據(jù)傳送
- 發(fā)送FIN通常是應(yīng)用層對(duì)socket進(jìn)行關(guān)閉的結(jié)果
9、tcp長連接和短連接
9.1TCP短連接
模擬一種TCP短連接的情況:
- client 向 server 發(fā)起連接請(qǐng)求
- server 接到請(qǐng)求伪窖,雙方建立連接
- client 向 server 發(fā)送消息
- server 回應(yīng) client
- 一次讀寫完成逸寓,此時(shí)雙方任何一個(gè)都可以發(fā)起 close 操作
在第 步驟5中,一般都是 client 先發(fā)起 close 操作覆山。當(dāng)然也不排除有特殊的情況竹伸。
從上面的描述看,短連接一般只會(huì)在 client/server 間傳遞一次讀寫操作簇宽!
9.2TCP長連接
再模擬一種長連接的情況:
- client 向 server 發(fā)起連接
- server 接到請(qǐng)求勋篓,雙方建立連接
- client 向 server 發(fā)送消息
- server 回應(yīng) client
- 一次讀寫完成,連接不關(guān)閉
- 后續(xù)讀寫操作...
- 長時(shí)間操作之后client發(fā)起關(guān)閉請(qǐng)求
tcp注意點(diǎn)
- tcp服務(wù)器一般情況下都需要綁定魏割,否則客戶端找不到這個(gè)服務(wù)器
- tcp客戶端一般不綁定譬嚣,因?yàn)槭侵鲃?dòng)鏈接服務(wù)器,所以只要確定好服務(wù)器的ip钞它、port等信息就好拜银,本地客戶端可以隨機(jī)
- tcp服務(wù)器中通過listen可以將socket創(chuàng)建出來的主動(dòng)套接字變?yōu)楸粍?dòng)的,這是做tcp服務(wù)器時(shí)必須要做的
- 當(dāng)客戶端需要鏈接服務(wù)器時(shí)遭垛,就需要使用connect進(jìn)行鏈接尼桶,udp是不需要鏈接的而是直接發(fā)送,但是tcp必須先鏈接耻卡,只有鏈接成功才能通信
- 當(dāng)一個(gè)tcp客戶端連接服務(wù)器時(shí)疯汁,服務(wù)器端會(huì)有1個(gè)新的套接字,這個(gè)套接字用來標(biāo)記這個(gè)客戶端卵酪,單獨(dú)為這個(gè)客戶端服務(wù)
- listen后的套接字是被動(dòng)套接字幌蚊,用來接收新的客戶端的鏈接請(qǐng)求的,而accept返回的新套接字是標(biāo)記這個(gè)新客戶端的
- 關(guān)閉listen后的套接字意味著被動(dòng)套接字關(guān)閉了溃卡,會(huì)導(dǎo)致新的客戶端不能夠鏈接服務(wù)器溢豆,但是之前已經(jīng)鏈接成功的客戶端正常通信。
- 關(guān)閉accept返回的套接字意味著這個(gè)客戶端已經(jīng)服務(wù)完畢
- 當(dāng)客戶端的套接字調(diào)用close后瘸羡,服務(wù)器端會(huì)recv解堵塞漩仙,并且返回的長度為0(是空字符串),因此服務(wù)器可以通過返回?cái)?shù)據(jù)的長度(或者判斷是否是空字符串)來區(qū)別客戶端是否已經(jīng)下線