【Skynet】Socket源碼剖析二

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)


發佈了129 篇原創文章 · 獲贊 47 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章