skynet 筆記 :socket 模塊

接觸skynet 大半年了 ,最近在研究 skynet 的源碼 並且在仿寫一個類似的服務程序。發現自己是在開着多個線程學習 。so 開始做一下簡單的現場保護。。


先大致記錄一下socket模塊源碼文件的作用

  • socket_epoll.c // epoll 機制 相關api 的簡單封裝
  • socket_server.c //socket 的主要實現文件
  • skynet_socket.c // 對socket_server 的進一步封裝 使他更加容易使用
  • skynet_start.c //這個主要是框架的啓動代碼 so socket 也是着這裏啓動的

運行流程
  1. 在skynet_start() 中 調用 skynet_socket_init() 初始化socket服務
  2. 接着在start()中 創建一個socket 服務線程
  3. 線程執行函數分析:
thread_socket(void *p) {
    struct monitor * m = p; //這是框架的一個管理者
    skynet_initthread(THREAD_SOCKET);
    //這個線程其實就是在不停的 skynet_socket_poll
    for (;;) {
        int r = skynet_socket_poll();
        if (r==0)
            break;
        if (r<0) {
            CHECK_ABORT
            continue;
        }
        wakeup(m,0);
    }
    return NULL;
}
  1. 進入 skynet_socket_poll()
//這個函數的主要工作是將 socket_server_poll 的數據 轉換成 skynet 通信機制中使用的格式 以便分發數據
// socket_sever_poll 返回的數據是  socket_message 
// 在forward_message 中將數據變爲 skynet_message 並且將消息壓入 二級隊列 (每個服務模塊的私有隊列)
skynet_socket_poll() {
    struct socket_server *ss = SOCKET_SERVER;
    assert(ss);
    struct socket_message result;
    int more = 1;
    int type = socket_server_poll(ss, &result, &more);
    switch (type) {
    case SOCKET_EXIT:
        return 0;
    case SOCKET_DATA:
        forward_message(SKYNET_SOCKET_TYPE_DATA, false, &result);
        break;
        /*  省略 */
    default:
        skynet_error(NULL, "Unknown socket message type %d.",type);
        return -1;
    }
    if (more) {
        return -1;
    }
    return 1;
}
  1. 繼續挖掘 進入 socket_server_poll 一趟究竟 !注意 這纔是socket 的核心實現 。如果您有幸看到這裏 ,請先大致看看 ,看完整個文章 在回過頭來琢磨。
