posix API的一些理解

TCP Posix API的理解

我們主要從TCP連接講解整個的流程。

  • 連接的建立
  • 消息的收發
  • 連接的斷開

連接的建立

先看一下一個TCP server的創建過程。

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<errno.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
using namespace std;

int main(int argc, char *argv[])
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd<0)
	{
		printf("Create Socket failed!code:%d\n", errno);
		return -1;
	}
	struct sockaddr_in ServerAddr;
	

	memset(&ServerAddr, 0, sizeof(ServerAddr));
	
	ServerAddr.sin_family = AF_INET;
	ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	ServerAddr.sin_port = htons(9999);
	if (bind(sockfd, (struct sockaddr *)&ServerAddr, sizeof(ServerAddr))<0)
	{
		printf("bind function is failed!error code:%d\n", errno);
		return -1;
	}
	if (listen(sockfd,10)<0)
	{
		printf("listen function is failed!error code:%d\n", errno);
		return -1;
	}
	
	//開始接受TCP連接
	while (true)
	{
		socklen_t len = 0;
		int client_sock = accept(sockfd, (struct sockaddr*)&socket, &len);
		if (client_sock<0)
		{
			printf("accept() error ! error code:%d\n", errno);
			return -1;
		}
		
		char buf[2048];
		while(1)
        {
            memset(buf,'\0',sizeof(buf));
            read(client_sock,buf,sizeof(buf));  


			printf("client:# %s\n",buf);

            printf("server:$ ");

            memset(buf,'\0',sizeof(buf));

            fgets(buf,sizeof(buf),stdin);
            buf[strlen(buf)-1]='\0';
            if(strncasecmp(buf,"quit",4)==0)
            {
                printf("quit\n");
                break;
            }
            write(client_sock,buf,strlen(buf)+1);
            printf("wait...\n");
        }
        close(client_sock);
    }
	
	close(sockfd);	
	
	return 0;
}

準備一張TCP狀態轉換圖

三次握手的過程。

比較常會被問到的爲什麼3次握手?
其實就是需要3次確定連接。

其次就是對應的API的階段是什麼。
在服務器端調用listen以後,客戶端開始進行連接的時候,
一般都會在第一次握手的時候,維護一個鏈表,表明已經進行過第一次握手的半連接。
其次就是在第三次握手以後,就會將這個連接,保存到另外一個全連接的隊列,表示這個連接已經三次握手完畢,可以進行連接。

然後我們在服務端的時候,就可以調用accept進行連接的接受。
從我們的已經準備的好的全連接的隊列中,取出一個連接,在進行消息的收發。

我們再討論一下 listen中的backlog這個參數的作用。

這個參數根據文檔來說是這樣的

The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow.
If  a connection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may  be  ignored  so  that  a later reattempt at connection succeeds.

而我們可以從文檔中看出來,這個隊列主要指的是全連接隊列的長度,指定我們全連接隊列的長度的大小。

順便說一下ddos的攻擊問題,我的連接就是有大量的經過第一次握手的連接的客戶端和目標服務器進行連接,導致新的連接。

數據發送階段

這裏我們只考慮我們可以控制的階段,比如消息的收發,對於消息在公網區域是如何傳輸的,我們是無法進行控制以及追蹤的,因此我們就考慮消息的收發。
普遍存在三種情況,

  • 單條send
  • 循環send
  • 發送很多的數據的send
    首先,有一點,在我們創建完一個TCP連接的時候,就會知道源IP,目的IP,源端口,目的端口,以及對應的協議類型,也就存在一個TCB的概念。

由於第一種的send的情況比較普遍,就是直接的收發就可以了,我們在實際調用send的時候,並不是返回是整數,就代表着我們的信息已經發送成功了,其實只是將我們想要發送的信息黏貼在內核的發送緩衝區中,TCP只是能保證我們的消息順序是順序的。這樣就在我們循環發送的消息的時候,可能就會出現一個問題,就是TCP粘包的問題。

怎麼解決粘包的問題?

  • 增加包長度的信息
  • 增加分隔符
    通過這兩種方法就能解決TCP粘包的問題了。

最後一種就是發送一個文件之類的,規定一個消息的結構,併發送完畢就可以了。

連接斷開

網線斷了(網線剪短),網卡停止供電,網卡設備會重啓,你所有的連接都會重啓。
再次供電的時候,就會有變化。應用程序就通過心跳包,來判斷連接是否已經斷開。客戶端直接宕機,也是通過心跳包來進行判斷。

這裏主要就是討論就是如何解決close_wait的情況。
主要是由於recv返回0,沒有調用close。
業務數據和網絡接口,可以做成異步的。

推薦一個零聲學院免費教程,個人覺得老師講得不錯,
分享給大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,
TCP/IP,協程,DPDK等技術內容,點擊立即學習:
服務器
音視頻
dpdk
Linux內核

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章