目錄
一、初始TCP
1.TCP協(xié)議特點
2.TCP頭:
?3.確認應(yīng)答機制
4.超時重傳機制
5.流量控制
6.擁塞控制
(1)TCP 的擁塞控制方法
慢開始
擁塞避免
快重傳
快恢復
二、建立連接——三次握手
?三、斷開連接——四次揮手
四、socket編程
##客戶端API函數(shù)
##服務(wù)端API函數(shù)
一、初始TCP
1.TCP協(xié)議特點
(1)TCP 是面向連接的運輸層協(xié)議
。應(yīng)用程序在使用 TCP 協(xié)議之前,必須先建立 TCP 連接。在傳送數(shù)據(jù)完畢后,必須釋放已經(jīng)建立的 TCP 連接
(2)每一條 TCP 連接只能有兩個端點
,每一條 TCP 連接只能是點對點
的(一對一)
(3)TCP 提供可靠交付(
確認機制、擁塞控制、流量控制、超時重傳)
的服務(wù)。通過 TCP 連接傳送的數(shù)據(jù),無差錯、不丟失、不重復,并且按序到達
(4)TCP 提供全雙工通信
。TCP 允許通信雙方的應(yīng)用進程在任何時候都能發(fā)送數(shù)據(jù)。TCP 連接的兩端都設(shè)有發(fā)送緩存和接受緩存,用來臨時存放雙向通信的數(shù)據(jù)
(5)
面向字節(jié)流
。TCP 中的“流”指的是流入到進程或從進程流出的字節(jié)序列
2.TCP頭:
?3.確認應(yīng)答機制
確認應(yīng)答機制是TCP可靠性中核心之一
接收方回復收到的一個應(yīng)答報文(ACK),表示已經(jīng)收到
例如:你叫朋友一起出去玩,TCP中的確認應(yīng)答機制如下所示。針對發(fā)送的請求進行編號,應(yīng)答時也針對相應(yīng)的編號應(yīng)答,這樣就能保證數(shù)據(jù)傳輸?shù)目煽啃浴?/p>
4.超時重傳機制
超時重傳也是TCP可靠性保證的必要條件之一
確認應(yīng)答是比較理想的情況,但數(shù)據(jù)在傳輸過程中,可能是會丟包的
我們以上面叫朋友去玩舉例:A 給 B 發(fā)消息,你在家嘛?等了很久,A 也沒收到 B 的消息,此時,存在以下幾種情況:
(1) B 不想回 A 的消息
TCP 抱著一種 “悲觀的態(tài)度”,當一次丟包重傳之后,TCP 就覺得大概率后面的重傳也沒用,所以就隔一個更長的時間,節(jié)省帶寬
(2)B 沒收到 A 的消息 (丟包情況:?發(fā)的請求丟失)
(3)B 回復了消息,但 A 沒收到 (丟包情況:應(yīng)答的 ACK 丟失,重傳就意味著接收到相同數(shù)據(jù))
(2)(3)情況:丟包的兩種情況,對于發(fā)送方來說無法確定是哪種情況,因此,進行統(tǒng)一處理:當發(fā)送了一條數(shù)據(jù)之后,TCP 內(nèi)部就會自動啟動一個定時器,達到一定時間也沒收到 ACK,定時器就會自動觸發(fā)重傳消息的動作 —— 超時重傳
5.流量控制
流量控制(flow control):讓發(fā)送方的發(fā)送速率不要太快,要讓接收方來得及接收
利用滑動窗口機制可以很方便地在 TCP 連接上實現(xiàn)對發(fā)送方的流量控制,實質(zhì)就是TCP在發(fā)送數(shù)據(jù)的時候?qū)?shù)據(jù)放到發(fā)送緩沖區(qū),將接收的數(shù)據(jù)放到接收緩沖區(qū)。而流量控制要做的事情就是通過接收緩沖區(qū)的大小,控制發(fā)送端的發(fā)送,如果對方的接收緩沖區(qū)滿了,就不能繼續(xù)發(fā)送。為了控制發(fā)送端的速率,接收端在進行ACK確認時會攜帶自身的窗口(rwnd)大小,就會告知發(fā)送端自己緩沖區(qū)的大小,進行相應(yīng)的流量控制。
6.擁塞控制
擁塞控制
就是防止過多的數(shù)據(jù)注入到網(wǎng)絡(luò)中,這樣可以使網(wǎng)絡(luò)中的路由器或鏈路不致過載
。擁塞控制所要做的都是一個前提,就是網(wǎng)絡(luò)能夠承受現(xiàn)有的網(wǎng)絡(luò)負荷
(1)TCP 的擁塞控制方法
TCP 進行擁塞控制的算法有四種,即慢開始
(slow-start)、擁塞避免
(congestion avoidance)、快重傳
(fast retransmit)和快恢復
(fast recovery)
慢開始
當主機開始發(fā)送數(shù)據(jù)時,由于并不清楚網(wǎng)絡(luò)的負荷情況,如果立即把大量數(shù)據(jù)字節(jié)注入到網(wǎng)絡(luò),就有可能引起網(wǎng)絡(luò)發(fā)生擁塞。經(jīng)驗證明,較好的方法是先探測一下,即
由小到大逐漸增大發(fā)送窗口
,也就是說,由小到大逐漸增大擁塞窗口數(shù)值。
在執(zhí)行慢開始算法時,發(fā)送方每收到一個隊新報文段的確認 ACK,就把擁塞窗口值加1,然后開始下一輪的傳輸。因此擁塞窗口 cwnd 隨著傳輸輪次按指數(shù)規(guī)律增長。當擁塞窗口 cwnd 增長到慢開始門限值 ssthresh 時,就改成執(zhí)行擁塞避免算法,擁塞窗口按線性規(guī)律增長
ssthresh:慢開始門限,一般會有一個初始值
擁塞避免
讓擁塞窗口 cwnd 緩慢地增大,即每經(jīng)過一個往返時間 RTT 就把發(fā)送方的擁塞窗口 cwnd 加1,而不是像慢開始階段那樣加倍增加。擁塞窗口 cwnd?
按線性規(guī)律緩慢增長
,比慢開始算法的擁塞窗口增長速率緩慢得多“擁塞避免”并非完全能夠避免擁塞,而是把擁塞窗口控制為按線性規(guī)律增長,
使網(wǎng)絡(luò)比較不容易出現(xiàn)擁塞
快重傳
采用快重傳算法可以讓發(fā)送方
盡早知道發(fā)生了個別報文段的丟失
。快重傳算法首先要求接收方不要等待自己發(fā)送數(shù)據(jù)時才進行捎帶確認,而是要立即發(fā)送確認
,即使收到了失序的報文段
也要立即發(fā)出對已收到的報文段的重復確認快恢復
發(fā)送方知道當前只是丟失了個別的報文段。于是不啟動慢開始,而是執(zhí)行
快恢復
算法。這時,發(fā)送方調(diào)整門限值 ssthresh = cwnd / 2 ,同時設(shè)置擁塞窗口 cwnd = ssthresh ,并開始執(zhí)行擁塞避免算法
二、建立連接——三次握手
建立TCP連接共有三步,即三次握手
先簡單的給大家舉個例子,我們可以把他類比于平時打電話,如下:
?
第一次A不知道B能否聽到自己的聲音,A對B說“喂,你能聽到嗎?”,第二次B聽到后還需要知道A是否也能聽到自己的聲音,就是B對A的回話“我能聽到,你呢”,第三次A的回復確認雙方都能聽到聲音,也就是A對B的回復“我也能”,由上三次就能保證通話的正常,類似于網(wǎng)絡(luò)建立連接時的三次握手。
兩次握手可以嗎??
不可以
兩次握手只能保證單向連接是通暢的,TCP?協(xié)議是雙向的,第三次握手是為了使得sever知道客戶端答應(yīng)了連接的請求。其中兩次握手只能確定從客戶端到服務(wù)端的網(wǎng)絡(luò)是可達的,但卻無法保證從服務(wù)端到客戶端的網(wǎng)絡(luò)是可達的。所以我們一定要保證雙向的可達。
?如上圖所示,沒有第三次握手則不知道A的聽筒是否正常,導致不能正常通話
TCP三次握手:
第一次握手:
TCP客戶端打算建立連接,向服務(wù)器發(fā)送連接請求報文,客戶端請求連接,客戶端進行同步發(fā)送狀態(tài)(SYN-SEND),在連接請求報文中,把SYN=1,表示這是一個請求連接報文,把序號字段seq=x(x就是一個初始值),作為客戶端的初始序號
第二次握手:
TCP服務(wù)端在收到客戶端的請求后,如果體連接,則會向客戶端發(fā)送確認請求報文,服務(wù)器進入同步接收狀態(tài),在確認報文中,把同步位SYN和確認位ACK置為1 ,表示這是一個請求確認報文。把序號seq設(shè)置為一個初始值y,作為服務(wù)器的初始序號。把確認字段ack=x+1作為對客戶端的確認
第三次握手:
TCP客戶端在收到了服務(wù)器的確認信號后,還要向服務(wù)器發(fā)送一個確認報文,并進入連接已建立(ESTABLISHED),發(fā)送針對服務(wù)器確認的確認報文。
確認位ACK=1,表示這是一個確認報文
序號seq=x+1,表示第一次是x,第二次發(fā)送x+1
確認號ack=y+1,則是對服務(wù)器的確認
服務(wù)器收到后,進入連接已建立
?三、斷開連接——四次揮手
???斷開TCP連接共有四步,即四次揮手
第一次揮手:
TCP客戶端主動關(guān)閉TCP連接,TCP客戶端發(fā)送連接釋放(斷開)報文給服務(wù)端,并進入終止等待態(tài)1,
在連接斷開釋放報文中:
(1)終止位FIN和確認位ACK要設(shè)置為1,表示是一個TCP連接釋放報文,同時對之前的報文做確認
(2)序號seq設(shè)置為u(表示特定的值),等于之前已經(jīng)傳送過去的數(shù)據(jù)最后一個字節(jié)+1
(3)確認號ack設(shè)置為v,等于之前收到的數(shù)據(jù)最后一個字節(jié)序號+1
第二次揮手:
TCP服務(wù)器收到了TCP連接斷開請求報文,會發(fā)送一個確認報文給客戶端,并進入關(guān)閉等待狀態(tài),客戶端收到確認會進入終止等待態(tài)2
在斷開確認報文中:
- 確認號ACK=1,表示是一個確認報文
- 序號seq=v,等于之前服務(wù)器發(fā)送的最后一個字節(jié)+1,與次一次揮手客戶端的確認號做匹配
繼續(xù)把當前沒有傳輸完成的數(shù)據(jù)傳輸完畢
第三次揮手:
TCP服務(wù)端給客戶端發(fā)送連接釋放報文,并進入最后確認狀態(tài)
在服務(wù)端連接釋放報文中:
- 終止位FIN和確認位ACK設(shè)置為1,表示這是一個TCP連接釋放,同時對之前的數(shù)據(jù)做確認
- 序號seq=w,這時服務(wù)器屬于半關(guān)閉狀態(tài)(斷開是雙向的)
- 確認號ack=u+1,釋放的重復確認
第四次揮手:
TCP客戶端在收到服務(wù)端的連接釋放,發(fā)送確認報文,并進入時間等待狀態(tài)
確認位ACK=1,表示是一個確認報文
序號seq=u+1(上一次是u,再發(fā)一次),表示是一個釋放報文
確認號ack=w+1,表示是一個確認
?TCP四次揮手為什么要等待2MSL?
一個MSL表示報文存活的最大時間,不管是A發(fā)送到B的報文,還是B發(fā)送到A的報文,都是最大可存活1MSL,那么等待2MSL,也就是報文一來一回。
四、socket編程
客戶端:類比于打電話
類比 |
功能 |
函數(shù) |
有手機 |
創(chuàng)建套接字(有對應(yīng)的TCP協(xié)議) |
socket() |
有手機號碼 |
綁定套接字(有自己的網(wǎng)絡(luò)信息) |
bind() |
撥打?qū)Ψ诫娫捥柎a |
請求服務(wù)器連接(與服務(wù)器建立連接) |
connect() |
進行通話 |
收發(fā)數(shù)據(jù)(進行通信) |
read()、write()、recv()、send()、 |
掛斷電話 |
結(jié)束通信(關(guān)閉套接字) |
close() |
##客戶端API函數(shù)
1.創(chuàng)建套接字
#include <sys/types.h>
#include <sys/socket.h>
//創(chuàng)建套接字文件,為進程添加一個對應(yīng)的網(wǎng)絡(luò)通信協(xié)議(文件),返回值就是創(chuàng)建號的文件描述符(socket描述符就代表一套協(xié)議---套接字)
int socket(int domain, int type, int protocol);
參數(shù)1:
int domain:地址族,選用那種網(wǎng)絡(luò)層協(xié)議地址
AF_INET------IPV4
AF_INET6-----IPV6
參數(shù)2:
int type:套接字類型
SOCK_STREAM--------TCP
SOCK_DGRAM---------UDP
SOCK_RAW:原始套接字(沒有傳輸層協(xié)議)
參數(shù)3:
int protocol:套接字協(xié)議
?0:套接字默認協(xié)議
返回值:
int:整數(shù)---------文件描述符(套接字文件)
成功:返回套接字描述符 >= 0
失敗:返回-1
2.綁定套接字
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//綁定本地網(wǎng)絡(luò)信息到套接字中
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
參數(shù)1:
int sockfd:綁定本地網(wǎng)絡(luò)信息到哪個套接字上(綁定到哪一套協(xié)議上)
參數(shù)2:
const struct sockaddr *addr:結(jié)構(gòu)體地址,這個結(jié)構(gòu)體中存儲的本地網(wǎng)絡(luò)信息(要綁定的網(wǎng)絡(luò)信息ip、port)
struct sockaddr {//通用結(jié)構(gòu)體,表示一個網(wǎng)絡(luò)信息內(nèi)容
sa_family_t sa_family;
char sa_data[14];
}
//IPV4 網(wǎng)絡(luò)信息結(jié)構(gòu)體
struct sockaddr_in {
sa_family_t sin_family;//地址族 AF_INET
in_port_t sin_port;//端口
struct in_addr sin_addr;//結(jié)構(gòu)體變量--ip地址
};
/* Internet address. */
struct in_addr {
? uint32_t s_addr;//ipv4地址
};
參數(shù)3:
socklen_t addrlen:整數(shù),結(jié)構(gòu)體大小(確定信息結(jié)構(gòu)體的大?。?/code>
返回值:
成功:返回0
失?。悍祷?1
3.請求連接
#include <sys/types.h>? ? ? ? ??
#include <sys/socket.h>
//請求與服務(wù)器建立連接
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
參數(shù)1:
int sockfd:客戶端的套接字,使用套接字與服務(wù)端建立連接
參數(shù)2:
const struct sockaddr *addr:服務(wù)端的ip、prot信息,客戶端要和哪個服務(wù)器建立連接
參數(shù)3:
socklen_t addrlen:結(jié)構(gòu)體的大小
返回值:
成功:返回0
失?。悍祷?1
實現(xiàn)代碼:
//TCP客戶端進行通信(先發(fā)再收)
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
//1、創(chuàng)建套接字,選擇對應(yīng)的網(wǎng)絡(luò)通信協(xié)議組
int socketfd = socket(AF_INET,SOCK_STREAM,0);//選擇TCP協(xié)議
if(socketfd < 0)
{
printf("socket create error\n");
return -1;
}
//2、綁定套接字,對套接字添加自己當前進行需要到本地網(wǎng)絡(luò)信息
struct sockaddr_in clientaddr;//有一個IPV4網(wǎng)絡(luò)信息結(jié)構(gòu)體
clientaddr.sin_family = AF_INET;//地址族-----IPV4
clientaddr.sin_port = htons(20000);//為當前進程添加的端口號為10000
clientaddr.sin_addr.s_addr = inet_addr("0.0.0.0");
bind(socketfd,(struct sockaddr *)&clientaddr,sizeof(clientaddr));
//與服務(wù)器建立連接
struct sockaddr_in serveraddr;//服務(wù)端IPV4網(wǎng)絡(luò)信息結(jié)構(gòu)體
serveraddr.sin_family = AF_INET;//地址族-----IPV4
serveraddr.sin_port = htons(10000);//為當前進程添加的端口號為10000
serveraddr.sin_addr.s_addr = inet_addr("192.168.138.1");
if(connect(socketfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr))<0)
{
printf("connect error\n");
}else{
printf("connect ok\n");
}
char buf[20];
while(1)
{
memset(buf,0,20);//清空buf
fgets(buf,20,stdin);
write(socketfd,buf,20);//發(fā)送
memset(buf,0,20);
read(socketfd,buf,20);//接收
printf("data is %s\n",buf);
}
close(socketfd);
return 0;
}
服務(wù)端:類比于接電話
類比 |
功能 |
函數(shù) |
有手機 |
創(chuàng)建套接字(有對應(yīng)的TCP協(xié)議) |
socket() |
有手機號碼 |
綁定套接字(有自己的網(wǎng)絡(luò)信息) |
bind() |
等待電話(待機) |
監(jiān)聽服務(wù)端套接字(查看是否有客戶端連接) |
listen() |
接聽電話 |
接收同意客戶端的連接請求 |
accept() |
進行通話 |
收發(fā)數(shù)據(jù)(進行通信) |
read()、write()、recv()、send()、 |
掛斷電話 |
結(jié)束通信(關(guān)閉套接字) |
close() |
##服務(wù)端API函數(shù)
1.創(chuàng)建套接字
#include <sys/types.h>
#include <sys/socket.h>
//創(chuàng)建套接字文件,為進程添加一個對應(yīng)的網(wǎng)絡(luò)通信協(xié)議(文件),返回值就是創(chuàng)建號的文件描述符(socket描述符就代表一套協(xié)議---套接字)
int socket(int domain, int type, int protocol);
參數(shù)1:
int domain:地址族,選用那種網(wǎng)絡(luò)層協(xié)議地址
AF_INET------IPV4
AF_INET6------IPV6
參數(shù)2:
int type:套接字類型
SOCK_STREAM--------TCP
SOCK_DGRAM---------UDP
SOCK_RAW:原始套接字(沒有傳輸層協(xié)議)
參數(shù)3:
int protocol:套接字協(xié)議
?0:套接字默認協(xié)議
返回值:
int:整數(shù)---------文件描述符(套接字文件)
成功:返回套接字描述符 >= 0
失敗:返回-1
2.邦定套接字
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//綁定本地網(wǎng)絡(luò)信息到套接字中
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
參數(shù)1:
int sockfd:綁定本地網(wǎng)絡(luò)信息到哪個套接字上(綁定到哪一套協(xié)議上)
參數(shù)2:
const struct sockaddr *addr:結(jié)構(gòu)體地址,這個結(jié)構(gòu)體中存儲的本地網(wǎng)絡(luò)信息(要綁定的網(wǎng)絡(luò)信息ip、port)
struct sockaddr {//通用結(jié)構(gòu)體,表示一個網(wǎng)絡(luò)信息內(nèi)容
sa_family_t sa_family;
char sa_data[14];
}
//IPV4 網(wǎng)絡(luò)信息結(jié)構(gòu)體
struct sockaddr_in {
sa_family_t sin_family;//地址族 AF_INET
in_port_t sin_port;//端口
struct in_addr sin_addr;//結(jié)構(gòu)體變量--ip地址
};
/* Internet address. */
struct in_addr {
? uint32_t s_addr;//ipv4地址
};
參數(shù)3:
socklen_t addrlen:整數(shù),結(jié)構(gòu)體大?。ù_定信息結(jié)構(gòu)體的大?。?/code>
返回值:
成功:返回0
失?。悍祷?1
3.監(jiān)聽
#include <sys/types.h>? ? ? ? ??
?#include <sys/socket.h>
監(jiān)聽等待客戶端連接,如果有客戶端的連接只會把客戶端的連接存儲起來(會創(chuàng)建一個監(jiān)聽隊列),只要有客戶端來進行連接,都會放在監(jiān)聽等待隊列中——能夠一直查看服務(wù)器自己的信息,是否有客戶端連接。且之后套接字只能用于監(jiān)聽,不能用于與客戶端進行通信
???????int listen(int sockfd, int backlog);當調(diào)用后自動監(jiān)聽(查看是否有客戶端連接)
參數(shù)1:
int sockfd:要進行監(jiān)聽的套接字,就是之前綁定了服務(wù)器ip、port的套接字,
表示要監(jiān)聽哪個套接字是否有客戶端連接
參數(shù)2:
int backlog:最多同時能夠存儲多少個客戶端連接——等待隊列大小
返回值:
成功—— ?0 ????
失敗——-1
4.同意連接請求
#include <sys/types.h> ?????????/* See NOTES */
#include <sys/socket.h>
//服務(wù)器同意連接請求(從等待隊列中取出一個客戶端連接請求,建立連接。如果監(jiān)聽隊列沒有連接請求,就阻塞等待監(jiān)聽隊列有連接請求)——一定要接收一個連接請求
???????int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
參數(shù)1:
int sockfd:監(jiān)聽套接字,從哪個監(jiān)聽中取出連接
參數(shù)2:
struct sockaddr *addr:用于存儲客戶端ip,port,不需要寫NULL
參數(shù)3:
socklen_t *addrlen:結(jié)構(gòu)體大小
返回值:
成功——返回與連接成功的客戶端通信的套接字 ??
失敗——-1
5.發(fā)送或接收數(shù)據(jù)
ssize_t send(int sockfd, const void *buf, size_t len, int?flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
參數(shù)1:
int sockfd:套接字
參數(shù)2:
const void *buf:要發(fā)送或接收的數(shù)據(jù)
參數(shù)3:
size_t len:數(shù)據(jù)大小
參數(shù)4:文章來源:http://www.zghlxwxcb.cn/news/detail-474455.html
int flags:標志,選項
0:阻塞
實現(xiàn)代碼:文章來源地址http://www.zghlxwxcb.cn/news/detail-474455.html
//tcp服務(wù)端,與客戶端進行通信
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
//1、創(chuàng)建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);//在服務(wù)器創(chuàng)建TCP套接字
//2、綁定套接字
struct sockaddr_in serveraddr;
serveraddr.sin_addr.s_addr = inet_addr("192.168.124.80");
serveraddr.sin_port = htons(9999);
serveraddr.sin_family = AF_INET;
bind(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
//3、監(jiān)聽套接字
listen(sockfd,10);
//4、接受客戶端連接
int clientfd = accept(sockfd,NULL,NULL);//返回值就是與客戶端通信的套接字
printf("ok\n");
//服務(wù)器與客戶端如何進行通信
char buf[50];
while(1)
{
printf("recv\n");
sleep(10);
memset(buf,0,50);
int num = recv(clientfd,buf,50,0);//返回值就是接收的大小
if(strcmp(buf,"quit\n") == 0)
break;
printf("size is %d; data is %s",num,buf);
send(clientfd,buf,50,0);
printf("send ok\n");
}
close(clientfd);
close(sockfd);
return 0;
}
到了這里,關(guān)于【網(wǎng)絡(luò)篇】socket編程——TCP(史上最全)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!