一個簡單的聊天系統

聲明:謝絕一切形式的轉載

socket套接字

socket起源於Unix,而Unix/Linux基本哲學之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關閉close”模式來操作。Socket就是該模式的一個實現, socket即是一種特殊的文件,一些socket函數就是對其進行的操作(讀/寫IO、打開、關閉). 說白了Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議
在這裏插入圖片描述

TCP/IP

TCP三次握手

tcp的三次握手如下所示。
在這裏插入圖片描述
有很多面試官很特別容易問爲什麼兩次不行?個人感覺,兩次也不是不行。只是在有些情況下會產生無效的連接。
比如客戶端發起連接,由於網絡阻塞,服務器一直沒有收到連接請求,客戶端沒有收到任何迴應,啓動重傳機制,又發起了一次連接,這次很順利,然後服務端正常返回信息,第二次的連接成功了。可是,萬萬沒想到,那個阻塞的連接經過跋山涉水終於到了服務器端,此時服務器作何處理那
服務器端認爲你咋又發了個請求呀,於是再次進行確認,客戶端收到一條服務器端的一個確認,不予理會。可服務器卻堅持認爲連接已經成功,等待客戶端傳輸數據,於是服務器的許多資源就浪費了。

抓包分析

個人認爲,涉及到tcp/ip原理講解的,空講理論略顯不足,甚至有點"耍流氓"的感覺。"三次握手,四次揮手"這種理論書本中講述的很清楚了,下面以WireShark工具見證一下這個理論。

三次握手

在這裏插入圖片描述通過上面可以看出三次握手的流程

  1. 客戶端向服務器端發送[SYN],seq=0
  2. 服務端向客戶端返回[SYN,ACK] Ack=1 seq=0
  3. 客戶端向服務器端返回[ACK] Ack=1
四次揮手

在這裏插入圖片描述在這裏插入圖片描述

tcp狀態變遷圖

在這裏插入圖片描述

基本的socket函數

使用socket編程用到的函數和基本流程如下:
在這裏插入圖片描述
注意:還有兩個函數send和recieve。這兩個函數比較容易望文生義,認爲是send把數據發送出去。其實不是這樣子的,只有當send把socket的發送緩衝區填滿的時候數據才進行發送,通過TCP/IP協議進行發送。進行數據傳輸的是協議。

簡單聊天系統

前面的理論知識還是很有必要了解的。之後開發程序就簡單多了。先看以下效果:
在這裏插入圖片描述

服務器端代碼

/*************************************************************************

 > File Name: server.c

 > Author: 無情劍客

 > Mail: [email protected]

 > Created Time: 2020年7月1日21時43分10秒
  ************************************************************************/

 #include<stdio.h>

 #include<stdlib.h>

 #include<string.h>

 #include<sys/types.h>

 #include<sys/socket.h>

 #include<unistd.h>

 #include<netinet/in.h>

 #include<arpa/inet.h>

 #define PORT 6666

 #define BACKLOG 10

 #define MAXDATASIZE  2048

int main(int argc, char *argv[])

{

    int listenfd;

    //創建一個socket描述符,此描述符僅是本主機上的一個普通文件描述符而已

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    //定義一個結構體變量servaddr,用來記錄給定的IP和port信息,爲bind函數做準備

    struct sockaddr_in serveraddr;

    bzero(&serveraddr, sizeof(serveraddr));

    serveraddr.sin_family = AF_INET;

    serveraddr.sin_port = htons(PORT); //把端口轉化爲網絡字節序,即大端模式

    serveraddr.sin_addr.s_addr = INADDR_ANY;

    //把“本地含義的描述符”綁定到一個IP和Port上,此時這個socket才具備對外連接的能力

    bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));

    //創建一個監聽隊列,用來保存用戶的請求連接信息(ip、port、protocol)

    listen(listenfd, BACKLOG);

    printf("======端口綁定成功,等待客戶端的連接======\n");

    //讓操作系統回填client的連接信息(ip、port、protocol)

    struct sockaddr_in peeraddr;

    socklen_t peer_len = sizeof(peeraddr);

    int connfd;

    while(1)

    {

        //accept函數從listen函數維護的監聽隊列裏取一個客戶連接請求處理

        connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peer_len);

        printf("\n=====================客戶端鏈接成功=====================\n");

        printf("IP = %s:PORT = %d\n", inet_ntoa(peeraddr.sin_addr), 		ntohs(peeraddr.sin_port));

        char buf[MAXDATASIZE];

        while(1)

        {

            memset(buf, '\0', MAXDATASIZE/sizeof  (char));

            int recv_length = recv(connfd, buf, MAXDATASIZE/sizeof (char), 0);

            if(recv_length == 0)

            {

                printf("客戶端已經關閉!\n");

                break;

            }
            char vul_buffer[64] ;
            strcpy(vul_buffer,buf);

            printf("客戶端說: ");

            fputs(vul_buffer, stdout);

            memset(buf, '\0', MAXDATASIZE/sizeof (char));

            printf("請輸入: ");

            fgets(buf, sizeof(buf), stdin);

            send(connfd, buf, recv_length, 0);

        }

        close(connfd);

        close(listenfd);

        return 0;

    }
}

這裏使用了strcpy這個危險函數,後續文章會針對這個漏洞進行攻擊。

客戶端代碼

/*************************************************************************

> File Name: client.c

> Author: 無情劍客

> Mail: [email protected]

> Created Time: 2020年07月01日 星期三 21時44分37秒

 ************************************************************************/

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<unistd.h>

#include<netinet/in.h>

#define PORT 6666

#define MAXDATASIZE 2048

int main(int argc, char *argv[])

{

    if(argc != 2)

    {

        fprintf(stderr, "請您輸入ip地址!\n");

        exit(1);

    }

    int sockfd;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    const char *server_ip = argv[1]; //從命令行獲取輸入的ip地址,此處沒有對ip地址進行檢驗

    struct sockaddr_in serveraddr;

    bzero(&serveraddr, sizeof(serveraddr));

    serveraddr.sin_family = AF_INET;

    serveraddr.sin_port = htons(PORT);

    inet_pton(AF_INET, server_ip, &serveraddr.sin_addr);

    connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));

    printf("=====================服務器鏈接成功=====================\n");

    char buf[MAXDATASIZE];

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

    printf("請輸入: ");

    while(fgets(buf, sizeof(buf), stdin) != NULL && (strcmp(buf, "quit")))

    {

        send(sockfd, buf, sizeof(buf), 0);

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

        recv(sockfd, buf, sizeof(buf), 0);

        printf("服務器說: ");

        fputs(buf, stdout);

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

        printf("請輸入: ");

    }

    printf("客戶端將要被關閉,下次再見\n");

    close(sockfd);

    return 0;

}

使用如下命令進行編譯

gcc -o client client.c && gcc -o server  server.c 

公衆號

瞭解更多網絡安全內容,歡迎關注我的公衆號:無情劍客
在這裏插入圖片描述

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