《Linux網路開發必學教程》12_TCP通訊框架:服務端設計

TianSong發表於2022-05-11
問題:如何設計與客戶端對應的服務端?

TCP 通訊框架設計

image.png

服務端

  • 負責監聽連線狀態

    • Connect : 產生通訊客戶端(TcpClient), 並給出事件通知
    • Close : 給出事件通知,並銷燬客戶端
  • 負責監聽資料通訊狀態,並給出事件通知

服務端事件設計

  • EVT_CONN: 客戶端連線服務端時觸發,並建立 TcpClient 用於通訊
  • EVT_DATA: 客戶端資料到達服務端時觸發,使用 TcpClient 讀取資料
  • EVT_CLOSE: 客戶端斷開服務時觸發,相關 TcpClient 將銷燬

image.png

問題:服務端如何知道什麼時候進行事件回撥通知?

服務端通過 select 機制觸發事件回撥!

image.png

服務端介面設計

typedef void TcpServer;
typedef void (*Listener)(TcpClient*, int);

enum {
    EVT_CONN,
    EVT_DATA,
    EVT_CLOSE
};

TcpServer *TcpServer_New();
int TcpServer_Start(TcpServer *server, int port, int max);
void TcpServer_Stop(TcpServer *server);
void TcpServer_SetListener(TcpServer *server, Listener listener);
int TcpServer_IsValid(TcpServer *server);
void TcpServer_DoWork(TcpServer *server);
void TcpServer_Del(TcpServer *server);

服務端關鍵程式碼實現 - 初始化

typedef struct tcp_server {
    int fd;
    int valid;
    Listener cb;
    TcpClient *client[FD_SIZE];
}Server;

TcpServer *TcpServer_New()
{
    Server *ret = malloc(sizeof(Server));

    if (ret) {
        int i = 0;

        ret->fd = -1;
        ret->valid = 0;
        ret->cb = NULL;
    }

    return ret;
}

服務端關鍵程式碼實現 - 事件監聽

FD_ZERO(&reads);
FD_SET(s->fd, &reads);

max = s->fd;

while (s->valid) {
    rset = reads;
    
    timeout.tv_sec = 0;
    timeout.tv_usec = 5000;

    num = select(max+1, &rset, 0, 0, &timeout);

    if (num > 0) {
        max = SelectHandler(s, &rset, &reads, num, mac);
    }
}

伺服器關鍵程式碼實現 - 連線事件 & 資料事件

if (index == s->fd) {  // 連線事件
    struct sockaddr_in caddr = {0};
    socklen_t asize = sizeof(caddr);
    
    index = accept(s->fd, (struct sockaddr*)&addr, &asize);

    if (index > -1) {
        FD_SET(index, reads);

        ret = (index > max) ? index : max;
        
        s->client[index] = TcpClient_From(index);

        event = EVT_CONN;
    }
} else {    // 資料事件
    event = EVT_DATA;
}

伺服器關鍵程式碼實現 - 斷開事件 & 事件通知

if (s->cb) {
    if (TcpClient_IsValid(s->client[index])) {
        s->cb(s->client[index], event);  // EVT_CONN & EVT_DATA 事件通知
    } else {
        if (s->client[index]) {
            s->cb(s->client[index], EVT_CLOSE);  // 斷連事件通知
            
            TcpClient_Del(s->client[index]);

            s->client[index] = NULL;
            
            FD_CLR(index, reads);
        }
    }
}

程式設計實驗

tcp_server.h
#ifndef TCP_SERVER_H
#define TCP_SERVER_H

#include "tcp_client.h"

typedef void TcpServer;
typedef void (*Listener)(TcpClient*, int);

enum {
    EVT_CONN,
    EVT_DATA,
    EVT_CLOSE
};

TcpServer *TcpServer_New();
int TcpServer_Start(TcpServer *server, int port, int max);
void TcpServer_Stop(TcpServer *server);
void TcpServer_SetListener(TcpServer *server, Listener listener);
int TcpServer_IsValid(TcpServer *server);
void TcpServer_DoWork(TcpServer *server);
void TcpServer_Del(TcpServer *server);

#endif
tcp_server.c
#include "tcp_server.h"
#include "tcp_client.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <netinet/tcp.h> 
#include <arpa/inet.h>
#include <unistd.h>
#include <malloc.h>

#define FD_SIZE 1024

typedef struct tcp_server {
    int fd;
    int valid;
    Listener cb;
    TcpClient *client[FD_SIZE];
}Server;

