remote_send_cb这个回调函数的工作是将从客户端收取来的数据转发给ss-server。在之前阅读server_recv_cb代码时可以看到,在STAGE_STREAM阶段有几种可能都会开启remote->fd的写事件的监听,从而当有写事件触发时调用remote_send_cb。从代码结构看,外层的分支是remote->send_ctx->connected是否为0,内部的分支是是否fast_open或直连。而根据实际代码执行流向,可看成是否有fast_open或直接。而最普通的情况就是没开启fast_open或者remote为直连目标服务器的情况,先讨论这种情况下的connected分支:

1. remote->send_ctx->connected为0时,即第一次进入STAGE_STREAM,尚未连接remote server时。

在server_recv_cb中直接调用非阻塞connect后,开启事件监听:

ev_io_start(EV_A_ & remote->send_ctx->io);

ev_timer_start(EV_A_ & remote->send_ctx->watcher);

因为非阻塞fd调用connect后,当connect成功后,fd可写。所以这儿监听了send_ctx->io即写事件(回忆一下:```ev_io_init(&remote->send_ctx->io, remote_send_cb, fd, EV_WRITE);```)

现在看一下remote_send_cb函数中,当connect成功回调cb时,由于此时 remote->send_ctx->connected==0,所以会进入:

if (!remote_send_ctx->connected) {
        struct sockaddr_storage addr;
        socklen_t len = sizeof addr;
        int r         = getpeername(remote->fd, (struct sockaddr *)&addr, &len);
        if (r == 0) {
            remote_send_ctx->connected = 1;
            ev_timer_stop(EV_A_ & remote_send_ctx->watcher);
            ev_timer_start(EV_A_ & remote->recv_ctx->watcher);
            ev_io_start(EV_A_ & remote->recv_ctx->io);

            // no need to send any data
            if (remote->buf->len == 0) {
                ev_io_stop(EV_A_ & remote_send_ctx->io);
                ev_io_start(EV_A_ & server->recv_ctx->io);
                return;
            }
        } else {
            // not connected
            ERROR("getpeername");
            close_and_free_remote(EV_A_ remote);
            close_and_free_server(EV_A_ server);
            return;
        }
    }

这儿通过getpeername获取对端地址,即服务器地址,如果返回值为0表示获取成功,因此判断为连接成功(其实我不明白为什么要判断,但是这么判断肯定没错)。连接成功后设置remote_send_ctx->connected=1,并且stop remote send timer。说明这种情况下先前设置的remote send timer只是用来做connect超时用。然后start remote recv的timer,且start remote fd的读事件监听(回忆一下:ev_io_init(&remote->recv_ctx->io, remote_recv_cb, fd, EV_READ);),即准备从remote接受返回的数据

这之后判断remote->buf是否为空,如果为空则停止发送并开始监听读取客户端的数据。当前这种情况,buf不可能为空,因为至少还有socks5相关的头,即之前的abuf。如果getpeername返回非0则表示连接失败,关闭remote和server。

继续看remote_send_cb的后半段:

if (remote->buf->len == 0) {
        // close and free
        close_and_free_remote(EV_A_ remote);
        close_and_free_server(EV_A_ server);
        return;
    } else {
        // has data to send
        ssize_t s = send(remote->fd, remote->buf->data + remote->buf->idx,
                         remote->buf->len, 0);
        if (s == -1) {
            if (errno != EAGAIN && errno != EWOULDBLOCK) {
                ERROR("remote_send_cb_send");
                // close and free
                close_and_free_remote(EV_A_ remote);
                close_and_free_server(EV_A_ server);
            }
            return;
        } else if (s < (ssize_t)(remote->buf->len)) {
            // partly sent, move memory, wait for the next time to send
            remote->buf->len -= s;
            remote->buf->idx += s;
            return;
        } else {
            // all sent out, wait for reading
            remote->buf->len = 0;
            remote->buf->idx = 0;
            ev_io_stop(EV_A_ & remote_send_ctx->io);
            ev_io_start(EV_A_ & server->recv_ctx->io);
        }
    }

如果buf为空则关闭连接,目前应该不会为空,所以走到else里面。使用send发送数据,发送的数据的指针是remote->buf->data+remote->buf->idx,回忆一下server_recv_cb里面,如果未connected的情况下,这个idx是设置为0的,所以这儿发送数据就是整个buf的数据。因为remote->fd是非阻塞的,send调用后就立即返回了,如果返回值s是-1,要检查一下errno是否为EAGAIN或EWOULDBLOCK,如果不是他俩就真出错了,断开连接;如果是他俩说明要等一下再发送,直接return出去等下次remote_send_cb被回调。下次再回调进来时,remote_send_ctx->connected已经是1了,所以直接进下半段代码继续send。