socket_server_poll(struct socket_server *ss, struct socket_message * result, int * more) {
    for (;;) {
        //先判斷有木有 cmd 需要處理 (這裏懵逼的話 看完文章就明白了 )
        if (ss->checkctrl) {
            if (has_cmd(ss)) {
                int type = ctrl_cmd(ss, result);
                if (type != -1) {
                    clear_closed_event(ss, result, type);
                    return type;
                } else
                    continue;
            } else {
                ss->checkctrl = 0;
            }
        }


}
/*
        在這裏看一下 socket_server  結構中部分數據的說明吧  下面的代碼用得着
        struct socket_server {
        int recvctrl_fd;    //接受管道
        int sendctrl_fd;    //發送管道
        int checkctrl;      //釋放檢測命令
        poll_fd event_fd;   //epoll 的fd 
        int alloc_id;       //應用層分配id用的
        int event_n;        //epoll_wait 返回的事件數
        int event_index;    //當前處理的事件序號
        struct socket_object_interface soi; 
        struct event ev[MAX_EVENT];     //epoll_wait 返回的事件集合
        struct socket slot[MAX_SOCKET]; //每個Socket server可以包含多個Socket,這是存儲這些Socket的數組(應用層預先分配的)
        char buffer[MAX_INFO];          //臨時數據的保存,比如保存對方的地址信息等
        uint8_t udpbuffer[MAX_UDP_PACKAGE];
        fd_set rfds;                    //用於select的fd集
    };
//這裏開始處理socket 的讀寫 
//event_index當前處理的事件序號==事件總數  等價於  之前epoll到的事件都處理完了
if (ss->event_index == ss->event_n)
     {
            //所以這裏繼續去 查詢有沒有要處理的事件
            ss->event_n = sp_wait(ss->event_fd, ss->ev, MAX_EVENT);
            ss->checkctrl = 1;
            if (more) {
                *more = 0;
            }
            ss->event_index = 0;
            //發生錯誤
            if (ss->event_n <= 0) {
                ss->event_n = 0;
                //忽略EINTR錯誤
                if (errno == EINTR) {
                    continue;
                }
                return -1;
            }
        }
        //取出一個事件來處理
        struct event *e = &ss->ev[ss->event_index++];
        struct socket *s = e->s;
        if (s == NULL) {
            // dispatch pipe message at beginning
            continue;
        }
        struct socket_lock l;
        socket_lock_init(s, &l);
        //根據socket 的類型 處理
        switch (s->type) {
        case SOCKET_TYPE_CONNECTING:
            return report_connect(ss, s, &l, result);
        case SOCKET_TYPE_LISTEN: {
            int ok = report_accept(ss, s, result);
            if (ok > 0) {
                return SOCKET_ACCEPT;
            } if (ok < 0 ) {
                return SOCKET_ERR;
            }
            // when ok == 0, retry
            break;
        }
        case SOCKET_TYPE_INVALID:
            fprintf(stderr, "socket-server: invalid socket\n");
            break;
        //默認的是處理 socket 的讀寫操作
        default:
        //讀
            if (e->read) {
                int type;
                if (s->protocol == PROTOCOL_TCP) {
                    type = forward_message_tcp(ss, s, &l, result);
                } else {
                    type = forward_message_udp(ss, s, &l, result);
                    if (type == SOCKET_UDP) {
                        // try read again
                        --ss->event_index;
                        return SOCKET_UDP;
                    }
                }
                if (e->write && type != SOCKET_CLOSE && type != SOCKET_ERR) {
                    // Try to dispatch write message next step if write flag set.
                    e->read = false;
                    --ss->event_index;
                }
                if (type == -1)
                    break;              
                return type;
            }
            //寫
            if (e->write) {
                int type = send_buffer(ss, s, &l, result);
                if (type == -1)
                    break;
                return type;
            }
            //錯誤處理
            if (e->error) {
                // close when error
                int error;
                socklen_t len = sizeof(error);  
                int code = getsockopt(s->fd, SOL_SOCKET, SO_ERROR, &error, &len);  
                const char * err = NULL;
                if (code < 0) {
                    err = strerror(errno);
                } else if (error != 0) {
                    err = strerror(error);
                } else {
                    err = "Unknown error";
                }
                force_close(ss, s, &l, result);
                result->data = (char *)err;
                return SOCKET_ERR;
            }
            break;
        }
    }

結構分析

這裏寫圖片描述
每個socket 服務都有 寫緩存隊列,所以 框架會異步的實現讀寫。

socket 的open close listen apect 等操作是通過給 socket_server 的管道寫入請求信息,在server_poll循環中再去處理他。


一些細節

  1. socket 在發送數據時 會嘗試的直接發送數據!如果不能直接發送數據 纔會把數據寫入 socket 對應的寫緩存 。
  2. 2.
struct socket {
    uintptr_t opaque;     //這個其實就是每個服務對應的handle
    struct wb_list high;  //寫緩存
    struct wb_list low;
    int64_t wb_size;
    int fd;              //socket fd
    int id;              //爲該socket 分配的id 供應用層座位標識使用  
    uint8_t protocol;    //udp or tcp
    uint8_t type;        //該socket 目前的狀態類型 
    uint16_t udpconnecting; 
    int64_t warn_size;    
    union {
        int size;
        uint8_t udp_address[UDP_ADDRESS_SIZE];
    } p;
    //直接發送數據的緩存 
    struct spinlock dw_lock;  //direct_write_XXX
    int dw_offset;
    const void * dw_buffer;
    size_t dw_size;
};
    3.
//這個結構體看起來很奇怪  。  其實這是利用 union 的特性。  
//多個成員共用一塊內存  u 中這些 結構體 在使用時 只是用其中的一種  這可以讓這些類型 公用這塊內存   
struct request_package {
    uint8_t header[8];  // 6 bytes dummy
    union {
        char buffer[256];
        struct request_open open;
        struct request_send send;
        struct request_send_udp send_udp;
        struct request_close close;
        struct request_listen listen;
        struct request_bind bind;
        struct request_start start;
        struct request_setopt setopt;
        struct request_udp udp;
        struct request_setudp set_udp;
    } u;
    uint8_t dummy[256];
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章