TCP四次揮手和伺服器主動斷開
發起斷開連線請求可以是客戶端也可以是伺服器,即主機1,主機2可以是客戶端也可以是伺服器。
ACK : TCP協議規定,只有ACK=1時有效,也規定連線建立後所有傳送的報文的ACK必須為1。
FIN (finis)即完,終結的意思, 用來釋放一個連線。當 FIN = 1 時,表明此報文段的傳送方的資料已經發送完畢,並要求釋放連線。
傳送序列號:Sequence Number
確認序列號:Acknowledgment Number
FIN_WAIT_1:表示等待對方的FIN報文。當SOCKET在ESTABLISHED狀態時,它想主動關閉連線,向對方傳送了FIN報文,此時該SOCKET進入到FIN_WAIT_1 狀態
FIN_WAIT_2:也表示等待對方的FIN報文。FIN_WAIT_2狀態下的SOCKET,表示半連線,也即有一方要求close連線,但另外還告訴對方,我暫時還有點資料需要傳送給你,稍後再關閉連線。
CLOSE_WAIT: 這種狀態的含義其實是表示在等待關閉。你回覆一個ACK給對方,並進入CLOSE_WAIT狀態。接下來就是檢視你是否還有資料要傳送給對方,如果沒有,就可以close這個socket,併發送FIN給對方,即關閉連線。
CLOSING:表示主機1給主機2傳送FIN後,並沒有收到主機2迴應的ACK,而收到了主機2傳送的FIN。表示雙方同時close一個socket,出現同時傳送FIN現象。
LAST_ACK: 傳送FIN報文後,等待對方的ACK報文,當收到ACK報文後,進入到CLOSED狀態。
TIME_WAIT: 表示收到了對方的FIN報文,併發送出了ACK確認,等2MSL後即可回到CLOSED可用狀態了。如果FIN_WAIT_1狀態下,收到了對方同時帶FIN標誌和ACK標誌的報文時,可以直接進入到TIME_WAIT狀態。
第一次揮手:主機1向主機2,傳送FIN報文段,表示關閉資料傳送,並主機1進入FIN_WAIT_1狀態,表示沒有資料要傳輸了。
第二次揮手:主機2收到FIN報文段後進入CLOSE_WAIT狀態(被動關閉),然後傳送ACK確認,表示同意你關閉請求了,主機到主機的資料鏈路關閉,主機進入FIN_WAIT_2狀態 。
第三次揮手:主機2等待主機1傳送完資料,傳送FIN到主機1請求關閉,主機2進入LAST_ACK狀態 。
第四次揮手:主機1收到主機2傳送的FIN後,回覆ACK確認到主機2,主機1進入TIME_WAIT狀態。主機2收到主機1的ACK後就關閉連線了,狀態為CLOSED。主機1等待2MSL,仍然沒有收到主機2的回覆,說明主機2已經正常關閉了,主機1關閉連線。
孤兒連線
連續停留在FIN_WAIT2狀態可能發生,客戶端執行半關閉狀態後,未等伺服器關閉連線就直接退出了,此時客戶端連線由核心接管。Linux為防止孤兒連線長時間存在核心中,定義了兩個變數指定孤兒連線數目和生存時間。
為什麼要四次揮手而不是三次
當主機2傳送ACK確認主機1的FIN時,並不代表主機2的資料傳送完畢,主機1傳送完FIN處於半關閉狀態(不能傳送資料,但可以接收資料),所以要等待主機2的資料傳送完畢後,發出FIN關閉連線請求時,主機2才進入CLOSED狀態,主機1再ACK確認關閉進入CLOSE狀態。
為什麼TIME_WAIT 狀態要等2MSL才進入CLOSED狀態
MSL(Maximum Segment Lifetime):報文最大生存時間,是任何報文段被丟棄前在網路內的最長時間。當主機1回覆主機2的FIN後,等待(2-4分鐘),即使兩端的應用程式結束。
如果主機1直接進入CLOSED狀態,由於IP協議不可靠性或網路問題,導致主機1最後發出的ACK報文未被主機2接收到,那麼主機2在超時後繼續向主機1重新發送FIN,而主機1已經關閉,那麼找不到向主機1傳送FIN的連線,主機2這時收到RST並把錯誤報告給高層,不符合TCP協議的可靠性特點。
如果主機1直接進入CLOSED狀態,而主機2還有資料滯留在網路中,當有一個新連線的埠和原來主機2的相同,那麼當原來滯留的資料到達後,主機1認為這些資料是新連線的。等待2MSL確保本次連線所有資料消失。
TIME_WAIT狀態過多會佔用大量的埠號,處理方法:
修改核心引數
儘可能被動關閉連線
將長連線改為短連線
close和shutdown
只要TCP棧的讀緩衝裡還有未讀取(read)資料,則呼叫close時會直接向對端傳送RST
close把描述字的引用計數減1,僅在該計數變為0的時候才關閉套介面。而使用shutdown可以不管引用計數的值是多少都能激發TCP的正常連線終止序列,即傳送FIN。
close終止資料傳送的兩個方向讀和寫。
shutdown函式進行關閉某一方向的操作。比如,有時我們只是需要告訴對方資料傳送完畢,只需要關閉資料傳送的一個通道,但還是要接受對方發過來的資料。
服務端主動斷開:心跳包
TCP socket心跳機制中,心跳包可以由伺服器傳送給客戶端,也可以由客戶端傳送給伺服器,不過比較起來,前者開銷可能更大。
這裡實現的是由客戶端給伺服器傳送心跳包,基本思路是:
1、伺服器為每個客戶端儲存了IP和計數器count,即map<fd, pair<ip, count>>。服務端主執行緒採用 select 實現多路IO複用,監聽新連線以及接受資料包(心跳包),子執行緒用於檢測心跳:
如果主執行緒接收到的是心跳包,將該客戶端對應的計數器 count 清零;
在子執行緒中,每隔3秒遍歷一次所有客戶端的計數器 count:
若 count 小於 5,將 count 計數器加 1;
若 count 等於 5,說明已經15秒未收到該使用者心跳包,判定該使用者已經掉線。
2、 客戶端則只是開闢子執行緒,定時給伺服器傳送心跳包(本示例中定時時間為3秒)。
Linux下一個socket心跳包的簡單實現:
#define BUFFER_SIZE 1024
enum Type {HEART, OTHER};
struct PACKET_HEAD
{
Type type;
int length;
};
void* heart_handler(void* arg);
class Server
{
private:
struct sockaddr_in server_addr;
socklen_t server_addr_len;
int listen_fd;
int max_fd;
fd_set master_set;
fd_set working_set;
struct timeval timeout;
map<int, pair<string, int> > mmap;
public:
Server(int port);
~Server();
void Bind();
void Listen(int queue_len = 20);
void Accept();
void Run();
void Recv(int nums);
friend void* heart_handler(void* arg);
};
Server::Server(int port)
{
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htons(INADDR_ANY);
server_addr.sin_port = htons(port);
listen_fd = socket(PF_INET, SOCK_STREAM, 0);
if(listen_fd < 0)
{
exit(1);
}
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
}
Server::~Server()
{
for(int fd=0; fd<=max_fd; ++fd)
{
if(FD_ISSET(fd, &master_set))
{
close(fd);
}
}
}
void Server::Bind()
{
if(-1 == (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))))
{
exit(1);
}
}
void Server::Listen(int queue_len)
{
if(-1 == listen(listen_fd, queue_len))
{
exit(1);
}
}
void Server::Accept()
{
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int new_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
if(new_fd < 0) {
exit(1);
}
string ip(inet_ntoa(client_addr.sin_addr));
mmap.insert(make_pair(new_fd, make_pair(ip, 0)));
FD_SET(new_fd, &master_set);
if(new_fd > max_fd)
{
max_fd = new_fd;
}
}
void Server::Recv(int nums)
{
for(int fd=0; fd<=max_fd; ++fd)
{
if(FD_ISSET(fd, &working_set))
{
bool close_conn = false;
PACKET_HEAD head;
recv(fd, &head, sizeof(head), 0);
if(head.type == HEART)
{
mmap[fd].second = 0;
}
else
{
// 資料包,通過head.length確認資料包長度
}
if(close_conn)
{
close(fd);
FD_CLR(fd, &master_set);
if(fd == max_fd)
{
while(FD_ISSET(max_fd, &master_set) == false)
--max_fd;
}
}
}
}
}
void Server::Run()
{
pthread_t id;
int ret = pthread_create(&id, NULL, heart_handler, (void*)this);
if(ret != 0) {
cout << "Can not create heart-beat checking thread.\n";
}
max_fd = listen_fd;
FD_ZERO(&master_set);
FD_SET(listen_fd, &master_set);
while(1)
{
FD_ZERO(&working_set);
memcpy(&working_set, &master_set, sizeof(master_set));
timeout.tv_sec = 30;
timeout.tv_usec = 0;
int nums = select(max_fd+1, &working_set, NULL, NULL, &timeout);
if(nums < 0) {
exit(1);
}
if(nums == 0) {
continue;
}
if(FD_ISSET(listen_fd, &working_set))
Accept();
else
Recv(nums);
}
}
void* heart_handler(void* arg)
{
Server* s = (Server*)arg;
while(1)
{
map<int, pair<string, int> >::iterator it = s->mmap.begin();
for( ; it!=s->mmap.end(); )
{
if(it->second.second == 5)
{
int fd = it->first;
close(fd);
FD_CLR(fd, &s->master_set);
if(fd == s->max_fd)
{
while(FD_ISSET(s->max_fd, &s->master_set) == false)
s->max_fd--;
}
s->mmap.erase(it++);
}
else if(it->second.second < 5 && it->second.second >= 0)
{
it->second.second += 1;
++it;
}
else
{
++it;
}
}
sleep(3);
}
}
---------------------
來源:CSDN
原文:https://blog.csdn.net/lisonglisonglisong/article/details/51327695
https://blog.csdn.net/qq_34501940/article/details/51119726
版權宣告:本文為博主原創文章,轉載請附上博文連結!