connect
- 参数: 一个参数ip:port字符串,或者两个参数,分别是ip, port
- 返回值: 套接字内部
id
- 功能: 创建到ip:port的套接字连接
- 示例:
local id = connect('192.168.0.123', 9527)
函数定义在lua_socket.c文件444行:
static int lconnect(lua_State *l)
{
/* 处理ip, port参数 */
int id = skynet_socket_connect(ctx, host, port);
lua_pushinteger(L, id);
return 1
skynet_socket_connect
会调用到socket_server_connect
函数,定义在socket_server.c文件1452行:
int socket_server_connect(struct socket_server *ss, uintptr_t opaque, const char * addr, int port)
{
...
int len = open_request(ss, &request, opaque, addr, port);
...
send_request(ss, &request, 'O', sizeof(request.u.open) + len);
return request.u.open.id;
}
open_request
填写打开套接字的请求的参数,重要是申请一个内部套接字id
,send_request
向管道写入请求消息,socket
线程会在事件监听循环中进行处理,1312行:
int socket_server_poll(struct socket_server *ss, struct socket_message * result, int * more)
{
...
if (has_cmd(ss)) {
int type = ctrl_cmd(ss, result);
...
}
...
}
has_cmd
使用select
来判断管道是否可读来判断是否有请求要处理,ctrl_cmd
读取套接字中的请求,根据请求类型进行处理(后面将跳过这个请求传递和接受的处理,直接跳到请求对应的处理逻辑)
lconnect
的请求类型是O
,处理函数定义在socket_server.c文件469行:
static int open_socket(struct socket_server *ss, struct request_open * request, struct socket_message *result)
{
从request中取参数
getaddrinfo获取可用于创建套接字的参数
status = getaddrinfo( request->host, port, &ai_hints, &ai_list );
创建套接字
设置套接字keepalive选项
设置套接字非阻塞
调用connect(注意,这里是非阻塞的connect)
创建内部套接字描述结构
ns = new_fd(ss, id, sock, PROTOCOL_TCP, request->opaque, true);
...
}
listen
- 参数: ip, port, 可选参数backlog
- 返回值: 套接字内部
id
- 功能: 创建监听在ip:port上的套接字
- 示例:
local id = listen('192.168.0.123', 9527)
listen
将套接字创建,绑定端口,对套接字监听三步进行了封装,socket_server.c文件1631行:
int socket_server_listen(struct socket_server *ss, uintptr_t opaque, const char * addr, int port, int backlog)
{
do_listen创建套接字,绑定端口,然后调用listen监听此套接字
int fd = do_listen(addr, port, backlog);
...
send_request(ss, &request, 'L', sizeof(request.u.listen));
...
}
function start(id)
-
参数:
- id: 套接字内部
id
- id: 套接字内部
返回值: 无
功能:将accept的新套接字或者创建的监听套接字加入到事件监听当中
示例 start(id)
socket_server.c文件1663行:
void socket_server_start(struct socket_server *ss, uintptr_t opaque, int id)
{
...
send_request(ss, &request, 'T', sizeof(request.u.setopt));
}
给socket
线程发送'T'请求,处理函数定义在935行:
static int start_socket(struct socket_server *ss, struct request_start *request, struct socket_message *result)
{
...
如果套接字类型是accept的套接字或者是监听套接字,则将套接字加入到事件监听
if (s->type == SOCKET_TYPE_PACCEPT || s->type == SOCKET_TYPE_PLISTEN) {
if (sp_add(ss->event_fd, s->fd, s)) {
...
}
}
...
}
由此可看出,在调用listen
创建了监听套接字后,需要再调用start
函数将套接字加入到事件监听中
function send(id, data[, size])
-
参数:
- id: 内部套接字id
- data: 字符串、字符串数组、用户定义数据
- size: 当data是用户定义数据时需要提供
返回值: true成功,false失败
功能: 发送网络数据
实现函数定义在lua_socket.c文件556行:
static int lsend(lua_State *L)
{
...
void *buffer = get_buffer(L, 2, &sz);
int err = skynet_socket_send(ctx, id, buffer, sz);
...
}
get_buffer
对参数data进行解析,支持LUA_TUSERDATA
,LUA_TLIGHTUSERDATA
, LUA_TTABLE
,LUA_TSTRING
类型,TABLE类型支持字符串数组,解析时会将字符串数组元素进行拼接,skynet_socket_send
会调用到socket_server_send
:
int socket_server_send(struct socket_server *ss, int id, const void * buffer, int sz)
{
struct socket * s = &ss->slot[HASH_ID(id)];
...
// 如果可以直接写入,该套接字发送数据缓冲队列均为空
if (can_direct_write(s,id) && socket_trylock(&l)) {
...
if (s->protocol == PROTOCOL_TCP) {
n = write(s->fd, so.buffer, so.sz);
}
}
...
//如果发送失败,需要将数据挂到缓冲队列,并监听套接字的可写事件
s->dw_buffer = buffer;
...
sp_write(ss->event_fd, s->fd, s, true);
...
return 0
}
填写request参数,发送数据请求
send_request(ss, &request, 'D', sizeof(request.u.send));
return 0
}
当套接字不可直接写入或者获取锁失败或者直接写失败,就会委托socket
线程来处理发送数据的请求,D
类型对应的高优先的写缓冲队列,处理函数定义在socket_server.c文件801行,本质上就是列表结构的操作,把要写的数据挂到写缓冲队列上,由socket
线程在套接字可写事件触发时进行发送。
sendlow
和send
一样,区别是sendlow
不会尝试直接写,并且只会加入到低优先级队列,但是当高低优先级队列为空时会加到高优先级队列。
function nodelay(id)
-
参数:
- id: 内部套接字id
返回值: 无
功能: 设置套接字TCP_NODELAY选项
function shutdown(id)
-
参数:
- id: 内部套接字id
返回值: 无
功能: 关闭套接字,关闭前会发送缓冲区数据,若一次没发送完则会强制关闭
function close(id)
-
参数:
- id: 内部套接字id
返回值: 无
功能: 关闭套接字,关闭前会发送缓冲区数据,若一次没发送完,则会将套接字设置为半关闭状态,数据发送完之后才会关闭,
shutdown
相比close
是暴力强制关闭一个套接字
function buffer()
- 参数: 无
- 返回值: userdata
struct socket_buffer
- 功能: 返回一个套接字缓冲区结构的用户定义数据
function push(buffer, buffer_pool, msg, size)
-
参数:
- buffer:
buffer
返回的struct socket_buffer
userdata - buffer_pool:
struct buffer_node
pool, LUA TABLE类型 - msg: userdata类型,消息数据
- size:
msg
长度
- buffer:
返回值: 当前
buffer
缓冲的数据长度功能呢: 将收到的消息
msg
缓存在buffer
中
实现函数定义在lua_socket.c文件98行:
static int lpushbuffer(lua_State *L)
{
// 提取参数
struct socket_buffer *sb = lua_touserdata(L,1);
char * msg = lua_touserdata(L,3);
int sz = luaL_checkinteger(L,4);
// free_node = buffer_pool[1]
// buffer_pool[1]是空闲buffer_node链表,当为空时会再分配填充
lua_rawgeti(L,2,1);
struct buffer_node * free_node = lua_touserdata(L,-1);
if (free_node == NULL) {
...
// 分配size个struct buffer_node
lnewpool(L, size);
free_node = lua_touserdata(L,-1);
...
}
// buffer_pool[1] = free_node->next
lua_pushlightuserdata(L, free_node->next);
lua_rawseti(L, pool_index, 1);
// 将msg挂在buffer_node上之后,buffer_node会在挂到socket_buffer链表中
...
}
push
是将收到的消息msg
进行缓存.
function pop(buffer, buffer_pool, size)
-
参数:
- buffer:
buffer
返回的struct socket_buffer
userdata - buffer_pool:
struct buffer_node
pool, LUA TABLE类型 - size: 需要从
buffer
中取的数据大小
- buffer:
-
返回值:
- 当缓冲区
buffer
中数据不足size时或size==0,则返回nil,buffer
数据长度 -
size
长度的数据,buffer
剩余数据长度
- 当缓冲区
功能: 从
buffer
中取size长度的数据
function drop(msg, size)
- 参数:
- msg: 收到的消息
- size: 收到的消息长度
- 返回值: 无
- 功能: 丢弃收到的消息
msg
,并释放其占有的内存空间
function readall(buffer, buffer_pool)
- 参数:
- buffer:
buffer
返回的struct socket_buffer
userdata - buffer_pool:
struct buffer_node
pool, LUA TABLE类型
- buffer:
- 返回值: 收到的消息数据
- 功能: 读取
buffer
中所有的数据
function readline(buffer, table, sep)
- 参数:
- buffer:
buffer
返回的struct socket_buffer
userdata - table: 未用到此参数,socket.lua中填写的是
nil
或者是buffer_pool
- sep: 一行的分隔串
- buffer:
- 返回值:
- 当
buffer
缓冲区没有找到sep
时返回nil
- 当
buffer
缓冲区中找到sep
,table
参数是非TABLE类型,返回true
,否则返回不包含sep
的一行数据
- 当
- 功能: 读取已
sep
为分隔的一行数据
function str2p(str)
- 参数:
- str: LUA字符串
- 返回值:
- lightuserdata,LUA字符串在C中的副本的指针
- size, C中字符串长度
- 功能: 将LUA string转换成C字符串,并返回字符串指针及字符串长度