TcpServer *TcpServer_New()
{
    Server *ret = malloc(sizeof(Server));

    if (ret) {
        int i = 0;

        ret->fd = -1;
        ret->valid = 0;
        ret->cb = NULL;

        for (i=0; i<FD_SIZE; ++i) {
            ret->client[i] = NULL;
        }
    }   

    return ret;
}

int TcpServer_Start(TcpServer *server, int port, int max)
{
    Server *s = (Server*)server;

    if (s && !s->valid) {
        struct sockaddr_in saddr = {0};

        s->fd = socket(PF_INET, SOCK_STREAM, 0);

        s->valid = (s->fd != -1);

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

        s->valid = s->valid && (bind(s->fd, (struct sockaddr*)&saddr, sizeof(saddr)) != -1);
        s->valid = s->valid && (listen(s->fd, max) != -1);
    }

    return s->valid;
}

void TcpServer_Stop(TcpServer *server)
{
    Server *s = (Server*)server;

    if (s) {
        int i =0;

        s->valid = 0;

        close(s->fd);

        for (i=0; i<FD_SIZE; ++i) {
            TcpClient_Del(s->client[i]);
            s->client[i] = NULL;
        }
    }
}

void TcpServer_SetListener(TcpServer *server, Listener listener)
{
    Server *s = (Server*)server;

    if (s) {
        s->cb = listener;
    }
}

int TcpServer_IsValid(TcpServer *server)
{
    return server ? ((Server*)server)->valid : 0;
}

static int SelectHandler(Server *s, fd_set *rset, fd_set *reads, int num, int max)
{
    int ret = max;
    int i =0;

    for (i=0; i<=max; ++i) {
        if (FD_ISSET(i, rset)) {
            int index = i;
            int event = -1;

            if (index == s->fd) {
                struct sockaddr_in caddr = {0};
                socklen_t asize = sizeof(struct sockaddr_in);

                index = accept(s->fd, (struct sockaddr*)&caddr, &asize);

                if (index > -1) {
                    FD_SET(index, reads);

                    ret = (index > max) ? index : max;

                    s->client[index] = TcpClient_From(index);

                    event = EVT_CONN;
                }
            }
            else {
                event = EVT_DATA;
            }
         
            if (s->cb) {
                if (TcpClient_IsValid(s->client[index])) {
                    s->cb(s->client[index], event);
                } else {
                    if (s->client[index]) {
                        s->cb(s->client[index], EVT_CLOSE);
                    }

                    TcpClient_Del(s->client[index]);

                    s->client[index] = NULL;

                    FD_CLR(index, reads);
                }
            }
        }
    }

    return ret;
}

void TcpServer_DoWork(TcpServer *server)
{
    Server *s = (Server*)server;

    if (s && s->valid) {
        int max = 0;
        int num = 0;
        fd_set reads = {0};
        fd_set rset = {0};
        struct timeval timeout = {0};

        FD_ZERO(&reads);
        FD_SET(s->fd, &reads);

        max = s->fd;

        while (s->valid) {
            rset = reads;

            timeout.tv_sec = 0;
            timeout.tv_usec = 10000;

            num = select(max + 1, &rset, 0, 0, &timeout);

            if (num > 0) {
                max = SelectHandler(s, &rset, &reads, num, max);
            }
        }
    }
}

void TcpServer_Del(TcpServer *server)
{
    TcpServer_Stop(server);
    free(server);
}
測試:server.c

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include "tcp_server.h"

void EventListener(TcpClient *client, int evt) 
{
    if (evt == EVT_CONN) {
        printf("Connect: %p\n", client);
    } else if (evt == EVT_DATA) {
        Message *m = TcpClient_RecvMsg(client);

        if (m) {
            char *s = TcpClient_GetDate(client);

            if (m->index == 0) {
                s = malloc(m->total + 1);

                TcpClient_SetData(client, s);
            }

            strcpy(s+m->index, m->payload);

            if ((m->index + 1) == m->total) {
                printf("Data: %s\n", s);
                free(s);
            }

            free(m);
        }
    }
    else if (evt == EVT_CLOSE) {
        printf("close: %p\n", client);
    }
}

int main()
{   
    TcpServer *server = TcpServer_New();

    if (server) {
        int r = TcpServer_Start(server, 8888, 20);

        if (r) {
            TcpServer_SetListener(server, EventListener);
            TcpServer_DoWork(server);
        }
    }

    return 0;
}
輸出:
Connect: 0x5602eb035690
Data: D.T.Software
close: 0x5602eb035690

相關文章