如果s小于buf->len,说明发送了部分数据,需要调整idx的位置并从len减去s。等下次写事件触发可以继续发送时,就从idx的位置继续发送。

如果s等于buf->len说明全部发送完毕,len和idx清0,stop remote fd的写事件监听并sart server fd的读事件监听,即继续从客户端读取数据。

小结一下上面的讨论:是不使用fast_open或是直接目标服务器的情况下,之前没有connect时进行connet调用,connect成功后调用了remote_send_cb,随即发送remote->buf中的全部数据,可能一次send只发送了一部分,那么回调就会多次触发; 如果发送完成了则需要再次从客户端读取数据,此时客户端还是处于STAGE_STREAM状态。好了,我们现在回过头继续看server_recv_cb,此时已经是connect过了。

2. remote->send_ctx->connected==1的情况。请进入server_recv_cb继续分析:

在进入if (!remote->send_ctx->connected)对应的else代码块之前。先看一下此时读取的数据,if (!remote->direct) 里面之前是第一次进入,会将abuf插入到前面,此时已经没有abuf了,所以读取出来的数据直接加密后发送。从这儿可以看到ss只是TCP一次连接要发送的所有数据的开头加上一个头数据,之后就都是原始数据了,当然所有这些数据都是加密的。好了,继续看server_recv_cb里面的发送代码:

else {
                int s = send(remote->fd, remote->buf->data, remote->buf->len, 0);
                if (s == -1) {
                    if (errno == EAGAIN || errno == EWOULDBLOCK) {
                        // no data, wait for send
                        remote->buf->idx = 0;
                        ev_io_stop(EV_A_ & server_recv_ctx->io);
                        ev_io_start(EV_A_ & remote->send_ctx->io);
                        return;
                    } else {
                        ERROR("server_recv_cb_send");
                        close_and_free_remote(EV_A_ remote);
                        close_and_free_server(EV_A_ server);
                        return;
                    }
                } else if (s < (int)(remote->buf->len)) {
                    remote->buf->len -= s;
                    remote->buf->idx  = s;
                    ev_io_stop(EV_A_ & server_recv_ctx->io);
                    ev_io_start(EV_A_ & remote->send_ctx->io);
                    return;
                } else {
                    remote->buf->idx = 0;
                    remote->buf->len = 0;
                }
            }

此处,即server_recv_cb的STAGE_STREAM阶段,已经连接上ss-server后,读取到新的客户端数据后随即发送到ss-server。即调用了send,注意这儿的send是从buf->data的开头发送的,结合之前的代码有个结论,server_recv_cb读取到数据后即试图发送buf,这些数据是新读取到的,所以从buf开头发送,如果发送了部分数据,则设置idx,然后在remote_send_cb里面从idx处继续发送,全部发送完毕后再次开启server_recv_cb对应的监听,继续读取客户端数据然后转发到ss-server。

因此这儿send之后的代码很好理解,出错处理就不说了都一样;部分发送就是设置idx和len并停启相应事件;全部发送就是请空idx,len就行并不需要再设置开启send监听了,因为已经发送完了,就等着从客户端再次读取数据过来。在server_recv_cb前面,如果读取到EOF,即返回值为0,则说明客户端已经没有数据要发送了,且断开了连接,此次转发发送成功,回忆一下之前的代码:

if (r == 0) {
            // connection closed
            close_and_free_remote(EV_A_ remote);
            close_and_free_server(EV_A_ server);
            return;
        } 

好了,那么remote_send_cb其实处理的就是server_recv_cb没有发送出去的数据,他要继续发送,因为已经connect过,所以直接进入上面贴出的remote_send_cb的后半段代码,和上面connect成功进入一样,调用send发送数据,处理部分发送和全部发送的情况。

至此,不使用fast open或者直连目标服务器的情况已经分析完了。再总结一下整个流程:

- remote_send_ctx->connected==0时,先在server_recv_cb里面执行connect,connect成功remote_send_cb被回调

- remote_send_cb第一次被调用是connect成功时,通过getpeername判断是否成功,如果成功会开启remote fd的读事件监听和timer,即准备从ss-server接受数据。然后就是向ss-server发送数据了。connect时附带的数据可能分几次才发送完。总之发送完后就再启用server_recv_cb读取数据。

- server_recv_cb再次被调用后就继续读取客户端的数据然后发送,如果是部分发送就再次调用remote_send_cb。最终没有数据可读取则断开连接。

