「TCP:三次握手」分析——以一個簡單的「服務器」和「客戶端」爲例

linux&C這兩天學到了網絡編程這一章,本身寫了一個小的」服務器」和」客戶端」程序,目的在於簡單理解tcp/ip模型,以及要搭建一臺簡單服務器,服務器和客戶端最基本的事情要幹什麼,這篇博客就這個小程序,也簡單分析了本身對」TCP-三次握手」過程的理解。由於初學網絡編程,說的不對的地方歡迎你們評論交流。linux

套接字:
套接字由4部分組成,服務器IP地址和客戶端IP地址以及服務器端口號和客戶端端口號,是客戶端和服務器端傳輸數據肯定線路的保證,是支持TCP/IP的網絡通訊的基本操做單元,能夠看作是不一樣主機之間的進程進行雙向通訊的端點,簡單的說就是通訊的兩方的一種約定,用套接字中的相關函數來完成通訊過程。編程

首先咱們來看服務器端的代碼:小程序

#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>

#define MAX_QUEUE_LENGTH 1024

int main(int argc,char *argv[])
{
    int                          sock_fd,conn_fd;
    struct     sockaddr_in       serv_addr,conn_addr;
    socklen_t                    conn_len;
    pid_t                        pid1;
    char                         recv_buf[128];
    int                          ret;

    //建立套接字
    sock_fd = socket(AF_INET,SOCK_STREAM,0);
    /*參數分別爲(使用IPV4 tcp/ip協議,使用tcp流套接字, 經過前兩個參數來肯定使用的協議類型,默認爲零)*/

    //mZ服務器端的套接字進行初始化
    memset(&serv_addr,0,sizeof(struct sockaddr_in));
    //memset函數將serv_addr 用0進行初始化
    serv_addr.sin_family = AF_INET;
    /*設置地址類型:對於sin_family,表示用tcp/ip協議編程, 因此此值只能爲AF_INEF*/
    serv_addr.sin_port = htons(4507);
    //設置端口號爲4507
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    //關於ANADDR——ANY這個宏的解釋在下面有,也能夠向下面同樣設定指定IP
    //inet_pton(AF_INET,"XXX.XXX.XXX.XXX",&serv_addr.sin_addr);
    //當我只有一塊網卡,一個IP,因此也只能設置爲本身的ip了


    //綁定套接字
    bind(sock_fd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr_in));
/*綁定套接字的過程是將我前面建立的套接字與初始化的端口綁定起來, 由於socket只是建立了一個套接字,這個套接字將在哪一個端口上工做, 並無被指定,做爲服務器,它的IP和端口通常是固定的,所以咱們須要將剛纔 初始化的端口和套接字綁定到一塊兒,這時套接字已經完成服務器的ip和端口這一半*/

    //將套接字轉化成監聽套接字
    listen(sock_fd,MAX_QUEUE_LENGTH);
    //第二個參數比較重要,它是咱們服務器已完成套接字隊列的長度,accept函數每次
    //會從已完成的套接字隊列中拿走一個即conn_fd,也就是監聽套接字,注意:內核會維護兩個隊列,
    //一個是已完成的套接字隊列,一個是未完成的套接字隊列,一個套接字完成的過程是在三次握手的
    //過程當中完成的,是由tcp/ip協議棧完成的


    //接受客戶端的請求
    conn_len = sizeof(struct sockaddr_in);
    while(1)
    {
        conn_fd = accept(sock_fd,(struct sockaddr *)&conn_addr,&conn_len);
        //服務器會將客戶端的鏈接消息先放在未完成的隊列中,三次握手以後,就到已完成隊列中
        //conn_fd就是accept返回的叫做鏈接套接字,它的信息有服務器的端口和IP以及客戶的端口和IP,
        //監聽套接字繼續監聽,對於新的處理,系統會從新開一個線程處理,本身也能夠用進程
        printf("accept a new connection,ip:%s\n",inet_ntoa(conn_addr.sin_addr));
        pid1 = fork();
        if(pid1 == 0)
            ret = recv(conn_fd,recv_buf,sizeof(recv_buf),0);
        recv_buf[ret-1] = '\0';
    }
    printf("%s\n",recv_buf);
}

註釋中解釋了套接字的初始化,創建,綁定,監聽的過程,結束以後咱們服務器端的工做就作好了,爲了檢驗程序是否正確,運行程序以後咱們能夠用netstat命令查看咱們服務器端是否開啓監聽模式服務器

$ netstat -apt | grep LISTEN markdown

能夠看到4507端口的狀態是LISTEN
能夠看到4507端口的狀態是LISTEN。網絡

在說說INADDR_ANY的做用socket

INADDR_ANY就是指定地址爲0.0.0.0的地址,這個地址事實上表示不肯定地址,或「全部地址」、「任意地址」。 通常來講,在各個系統中均定義成爲0值。舉個例子:你的電腦上若是有多塊網卡就會有好幾個IP,或者說你的服務器有多臺主機也會有好多個IP,而若是你的IP會發生變化,好比變多或者變少,可是爲了減小bind()時的麻煩,能夠統一設定就在0.0.0.0這個地址上監聽。全部的信息都會到這個地址上,若是你須要將服務器上的全部IP中的80端口監聽,再將80端口綁定就OK了,我就瞭解了這麼多。tcp

而後再看咱們的客戶端代碼:函數

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<netinet/in.h>

int main(int argc,char *argv)
{
    struct sockaddr_in      serv_addr;
    int                     conn_fd;

    //一樣咱們須要在客戶端也創建套接字
    conn_fd = socket(AF_INET,SOCK_STREAM,0);

    //初始化與服務器端匹配的信息
    memset(&serv_addr,0,sizeof(struct sockaddr_in));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(4507);
    inet_aton("192.168.20.144",&serv_addr.sin_addr);

    //用於向服務器端發送鏈接請求,服務器的IP地址和端口號由參數serv_addr指定
    connect(conn_fd,(struct sockaddr*)&serv_addr,sizeof(struct sockaddr_in));
    close(conn_fd);
}

客戶端有點簡單啊,可是能夠登陸,這樣就能夠基本實現咱們的客戶端和服務器之間鏈接了,下面咱們進入咱們的主題:是否是都忘了咱們的主題是——「三次握手「。
在這個實例中,三次握手發生在客戶端執行connect函數的時候。三次握手的過程下面的圖片能夠顯示清楚,我再舉個例子ui

第一次 client->server SYN=1,seq = x
第二次 server->client SYN=1,ACK = 1,seq = y,ack = x+1
第三次 client->server ACK=1,seq = x+1
SYN:同步序號,當SYN=1,ACK=0時,代表這是一個鏈接請求。
ACK:當ACK=1時,確認號字段即ack纔有效。
seq:範圍:[0,2^32 - 1],表示發送的第一個字節的編號。
ack:確認號,當ack = n,表示到n-1爲止的全部字節都已經被收到。

另外我用tcpdump命令抓了個包,能夠分析下,不對的地方指出

$ tcpdump port 4507

這裏寫圖片描述
個人客戶端IP爲192.168.122.172,服務器的IP爲192.168.20.128
第一次:Flag[S],表示這是一個請求鏈接,S是SYN字母的首字母,seq是將要發送的首字節的序列號,此時SYN = 1,ACK = 0。
第二次:收到SYN = 1的包,將ACK置爲1,ack = 1588197944表示在此以前的字節我已經所有收到。
第三次:當再次收到SYN=1,ACK=1的包時,將ack = 1,表示確認鏈接。
這裏寫圖片描述

相關文章
相關標籤/搜索