1. 程式人生 > >Linux學習之網路程式設計(TCP三次握手四次揮手)

Linux學習之網路程式設計(TCP三次握手四次揮手)

言之者無罪,聞之者足以戒。 - “詩序”

1、三次握手:

看一下三次握手的框圖:

(1)、伺服器必須準備好接受外來連線

(2)、客戶端呼叫connect來主動開啟一個連線,此時客戶端TCP將會發送一個SYN分節

(3)、伺服器必須確認客戶的SYN

(4)、客戶必須確認伺服器的SYN

下面我們看一下wireshark

對照著這張圖我們就可以理解上面的框圖中,三次握手的概念了。

下面我們來看一下server的程式:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#define MAX_LISTEN_QUE 5

int main(int argc,char *argv[])
{
        int listenfd,sockfd,opt=1;
        struct sockaddr_in server,client;
        char buf[200];
        socklen_t len;
        int timep;
        int ret;
        //建立套接字
        listenfd = socket(AF_INET,SOCK_STREAM,0);
        if(listenfd < 0)
        {
                perror("Create socket fail");
                return -1;
        }
        //設定套結字關聯的選項
        if((ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) < 0)
        {
                perror("Error ,set socket reuse addr failed");
                return -1;
        }
        //清空結構體中的變數值
        bzero(&server, sizeof(server));
        //初始化結構體的變數
        server.sin_family = AF_INET;//IPv4
        server.sin_port   = htons(8888);//主機轉換到網路
        server.sin_addr.s_addr  = htonl(INADDR_ANY);//同上(使用這個巨集套接字可以繫結到所有的
埠)
        //獲取結構體地址長度
        len = sizeof(struct sockaddr);
        //繫結伺服器ip地址和埠到套接字
        if(bind(listenfd, (struct sockaddr *)&server, len)<0)
        {
                perror("bind error.");
                return -1;
        }
        //設定伺服器的最大連線數
        listen(listenfd, MAX_LISTEN_QUE);

        while(1)
        {
           //等待客戶端請求,如果請求到來,返回一個新的socket
           //伺服器和客戶端利用新的socket來通訊
            sockfd = accept(listenfd, (struct sockaddr *)&client, &len);
            if(sockfd < 0)
            {
                perror("accept error.");
                return -1;
            }
        }
            sleep(10);
            //顯示系統的當前時間 
            timep = time(NULL);
            //將資料按照一定的格式轉換之後複製到buf
            //ctime返回一個表示當地時間的字串
            snprintf(buf, sizeof(buf), "%s", ctime(&timep));
            //向套接字中寫入buf儲存的時間
            write(sockfd, buf, strlen(buf));
            //列印儲存的位元組數
            printf("Bytes:%d\n", strlen(buf));
            //列印套接字的檔案描述符
            printf("sockfd=%d\n", sockfd);
            printf("IP:0x%x, Port:%d\n",ntohl(client.sin_addr.s_addr),ntohs(client.sin_port));
            sleep(10);
            //關閉套接字
            //close(sockfd);

        return 0;
}

在執行程式進行抓包的時候,sever函式我們是在:Linux系統下先執行,在通過windows系統下的命令提示符中連線sever。

備註:在windows系統下的搜尋中輸入:cmd就可以看到命令提示符程式

開啟之後輸入:telnet 192.168.177.133 8888就可以連線sever了(192.168.177.133是我的Linux的伺服器地址,8888是我設定的 埠號)。

幫助:如果你輸入上述命令之後提示:telnet不是內部或外部命令也不是可執行的程式或批處理,解決辦法可以參考下面的文章

https://blog.csdn.net/haijing1995/article/details/66475546

注意:在啟動客戶端和伺服器端的時候,要先啟動伺服器端之後再啟動客戶端,然後進行抓包。使用windows下的命令提示符一旦啟動程式是不會自己退出的。需要我們強制退出

2、四次揮手:

看一下四次揮手的框圖:

這裡我們還是先用上面所用到的命令提示符來用wireshark來抓包:

對照著這張圖我們就可以理解上面的框圖中,四次揮手的概念了。

