• scp源码浅析


    背景:

    • 经常使用scp传文件,发现它真的很给力,好奇心由来已久!
    • 恰好接到一个移植SSH服务到专有网络(非IP网络)的小任务,完成工作又能满足好奇心,何乐而不为!
    • 我只从源码浅浅的分析一下,后续有更多想法再补充

    源码赏析:

    1、所有的故事都从main开始,也从main结束。(main也很无辜,它只是打开了计算机的一扇窗):

      作为一个命令行工具,命令行是必须要处理的,这里scp也是采用常见的getopt来处理命令行。

    1 while ((ch = getopt(argc, argv, "dfl:prtvBCc:i:P:q12346S:o:F:")) != -1)

        上面的字符串就是可以使用的命令行选项,带冒号的表示有参数,比如 d 表示可以在shell输入 scp -d ...,l: 表示可以在shell输入 scp -l 1000 ... ,当然这样重点要提到 -r, 加上它就可以递归传输子目录,非常实用,其他参数我就不再详解了。

        接下来会看到如下代码:

    1 /* Command to be executed on remote system using "ssh". */
    2     (void) snprintf(cmd, sizeof cmd, "scp%s%s%s%s",
    3         verbose_mode ? " -v" : "",
    4         iamrecursive ? " -r" : "", pflag ? " -p" : "",
    5         targetshouldbedirectory ? " -d" : "");

      可以看到,注释里提到了,这是要通过ssh到远程linux系统(你的目的电脑)去执行的一条命令,同样也是scp喔,所以这是scp神奇又方便的原因之一!

      它通过ssh连接到目的机器,同样执行了一个scp程序来实现数据通路,完成数据接收或者发送。

    注意:上面隐含了2个条件:

    (1)你本机或者远程机,两者之间必须有一个ssh服务端

    (2)两者都必须有scp这个工具

    2、文件的发送和接收

      之所以来看scp的源码,也就是对它的文件读写传输很感兴趣,首先看的是如何选择合适大小的块来读写,如下函数:

    1 BUF * allocbuf(BUF *bp, int fd, int blksize)

      这个函数就会根据文件大小来分配一个合适的文件块,来分块读写,并网络传输。

      函数的返回值是一个结构,用来存放即将分配的内存地址和内存大小。

    1 typedef struct {
    2     size_t cnt;
    3     char *buf;
    4 } BUF;

      而这个函数最核心的逻辑就是获取文件的块大小(基于文件系统I/O),并按照预定的块大小来补齐,就像常见的64字节对齐一样,如果你不是64字节的倍数,那就给你补齐。这里scp的预定块大小的补齐是按16384字节来补齐的。

    1 if (fstat(fd, &stb) < 0) {
    2         run_err("fstat: %s", strerror(errno));
    3         return (0);
    4     }
    5     size = roundup(stb.st_blksize, blksize);
    1 #ifndef roundup
    2 # define roundup(x, y)   ((((x)+((y)-1))/(y))*(y))
    3 #endif

      这里,roundup就是按16384的倍数向上取整了,比如XFS的块大小是512bytes到64KB,EXT3,EXT4的块大小是4KB。

      然后就是分配size大小的内存了。

    1 if (bp->cnt >= size)
    2         return (bp);
    3     if (bp->buf == NULL)
    4         bp->buf = xmalloc(size);
    5     else
    6         bp->buf = xreallocarray(bp->buf, 1, size);
    7     memset(bp->buf, 0, size);
    8     bp->cnt = size;

      然后就是逐块的发送文件了。

     1 set_nonblock(remout);
     2         for (haderr = i = 0; i < stb.st_size; i += bp->cnt) {
     3             amt = bp->cnt;
     4             if (i + (off_t)amt > stb.st_size)
     5                 amt = stb.st_size - i;
     6             if (!haderr) {
     7                 if ((nr = atomicio(read, fd,
     8                     bp->buf, amt)) != amt) {
     9                     haderr = errno;
    10                     memset(bp->buf + nr, 0, amt - nr);
    11                 }
    12             }
    13             /* Keep writing after error to retain sync */
    14             if (haderr) {
    15                 (void)atomicio(vwrite, remout, bp->buf, amt);
    16                 memset(bp->buf, 0, amt);
    17                 continue;
    18             }
    19             if (atomicio6(vwrite, remout, bp->buf, amt, scpio,
    20                 &statbytes) != amt)
    21                 haderr = errno;
    22         }
    23         unset_nonblock(remout);
    View Code

      当然,如果设置了-r选项,就会递归处理子目录以及子目录的文件

     文件接收方会收到发送方发过来的整个文件大小,然后整个过程就跟发送有点类似了:

     1 set_nonblock(remin);
     2         for (count = i = 0; i < size; i += bp->cnt) {
     3             amt = bp->cnt;
     4             if (i + amt > size)
     5                 amt = size - i;
     6             count += amt;
     7             do {
     8                 j = atomicio6(read, remin, cp, amt,
     9                     scpio, &statbytes);
    10                 if (j == 0) {
    11                     run_err("%s", j != EPIPE ?
    12                         strerror(errno) :
    13                         "dropped connection");
    14                     exit(1);
    15                 }
    16                 amt -= j;
    17                 cp += j;
    18             } while (amt > 0);
    19 
    20             if (count == bp->cnt) {
    21                 /* Keep reading so we stay sync'd up. */
    22                 if (wrerr == NO) {
    23                     if (atomicio(vwrite, ofd, bp->buf,
    24                         count) != count) {
    25                         wrerr = YES;
    26                         wrerrno = errno;
    27                     }
    28                 }
    29                 count = 0;
    30                 cp = bp->buf;
    31             }
    32         }
    33         unset_nonblock(remin);
    View Code

    参考:

    https://en.wikipedia.org/wiki/XFS

    https://en.wikipedia.org/wiki/Ext4

  • 相关阅读:
    轻量级微服务架构【读书笔记3】
    轻量级微服务架构【读书笔记2】
    轻量级微服务架构【读书笔记1】
    mvn package 和 mvn install
    SpringBoot 使用MultipartFile上传文件相关问题解决方案
    Docker学习笔记【三】安装Redis
    RESTful 最佳实战
    HTTP Status Codes 查询表
    扎实基础总述
    文本挖掘2相似度计算
  • 原文地址:https://www.cnblogs.com/danxi/p/6680549.html
Copyright © 2020-2023  润新知