《Linux網路開發必學教程》8_應用協議設計與實現

TianSong發表於2022-05-05
問題:下面的程式碼輸出什麼?為什麼?
printf("connect success\n");

send(sock, "A", 1, 0);
send(sock, "B", 1, 0);
send(sock, "C", 1, 0);

close(sock);
do {
    r = recv(client, buf, sizeof(buf), 0);
    if (r > 0) {
        printf("Recv: %s\n", buf);
    }
}while (1);

close(client);
完整程式碼 client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int sock = 0;
    struct sockaddr_in addr = {0};
    int len = 0;
    char buf[128] = {0};
    char input[32] = {0};
    int r = 0;

    sock = socket(PF_INET, SOCK_STREAM, 0);

    if (sock == -1) {
        printf("socket error\n");
        return -1;
    }

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(8888);

    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        printf("connect error\n");
        return -1;
    }

    printf("connect success\n");

    send(sock, "A", 1, 0);

    send(sock, "B", 1, 0);

    send(sock, "C", 1, 0);

    close(sock);

    return 0;
}
完整程式碼:server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int server = 0;
    struct sockaddr_in saddr = {0};
    int client = 0;
    struct sockaddr_in caddr = {0};
    socklen_t asize = 0;
    int len = 0;
    char buf[32] = {0};
    int r = 0;

    server = socket(PF_INET, SOCK_STREAM, 0);

    if (server == -1) {
        printf("server socket error\n");
        return -1;
    }

    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    saddr.sin_port = htons(8888);

    if (bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) {
        printf("server bind error\n");
        return -1;
    }

    if (listen(server, 1) == -1) {
        printf("server listen error\n");
        return -1;
    }

    printf("server start success\n");

    while (1) {
        asize = sizeof(caddr);

        client = accept(server, (struct sockaddr*)&caddr, &asize);

        if (client == -1) {
            printf("client accept error\n");
            return -1;
        }

        printf("client: %d\n", client);

        do {
            r = recv(client, buf, sizeof(buf), 0);
            if (r > 0) {
                printf("Receive: %s\n", buf);  
            } 
        } while (r > 0);

        close(client);
    }

    close(server);

    return 0;
}
輸出
server start success
client: 4
Receive: ABC

小知識

  • 傳送緩衝區

    • 資料先進入傳送緩衝區,之後由作業系統送往遠端主機
  • 接收緩衝區

    • 遠端資料被作業系統接受後放入接收緩衝區
    • 之後應用程式從接收緩衝區讀取資料

image.png

TCP 應用程式設計中的 “問題”

資料接收端無法知道資料的傳送方式

image.png

接收端無法知道 "ABC" 是分開傳送的!

網路程式設計中的期望

  • 每次傳送一條完整的消息,每次接收一條完整的訊息
  • 即使接收緩衝區中有多條訊息,也不會出現訊息粘連
  • 訊息中涵蓋了資料型別和資料長度等資訊

應用層協議設計

  • 什麼是協議?

    • 協議是通訊雙方為資料交換而建立的規則標準預定的集合
  • 協議對資料傳輸的作用

    • 通訊雙方根據協議能夠正確收發資料
    • 通訊雙方根據協議能夠解釋資料的意義

協議設計示例

  • 目標:設計可用於資料傳輸的協議
  • 完整訊息包含

    • 資料頭:資料型別(即:資料區用途,固定長度)
    • 資料長度:資料區長度(固定長度)
    • 資料區:位元組資料(變長區域)

image.png

上圖可知:
訊息至少 12 個位元組(訊息頭 + 資料長度)
通過計算訊息的總長度,能夠避開資料粘連的問題
typedef struct {
    unsigned short type;
    unsigned short cmd;
    unsigned short index;
    unsigned short total;
    unsigned int length;
    unsigned char payload[];  // 柔性陣列
}Message;

程式設計實驗:應用層協議設計與實現

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

#include "message.h"

int main()
{
    int sock = 0;
    struct sockaddr_in addr = {0};
    int len = 0;
    char buf[128] = {0};
    char input[32] = {0};
    int r = 0;
    Message *pm = NULL;

    sock = socket(PF_INET, SOCK_STREAM, 0);

    if (sock == -1) {
        printf("socket error\n");
        return -1;
    }

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(8888);

    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        printf("connect error\n");
        return -1;
    }

    printf("connect success\n");

    pm = Message_New(0, 0, 1, 3, "A", 1);
    send(sock, pm, sizeof(Message) + 1, 0);

    pm = Message_New(0, 0, 2, 3, "B", 1);
    send(sock, pm, sizeof(Message) + 1, 0);

    pm = Message_New(0, 0, 3, 3, "C", 1);
    send(sock, pm, sizeof(Message) + 1, 0);

    close(sock);

    return 0;
}
server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int server = 0;
    struct sockaddr_in saddr = {0};
    int client = 0;
    struct sockaddr_in caddr = {0};
    socklen_t asize = 0;
    int len = 0;
    char buf[32] = {0};
    int r = 0;

    server = socket(PF_INET, SOCK_STREAM, 0);

    if (server == -1) {
        printf("server socket error\n");
        return -1;
    }

    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    saddr.sin_port = htons(8888);

    if (bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) {
        printf("server bind error\n");
        return -1;
    }

    if (listen(server, 1) == -1) {
        printf("server listen error\n");
        return -1;
    }

    printf("server start success\n");

    while (1) {
        asize = sizeof(caddr);

        client = accept(server, (struct sockaddr*)&caddr, &asize);

        if (client == -1) {
            printf("client accept error\n");
            return -1;
        }

        printf("client: %d\n", client);

        do {
            r = recv(client, buf, sizeof(buf), 0);
            if (r > 0) {
                int i = 0;
                for (i=0; i<r; ++i) {
                    printf("%02x", buf[i]);
                }
                printf("\n");
            } 
        } while (r > 0);

        close(client);
    }

    close(server);

    return 0;
}
輸出
0000000001000300010000004100000000020003000100000042000000000300
03000100000043

思考:如何在程式碼層封裝協議細節(僅關係訊息本身)?

相關文章