2

所以我想了解 PuTTY 如何实施 SCP 并有几个问题。

首先,这是一个较旧且(恕我直言)更易于阅读的 PuTTY SCP 实现版本的副本:

https://github.com/Yasushi/putty/blob/4eeae4a39bc9faf539b8f819c12b1d1b9f22fc86/scp.c

这是发送数据的函数:

int scp_send_filedata(char *data, int len)
{
    int bufsize = back->send(data, len);

    /*
     * If the network transfer is backing up - that is, the remote
     * site is not accepting data as fast as we can produce it -
     * then we must loop on network events until we have space in
     * the buffer again.
     */
    while (bufsize > MAX_SCP_BUFSIZE) {
        if (!scp_process_network_event())
            return 1;
        bufsize = back->sendbuffer();
    }

    return 0;
}

这是 scp_process_network_event:

static int scp_process_network_event(void)
{
    fd_set readfds;

    FD_ZERO(&readfds);
    FD_SET(scp_ssh_socket, &readfds);
    if (select(1, &readfds, NULL, NULL, NULL) < 0)
        return 0;                       /* doom */
    select_result((WPARAM) scp_ssh_socket, (LPARAM) FD_READ);
    return 1;
}

那么 scp_process_network_event 执行 select() 系统调用,阻塞直到写入操作不会阻塞?

我在想 back->sendbuffer 和 back->send 对应于:

https://github.com/Yasushi/putty/blob/4eeae4a39bc9faf539b8f819c12b1d1b9f22fc86/ssh.c

/*
 * Called to send data down the Telnet connection.
 */
static int ssh_send(char *buf, int len)
{
    if (s == NULL || ssh_protocol == NULL)
        return 0;

    ssh_protocol(buf, len, 0);

    return ssh_sendbuffer();
}

/*
 * Called to query the current amount of buffered stdin data.
 */
static int ssh_sendbuffer(void)
{
    int override_value;

    if (s == NULL || ssh_protocol == NULL)
        return 0;

    /*
     * If the SSH socket itself has backed up, add the total backup
     * size on that to any individual buffer on the stdin channel.
     */
    override_value = 0;
    if (ssh_throttled_all)
        override_value = ssh_overall_bufsize;

    if (ssh_version == 1) {
        return override_value;
    } else if (ssh_version == 2) {
        if (!mainchan || mainchan->closes > 0)
            return override_value;
        else
            return override_value + bufchain_size(&mainchan->v.v2.outbuffer);
    }

    return 0;
}

不过我对此并不完全确定,而且我也不太擅长 C :(

无论如何,我并不完全清楚 bufsize 应该是什么。

有任何想法吗?

谢谢!

4

1 回答 1

2

选择在读取时阻塞。我们不能做任何事情(例如发送任何东西),所以我们必须等待某些内容出现才能读取 - 一旦我们读取了某些内容,我们就可以尝试再次发送我们排队的数据。

看起来 send() 将数据缓冲区放在缓冲区内部链表的末尾并尝试发送它,并且 sendbuffer() 将刷新任何剩余数据的缓冲区内部链表(例如,如果 send() 不能全部发送,因为它有备份)。

send() / sendbuffer() 方法看起来像是返回要发送的数据量,例如,有 5 个 10 字节的缓冲区要发送,您只能发送 2 个缓冲区,剩下 30 个字节仍要在内部发送缓冲区链表,因此发送方法将返回 30。

重要的一点是通过查看发送方法返回的值来检测拥塞 - 如果它们返回非零,则意味着一些数据保留在内部发送缓冲区中并且可能由于拥塞而尚未发送出去.

ssh 后端 (ssh.c) 与普通的旧 telnet 后端共享一个 API,因此我们可以从 telnet.c 和 wininet.c 源中派生 API,而无需担心加密层。在wininet.c:711中,我们实际上看到了在套接字上最终调用 send() 的位置,如果此发送成功,我们调用 buffchain_consume() 从缓冲区链表的开头(例如 FIFO 队列)出列这么多字节. 整个函数有一个外部 while 循环,该循环一直持续到缓冲区链为空(或者除非我们在中间返回错误)。

正是在这种方法中,我们从 libc(以及 php)中的 send() 中都知道的“字节成功发送”范式被转换为示例中腻子代码中使用的“排队等待发送的字节”范式。

基本上你有 N 个字节的数据要发送,你成功地 send() X 个字节,所以你还有 NX 字节要发送。

实际减法发生在buffchain_consume():127缓冲区内容的链接列表由该文件中的 buffchain_*() 方法管理。

于 2013-12-11T20:26:44.593 回答