socket_server 層使用簡單封裝後的 epoll ,向上提供一些列 socket_server_*** 的API。
skynet_socket封裝:
爲了進一步適用於Skynet框架,又進行一步對socket_server進行了封裝,所有常用的接口都封裝在skynet_socket.c 。相比 socket_server.c 的1600多行代碼,skynet_socket.c 則只有200多行,其主要功能都由 socket_server 提供的API來完成。
先來看看 skynet_socket.h
#ifndef skynet_socket_h
#define skynet_socket_h
struct skynet_context;
// 可以看做是對 socket_server 裏返回值類型的封裝
#define SKYNET_SOCKET_TYPE_DATA 1
#define SKYNET_SOCKET_TYPE_CONNECT 2
#define SKYNET_SOCKET_TYPE_CLOSE 3
#define SKYNET_SOCKET_TYPE_ACCEPT 4
#define SKYNET_SOCKET_TYPE_ERROR 5
#define SKYNET_SOCKET_TYPE_UDP 6
#define SKYNET_SOCKET_TYPE_WARNING 7
struct skynet_socket_message {
int type;
int id;
int ud;
char * buffer;
};
void skynet_socket_init();
void skynet_socket_exit();
void skynet_socket_free();
int skynet_socket_poll();
int skynet_socket_send(struct skynet_context *ctx, int id, void *buffer, int sz);
void skynet_socket_send_lowpriority(struct skynet_context *ctx, int id, void *buffer, int sz);
int skynet_socket_listen(struct skynet_context *ctx, const char *host, int port, int backlog);
int skynet_socket_connect(struct skynet_context *ctx, const char *host, int port);
int skynet_socket_bind(struct skynet_context *ctx, int fd);
void skynet_socket_close(struct skynet_context *ctx, int id);
void skynet_socket_shutdown(struct skynet_context *ctx, int id);
void skynet_socket_start(struct skynet_context *ctx, int id);
void skynet_socket_nodelay(struct skynet_context *ctx, int id);
int skynet_socket_udp(struct skynet_context *ctx, const char * addr, int port);
int skynet_socket_udp_connect(struct skynet_context *ctx, int id, const char * addr, int port);
int skynet_socket_udp_send(struct skynet_context *ctx, int id, const char * address, const void *buffer, int sz);
const char * skynet_socket_udp_address(struct skynet_socket_message *, int *addrsz);
#endif
對比一下之前 socket_server.h
#ifndef skynet_socket_server_h
#define skynet_socket_server_h
#include <stdint.h>
#define SOCKET_DATA 0
#define SOCKET_CLOSE 1
#define SOCKET_OPEN 2
#define SOCKET_ACCEPT 3
#define SOCKET_ERROR 4
#define SOCKET_EXIT 5
#define SOCKET_UDP 6
struct socket_server;
struct socket_message {
int id;
uintptr_t opaque;
int ud; // for accept, ud is new connection id ; for data, ud is size of data
char * data;
};
struct socket_server * socket_server_create();
void socket_server_release(struct socket_server *);
int socket_server_poll(struct socket_server *, struct socket_message *result, int *more);
void socket_server_exit(struct socket_server *);
void socket_server_close(struct socket_server *, uintptr_t opaque, int id);
void socket_server_shutdown(struct socket_server *, uintptr_t opaque, int id);
void socket_server_start(struct socket_server *, uintptr_t opaque, int id);
// return -1 when error
int64_t socket_server_send(struct socket_server *, int id, const void * buffer, int sz);
void socket_server_send_lowpriority(struct socket_server *, int id, const void * buffer, int sz);
// ctrl command below returns id
int socket_server_listen(struct socket_server *, uintptr_t opaque, const char * addr, int port, int backlog);
int socket_server_connect(struct socket_server *, uintptr_t opaque, const char * addr, int port);
int socket_server_bind(struct socket_server *, uintptr_t opaque, int fd);
// for tcp
void socket_server_nodelay(struct socket_server *, int id);
struct socket_udp_address;
int socket_server_udp(struct socket_server *, uintptr_t opaque, const char * addr, int port);
int socket_server_udp_connect(struct socket_server *, int id, const char * addr, int port);
int64_t socket_server_udp_send(struct socket_server *, int id, const struct socket_udp_address *, const void *buffer, int sz);
const struct socket_udp_address * socket_server_udp_address(struct socket_server *, struct socket_message *, int *addrsz);
struct socket_object_interface {
void * (*buffer)(void *);
int (*size)(void *);
void (*free)(void *);
};
void socket_server_userobject(struct socket_server *, struct socket_object_interface *soi);
#endif
很是相似吧。
核心是 skynet_socket_poll(),該函數調用了socket_server 層的核心 socket_server_poll(),寫法類似 socket-server 中 test.c 的 _poll() 函數,用於處理 socket_server_poll() 的返回值。
int
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;
case SOCKET_CLOSE:
forward_message(SKYNET_SOCKET_TYPE_CLOSE, false, &result);
break;
case SOCKET_OPEN:
forward_message(SKYNET_SOCKET_TYPE_CONNECT, true, &result);
break;
case SOCKET_ERROR:
forward_message(SKYNET_SOCKET_TYPE_ERROR, true, &result);
break;
case SOCKET_ACCEPT:
forward_message(SKYNET_SOCKET_TYPE_ACCEPT, true, &result);
break;
case SOCKET_UDP:
forward_message(SKYNET_SOCKET_TYPE_UDP, false, &result);
break;
default:
skynet_error(NULL, "Unknown socket message type %d.",type);
return -1;
}
if (more) {
return -1;
}
return 1;
}
絕大部分的返回值都交給了 forward_message() 函數處理,並把之前得到的 result 傳入。這裏重點提出三種用於消息傳輸的結構體:
// 對應於 socket_server 服務中的消息傳輸
struct socket_message {
int id;
uintptr_t opaque; // 在skynet中對應一個Actor實體的handle句柄
int ud; // 對於accept連接來說, ud是新連接的id;對於數據(data)來說, ud是數據的大小
char * data; // 對於數據收發事件,存放數據;對於socket連接事件,存放地址;其他情況下還可以存放打印日誌。
};
// 對應 skynet_socket_server 服務中消息傳輸
struct skynet_socket_message {
int type;
int id;
int ud;
char * buffer;
};
// 在skynet不同服務(Actor)間進行通信
struct skynet_message {
uint32_t source;
int session;
void * data;
size_t sz;
};
再來看看 forward_message():
// mainloop thread
static void
forward_message(int type, bool padding, struct socket_message * result) {
struct skynet_socket_message *sm;
size_t sz = sizeof(*sm);
if (padding) {
if (result->data) {
size_t msg_sz = strlen(result->data);
if (msg_sz > 128) {
msg_sz = 128;
}
sz += msg_sz;
} else {
result->data = "";
}
}
sm = (struct skynet_socket_message *)skynet_malloc(sz); // skynet_malloc() 內部使用了 jemalloc 分配內存
sm->type = type;
sm->id = result->id;
sm->ud = result->ud;
if (padding) {
sm->buffer = NULL;
memcpy(sm+1, result->data, sz - sizeof(*sm));
} else {
sm->buffer = result->data;
}
struct skynet_message message;
message.source = 0;
message.session = 0;
message.data = sm;
message.sz = sz | ((size_t)PTYPE_SOCKET << MESSAGE_TYPE_SHIFT); // 很有意思的寫法,其實就是將類型 PTYPE_SOCKET 值置於 sz 的高8bit,再賦值給 message.sz
// sz的值最大不超過sizeof(struct skynet_socket_message) + 128,該值並不大,高八位並沒有值,沒有數據覆蓋問題。
if (skynet_context_push((uint32_t)result->opaque, &message)) { // 看到 opaque 的作用了吧,其實就是上層handle的標記,按這個標記將信息向上層傳遞
// don't call skynet_socket_close here (It will block mainloop)
skynet_free(sm->buffer);
skynet_free(sm);
}
}
skynet_context_push() 已經涉及到 skynet 的消息調度,關於 skynet 消息調度機制,後面將有博文專門學習。這裏我們就不探尋 skynet_socket_push() 之後的步驟了。
lua_socket與lua封裝:
lua_socket 這一層將 C 代碼封裝成 lua 接口,最後在進行一次lua封裝完成最終提供給用戶的lua API。
int
luaopen_socketdriver(lua_State *L) { // socketdriver,對應lua中 require “socketdriver”
luaL_checkversion(L); // 參見雲風:https://blog.codingnow.com/2012/01/lua_link_bug.html
luaL_Reg l[] = {
{ "buffer", lnewbuffer }, // {"(lua調用時使用的函數名)",C定義函數名}
{ "push", lpushbuffer },
{ "pop", lpopbuffer },
{ "drop", ldrop },
{ "readall", lreadall },
{ "clear", lclearbuffer },
{ "readline", lreadline },
{ "str2p", lstr2p },
{ "header", lheader },
{ "unpack", lunpack },
{ NULL, NULL },
};
luaL_newlib(L,l);
luaL_Reg l2[] = {
{ "connect", lconnect },
{ "close", lclose },
{ "shutdown", lshutdown },
{ "listen", llisten },
{ "send", lsend },
{ "lsend", lsendlow },
{ "bind", lbind },
{ "start", lstart },
{ "nodelay", lnodelay },
{ "udp", ludp },
{ "udp_connect", ludp_connect },
{ "udp_send", ludp_send },
{ "udp_address", ludp_address },
{ NULL, NULL },
};
lua_getfield(L, LUA_REGISTRYINDEX, "skynet_context"); // 這兩句話大致就是存在lua棧LUA_REGISTRYINDEX處的 skynet_context 變量壓入棧頂,同時將其首地址交給 ctx
struct skynet_context *ctx = lua_touserdata(L,-1); // 可是 LUA_REGISTRYINDEX 到底存放了什麼,什麼時候賦的值?
if (ctx == NULL) {
return luaL_error(L, "Init skynet context first");
}
luaL_setfuncs(L,l2,1);
return 1;
}
向lua註冊的函數可以參見表 l 和 l2。我們以 llisten() 爲例,它調用了 C 接口。
static int
llisten(lua_State *L) {
const char * host = luaL_checkstring(L,1); // 從棧獲取 address 字符串
int port = luaL_checkinteger(L,2); // 從棧獲取 port 整型
int backlog = luaL_optinteger(L,3,BACKLOG); // 從棧獲取 backlog 整型,若沒有該參數,使用默認值BACKLOG
struct skynet_context * ctx = lua_touserdata(L, lua_upvalueindex(1));
int id = skynet_socket_listen(ctx, host,port,backlog);
if (id < 0) {
return luaL_error(L, "Listen error");
}
lua_pushinteger(L,id); // 將返回值壓棧
return 1; // 返回值個數
}
接着是最後一次封裝:
local driver = require "socketdriver"
function socket.listen(host, port, backlog)
if port == nil then
host, port = string.match(host, "([^:]+):(.+)$")
port = tonumber(port)
end
return driver.listen(host, port, backlog)
end
有的函數可能沒做封裝,直接賦值,比如 socket.write = assert(driver.send),socket.lwrite = assert(driver.lsend) 的寫法。最後看看雲風wiki上向外提供的對應 lua API:
socket.listen(address, port) 監聽一個端口,返回一個 id ,供 start 使用。 // 官方文檔就是這樣寫的。可能有筆誤,沒寫 backlog 參數。
這裏有幾個相關的宏,目前還不是很清楚其用法:
// ./3rd/lua/luaconfig.h
/*
@@ LUAI_MAXSTACK limits the size of the Lua stack.
** CHANGE it if you need a different limit. This limit is arbitrary;
** its only purpose is to stop Lua from consuming unlimited stack
** space (and to reserve some numbers for pseudo-indices).
*/
#if LUAI_BITSINT >= 32
#define LUAI_MAXSTACK 1000000
#else
#define LUAI_MAXSTACK 15000
#endif
// ./3rd/lua/lua.h
/*
** Pseudo-indices
** (-LUAI_MAXSTACK is the minimum valid index; we keep some free empty
** space after that to help overflow detection)
*/
#define LUA_REGISTRYINDEX (-LUAI_MAXSTACK - 1000)
#define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i))
其他接口類似。
接着看看 skynet 向外提供的 socket lua 接口,常見的接口如下,更多詳細內容見 ./lualib/socket.lua,以及 skynet Socket WiKi
* 發起一個TCP連接: socket.open(address, port)
* 啓動epoll監聽: socket.start(id)
* 從socket讀數據: socket.read(id, sz)
* 向socket寫數據: socket.write(id, str)
* 開啓一個TCP監聽: socket.listen(address, port)
* 關閉socket: socket.close(id)