以上,是不开启fast open或直连的情况。下面分析开启fast open的情况。

ss-libev 源码解析local篇(5):ss-local之remote_send_cb的更多相关文章

  1. jQuery2.x源码解析(缓存篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 缓存是jQuery中的又一核心设计,jQuery ...

  2. jQuery2.x源码解析(构建篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 笔者阅读了园友艾伦 Aaron的系列博客< ...

  3. jQuery2.x源码解析(设计篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...

  4. jQuery2.x源码解析(回调篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 通过艾伦的博客,我们能看出,jQuery的pro ...

  5. Shiro源码解析-Session篇

    上一篇Shiro源码解析-登录篇中提到了在登录验证成功后有对session的处理,但未详细分析,本文对此部分源码详细分析下. 1. 分析切入点:DefaultSecurityManger的login方 ...

  6. myBatis源码解析-类型转换篇(5)

    前言 开始分析Type包前,说明下使用场景.数据构建语句使用PreparedStatement,需要输入的是jdbc类型,但我们一般写的是java类型.同理,数据库结果集返回的是jdbc类型,而我们需 ...

  7. Spring源码解析 | 第二篇:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析

    一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...

  8. myBatis源码解析-数据源篇(3)

    前言:我们使用mybatis时,关于数据源的配置多使用如c3p0,druid等第三方的数据源.其实mybatis内置了数据源的实现,提供了连接数据库,池的功能.在分析了缓存和日志包的源码后,接下来分析 ...

  9. myBatis源码解析-反射篇(4)

    前沿 前文分析了mybatis的日志包,缓存包,数据源包.源码实在有点难顶,在分析反射包时,花费了较多时间.废话不多说,开始源码之路. 反射包feflection在mybatis路径如下: 源码解析 ...

  10. libev 源码解析

    一  libev简介 libev是一个轻量级的事件通知库,具备支持多种事件通知能力,通过对libev的源码的阅读,可以清楚了解事件通知实现内部机制. 二 核心数据结构 在libev中关键的数据结构是, ...

随机推荐

  1. 20145322《Java程序设计》第5次实验报告

    20145322<Java程序设计>第5次实验报告 实验内容 1.根据所学内容,编写代码实现服务器与客户端 2.掌握密码技术的使用 3.设计安全传输系统,客户端中输入明文,利用DES算法加 ...

  2. 贝叶斯公式由浅入深大讲解—AI基础算法入门【转】

    本文转载自:https://www.cnblogs.com/zhoulujun/p/8893393.html 1 贝叶斯方法 长久以来,人们对一件事情发生或不发生的概率,只有固定的0和1,即要么发生, ...

  3. oracle查看被锁的表以及解锁表

    在oracle 上面查看别锁定的表,以及解锁表的sql: select t3.object_name,t3.owner,t2.machine,t2.sid,t2.serial# from v$lock ...

  4. tsar的使用

    项目地址https://github.com/alibaba/tsar 安装 $ git clone git://github.com/kongjian/tsar.git $ cd tsar $ ma ...

  5. Nodejs V8引擎 fast property lookup

    前言 之所以会研究这个东西,是我在网上找了一下各个语言的执行效率比较.好吧,我承认这是个无聊的东西,不过看看总是无妨,然而我惊讶的发现,有些测试声称Java,C,Nodejs是处在同一个效率级别参见链 ...

  6. SQLite-C#-帮助类

    public static class SQLiteHelper { private static string connectionString = string.Empty; #region vo ...

  7. codeforce AIM tech Round 4 div 2 B rectangles

    2017-08-25 15:32:14 writer:pprp 题目: B. Rectangles time limit per test 1 second memory limit per test ...

  8. mac iterm2 打开Linux 服务器文件乱码

    我的mac 上用是iterm2终端, Shell 环境是zsh.ssh 到Linux 服务器上查看一些文件时,中文乱码. 这种情况一般是终端和服务器的字符集不匹配,MacOSX下默认的是utf8字符集 ...

  9. outline详解

    outline这个属性平时用的不太多,最近被问及专门研究一下这个属性的作用. CSS2加进来的outline属性,中文翻译过来是外轮廓. 神马是轮廓? 轮廓,指边缘:物体的外周或图形的外框. 那这样的 ...

  10. 在 Spark 中使用 IPython Notebook

    本文是从 IPython Notebook 转化而来,效果没有本来那么好. 主要为体验 IPython Notebook.至于题目,改成<在 IPython Notebook 中使用 Spark ...