接觸skynet 大半年了 ,最近在研究 skynet 的源碼 並且在仿寫一個類似的服務程序。發現自己是在開着多個線程學習 。so 開始做一下簡單的現場保護。。
先大致記錄一下socket模塊源碼文件的作用
- socket_epoll.c // epoll 機制 相關api 的簡單封裝
- socket_server.c //socket 的主要實現文件
- skynet_socket.c // 對socket_server 的進一步封裝 使他更加容易使用
- skynet_start.c //這個主要是框架的啓動代碼 so socket 也是着這裏啓動的
運行流程
- 在skynet_start() 中 調用 skynet_socket_init() 初始化socket服務
- 接着在start()中 創建一個socket 服務線程
- 線程執行函數分析:
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;
}
- 進入 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;
}
- 繼續挖掘 進入 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循環中再去處理他。
一些細節
- socket 在發送數據時 會嘗試的直接發送數據!如果不能直接發送數據 纔會把數據寫入 socket 對應的寫緩存 。 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];
};