下面我們在Linux中重複操作一下上面的過程:

先貼出server的程式:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#define MAX_LISTEN_QUE 5

int main(int argc,char *argv[])
{
        int listenfd,sockfd,opt=1;
        struct sockaddr_in server,client;
        char buf[200],read_buf[100];
        int bytes=0;
        socklen_t len;
        int timep;
        int ret;
        //建立套接字
        listenfd = socket(AF_INET,SOCK_STREAM,0);
        if(listenfd < 0)
        {
                perror("Create socket fail");
                return -1;
        }
        //設定套結字關聯的選項
        if((ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) < 0)
        {
                perror("Error ,set socket reuse addr failed");
                return -1;
        }
        //清空結構體中的變數值
        bzero(&server, sizeof(server));
        //初始化結構體的變數
        server.sin_family = AF_INET;//IPv4
        server.sin_port   = htons(8888);//主機轉換到網路
        server.sin_addr.s_addr  = htonl(INADDR_ANY);//同上(使用這個巨集套接字可以繫結到所有的
埠)
        //獲取結構體地址長度
        len = sizeof(struct sockaddr);
        //繫結伺服器ip地址和埠到套接字
        if(bind(listenfd, (struct sockaddr *)&server, len)<0)
        {
                perror("bind error.");
                return -1;
        }
        //設定伺服器的最大連線數
        listen(listenfd, MAX_LISTEN_QUE);
        while(1)
        {
           //等待客戶端請求,如果請求到來,返回一個新的socket
           //伺服器和客戶端利用新的socket來通訊
            sockfd = accept(listenfd, (struct sockaddr *)&client, &len);
            if(sockfd < 0)
            {
                perror("accept error.");
                return -1;
            }

            //顯示系統的當前時間 
            timep = time(NULL);
            //將資料按照一定的格式轉換之後複製到buf
            //ctime返回一個表示當地時間的字串
            snprintf(buf, sizeof(buf), "%s", ctime(&timep));
            //向套接字中寫入buf儲存的時間
            write(sockfd, buf, strlen(buf));
            //列印儲存的位元組數
            printf("Bytes:%d\n", strlen(buf));
            //列印套接字的檔案描述符
            printf("sockfd=%d\n", sockfd);
            printf("IP:0x%x, Port:%d\n",ntohl(client.sin_addr.s_addr),ntohs(client.sin_port));
            bytes = read(sockfd,read_buf,100);
            if(bytes < 0)
            {
                printf("read err\n");
                return -1;
            }
            if(bytes == 0)
             {
                printf("client connection closed\n");
                return 0;
             }
//              sleep(10);
            //關閉套接字
            close(sockfd);
        }
        return 0;
}

接下來看一下client的程式:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h> 
#include <fcntl.h>

int main(int argc,char *argv[]) 
{
        int sockfd; 
        struct sockaddr_in servaddr;
        char buf[100];//儲存讀取的內容
        int bytes;//儲存讀取的位元組數
        
        if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)//建立套接字
        {
                printf("socket error\n");
                return -1; 
        }
        //結構體中成員變數初始化為0     
        bzero(&servaddr,sizeof(servaddr));
        //初始化成員變數
        //初始化成員變數
        servaddr.sin_family = AF_INET;//IPv4
        servaddr.sin_addr.s_addr = inet_addr("192.168.177.133");//轉換為地址格式
        servaddr.sin_port = htons(8888);//主機序轉換到網路序

        if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0)//繫結伺服器
ip和埠到套結字
        {
                perror("connect error");
                return -2;
        }
        //儲存讀取的資料和讀取的位元組數
        bytes == read(sockfd,buf,100);
        if(bytes < 0)
        {
                printf("Error ,read failed\n");
                return -3;
        }
        //如果讀取的位元組數為0 ,就說明連線已經關閉了
        if(0 == bytes)
        {
                printf("Server close connection\n");
                return -4;

        }
        //列印讀取的位元組數和讀取的內容
        printf("read bytes %d\n",bytes);
        printf("Time: %s\n",buf);
        //關閉套結字
        close(sockfd);
        return 0;
}

之後看一下程式的執行結果: