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;

  而这个函数最核心的逻辑就是获取文件的总大小,并按照预定的块大小来补齐,就像常见的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);

  当然,如果设置了-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);

参考:

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

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

时间: 2024-11-10 13:50:41

scp源码浅析的相关文章

Volley框架源码浅析(一)

尊重原创http://blog.csdn.net/yuanzeyao/article/details/25837897 从今天开始,我打算为大家呈现关于Volley框架的源码分析的文章,Volley框架是Google在2013年发布的,主要用于实现频繁而且粒度比较细小的Http请求,在此之前Android中进行Http请求通常是使用HttpUrlConnection和HttpClient进行,但是使用起来非常麻烦,而且效率比较地下,我想谷歌正式基于此种原因发布了Volley框架,其实出了Voll

PM2源码浅析

PM2工作原理 最近在玩一个游戏,<地平线:黎明时分>,最终Boss是一名叫黑底斯的人,所谓为人,也许不对,黑底斯是一段强大的毁灭进程,破坏了盖娅主进程,从而引发的整个大陆机械兽劣化故事. 为什么要讲这么一段呢,是希望大家可以更好地理解pm2的原理,要理解pm2就要理解god和santan的关系,god和santan的关系就相当于盖娅和黑底斯在pm2中的01世界中,每一行代码每一个字节都安静的工作god就是Daemon进程 守护进程,重启进程,守护node程序世界的安宁,santan就是进程的

Android源码浅析(一)——VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置

Android源码浅析(一)--VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置 最近地方工作,就是接触源码的东西了,所以好东西还是要分享,系列开了这么多,完结 的也没几个,主要还是自己覆盖的太广了,却又不精通,嘿嘿,工作需要,所以写下了本篇博客 一.VMware 12 我选择的虚拟机试VMware,挺好用的感觉,下载VMware就不说了,善用搜索键嘛,这里我提供一个我现在在用的 下载地址:链接:http://pan.baidu.com/s/1k

ReactiveCocoa2 源码浅析

ReactiveCocoa2 源码浅析 标签(空格分隔): ReactiveCocoa iOS Objective-C ? 开车不需要知道离合器是怎么工作的,但如果知道离合器原理,那么车子可以开得更平稳. ReactiveCocoa 是一个重型的 FRP 框架,内容十分丰富,它使用了大量内建的 block,这使得其有强大的功能的同时,内部源码也比较复杂.本文研究的版本是2.4.4,小版本间的差别不是太大,无需担心此问题. 这里只探究其核心 RACSignal 源码及其相关部分.本文不会详细解释里

【Spark Core】任务执行机制和Task源码浅析2

引言 上一小节<任务执行机制和Task源码浅析1>介绍了Executor的注册过程. 这一小节,我将从Executor端,就接收LaunchTask消息之后Executor的执行任务过程进行介绍. 1. Executor的launchTasks函数 DriverActor提交任务,发送LaunchTask指令给CoarseGrainedExecutorBackend,接收到指令之后,让它内部的executor来发起任务,即调用空闲的executor的launchTask函数. 下面是Coars

Volley框架源码浅析(二)

尊重原创 http://write.blog.csdn.net/postedit/25921795 在前面的一片文章Volley框架浅析(一)中我们知道在RequestQueue这个类中,有两个队列:本地队列和网络队列 /** The cache triage queue. */ private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<

Android手势源码浅析-----手势绘制(GestureOverlayView)

Android手势源码浅析-----手势绘制(GestureOverlayView)

【Spark】Stage生成和Stage源码浅析

引入 上一篇文章<DAGScheduler源码浅析>中,介绍了handleJobSubmitted函数,它作为生成finalStage的重要函数存在,这一篇文章中,我将就DAGScheduler生成Stage过程继续学习,同时介绍Stage的相关源码. Stage生成 Stage的调度是由DAGScheduler完成的.由RDD的有向无环图DAG切分出了Stage的有向无环图DAG.Stage的DAG通过最后执行的Stage为根进行广度优先遍历,遍历到最开始执行的Stage执行,如果提交的St

转:Spring FactoryBean源码浅析

http://blog.csdn.net/java2000_wl/article/details/7410714 在Spring BeanFactory容器中管理两种bean 1.标准Java Bean 2,另一种是工厂Bean,   即实现了FactoryBean接口的bean  它不是一个简单的Bean 而是一个生产或修饰对象生成的工厂Bean 在向Spring容器获得bean时  对于标准的java Bean  返回的是类自身的实例 而FactoryBean 其返回的对象不一定是自身类的一