skynet socket C库代码分析

2017-11-09  本文已影响0人  Tao_Liang

connect

函数定义在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填写打开套接字的请求的参数,重要是申请一个内部套接字idsend_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

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)

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])

实现函数定义在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_TUSERDATALUA_TLIGHTUSERDATALUA_TTABLELUA_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线程在套接字可写事件触发时进行发送。

sendlowsend一样,区别是sendlow不会尝试直接写,并且只会加入到低优先级队列,但是当高低优先级队列为空时会加到高优先级队列。


function nodelay(id)


function shutdown(id)


function close(id)


function buffer()


function push(buffer, buffer_pool, msg, size)

实现函数定义在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)


function drop(msg, size)


function readall(buffer, buffer_pool)


function readline(buffer, table, sep)


function str2p(str)

上一篇 下一篇

猜你喜欢

热点阅读