skynet socket C库代码分析


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

  • 参数: 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
  • 返回值: 无

  • 功能:将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_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)

  • 参数:

    • 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缓冲的数据长度

  • 功能呢: 将收到的消息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中数据不足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中所有的数据

function readline(buffer, table, sep)

  • 参数:
    • buffer: buffer返回的struct socket_buffer userdata
    • table: 未用到此参数,socket.lua中填写的是nil或者是buffer_pool
    • sep: 一行的分隔串
  • 返回值:
    • buffer缓冲区没有找到sep时返回nil
    • buffer缓冲区中找到septable参数是非TABLE类型,返回true,否则返回不包含sep的一行数据
  • 功能: 读取已sep为分隔的一行数据

function str2p(str)

  • 参数:
    • str: LUA字符串
  • 返回值:
    • lightuserdata,LUA字符串在C中的副本的指针
    • size, C中字符串长度
  • 功能: 将LUA string转换成C字符串,并返回字符串指针及字符串长度
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容