>  기사  >  php教程  >  GIT 전송 프로토콜 구현

GIT 전송 프로토콜 구현

高洛峰
高洛峰원래의
2016-11-23 14:18:111291검색

GIT 전송 프로토콜 구현

GIT의 세 가지 주류 전송 프로토콜인 HTTP SSH GIT 중에서 GIT 프로토콜은 가장 적게 사용되는 프로토콜(즉, URL이 git://으로 시작하는 프로토콜)입니다. 이는 git 프로토콜에는 권한 제어가 거의 없기 때문에 모두 읽을 수 있거나 모두 쓸 수 있거나 모두 읽고 쓸 수 있기 때문입니다. 따라서 코드 호스팅 플랫폼의 경우 git 프로토콜의 목적은 공개 프로젝트에 대한 읽기 전용 액세스만 지원하는 것입니다.

git의 다양한 전송 프로토콜 중에서 의심할 여지없이 가장 효율적인 프로토콜은 git 프로토콜입니다. HTTP는 HTTP의 특성으로 인해 제한되며 전송 프로세스에는 HTTP 요청 및 응답 구성이 필요합니다. HTTPS인 경우 암호화 및 암호 해독도 포함됩니다. 또한 HTTP 시간 초과 설정 및 패키지 크기 제한은 사용자 경험에 영향을 미칩니다.

SSH 프로토콜의 성능 문제는 주로 암호화 및 복호화에 중점을 둡니다. 물론, 이러한 비용은 사용자의 정보 보안에 비하면 수용할 수 있는 수준입니다.

git 프로토콜은 실제로 암호화와 검증이 없으면 SSH와 동일하므로 권한 제어에 대해 말할 방법이 없습니다. 그러나 실제로 코드 호스팅 플랫폼 내의 일부 동기화 서비스를 사용하여 구현하면 크게 향상됩니다. Git 프로토콜의 성능이 향상되었습니다.

전송 프로토콜 사양

git 프로토콜의 기술 문서는 git 소스 코드 디렉토리의 Documentation/technical, 즉 Packfile 전송 프로토콜에서 찾을 수 있습니다. TCP 연결을 생성한 후 git은 클라이언트가 요청 본문과 요청 형식을 주도적으로 전송합니다. BNF를 기반으로 설명은 다음과 같습니다.

git-proto-request = request-command SP pathname NUL [ host-parameter NUL ]
request-command   = "git-upload-pack" / "git-receive-pack" / "git-upload-archive"   ; case sensitive
pathname          = *( %x01-ff ) ; exclude NUL
host-parameter    = "host=" hostname [ ":" port ]

예는 다음과 같습니다.

0033git-upload-pack /project. 자식

在 C 语言中,有 popen 函数,可以创建一个进程,并将进程的标准输出或标准输入创建成一个文件指针,即 FILE*其他可以使用 C 函数的语言很多也提供了类似的实现,比如 Ruby,基于 Ruby 的 git HTTP 服务器 grack 正是使用 的 popen,相比与其他语言改造的 popen,C 语言中 popen 存在了一些缺陷,比如无法同时读写,如果要输出标准 错误,需要在命令参数中额外的将标准错误重定向到标准输出。

在 musl libc 的中,popen 的实现如下:

FILE *popen(const char *cmd, const char *mode)
{
    int p[2], op, e;
    pid_t pid;
    FILE *f;
    posix_spawn_file_actions_t fa;

    if (*mode == 'r') {
        op = 0;
    } else if (*mode == 'w') {
        op = 1;
    } else {
        errno = EINVAL;
        return 0;
    }

    if (pipe2(p, O_CLOEXEC)) return NULL;
    f = fdopen(p[op], mode);
    if (!f) {
        __syscall(SYS_close, p[0]);
        __syscall(SYS_close, p[1]);
        return NULL;
    }
    FLOCK(f);

    /* If the child's end of the pipe happens to already be on the final
     * fd number to which it will be assigned (either 0 or 1), it must
     * be moved to a different fd. Otherwise, there is no safe way to
     * remove the close-on-exec flag in the child without also creating
     * a file descriptor leak race condition in the parent. */
    if (p[1-op] == 1-op) {
        int tmp = fcntl(1-op, F_DUPFD_CLOEXEC, 0);
        if (tmp < 0) {
            e = errno;
            goto fail;
        }
        __syscall(SYS_close, p[1-op]);
        p[1-op] = tmp;
    }

    e = ENOMEM;
    if (!posix_spawn_file_actions_init(&fa)) {
        if (!posix_spawn_file_actions_adddup2(&fa, p[1-op], 1-op)) {
            if (!(e = posix_spawn(&pid, "/bin/sh", &fa, 0,
                (char *[]){ "sh", "-c", (char *)cmd, 0 }, __environ))) {
                posix_spawn_file_actions_destroy(&fa);
                f->pipe_pid = pid;
                if (!strchr(mode, &#39;e&#39;))
                    fcntl(p[op], F_SETFD, 0);
                __syscall(SYS_close, p[1-op]);
                FUNLOCK(f);
                return f;
            }
        }
        posix_spawn_file_actions_destroy(&fa);
    }
fail:
    fclose(f);
    __syscall(SYS_close, p[1-op]);

    errno = e;
    return 0;
}

在 Windows Visual C++ 中,popen 源码在 C:\Program Files (x86)\Windows Kits\10\Source\${SDKVersion}\ucrt\conio\popen.cpp , 按照 MSDN 文档说明,Windows 32 GUI 程序,即 subsystem 是 Windows 的程序,使用 popen 可能导致程序无限失去响应。

所以在笔者实现 git-daemon 及其他 git 服务器时,都不会使用 popen 这个函数。

为了支持跨平台和简化编程,笔者在实现 svn 代理服务器时就使用了 Boost Asio 库,后来也用 Asio 实现过一个 git 远程命令服务, 每一个客户端与服务器连接后,服务器启动程序,需要创建 3 条管道,分别是 子进程的标准输入 输出 错误,即 stdout stdin stderr, 然后注册读写异步事件,将子进程的输出与错误写入到 socket 发送出去,读取 socket 写入到子进程的标准输入中。

在 POSIX 系统中,boost 有一个文件描述符类 boost::asio::posix::stream_descriptor 这个类不能是常规文件,以前用 go 做 HTTP 前端 没注意就 coredump 掉。

在 Windows 系统中,boost 有文件句柄类 boost::asio::windows::stream_handle 此处的文件应当支持随机读取,比如命名管道(当然 在 Windows 系统的,匿名管道实际上也是命名管道的一种特例实现)。

以上两种类都支持 async_read async_write ,所以可以很方便的实现异步的读取。

上面的做法,唯一的缺陷是性能并不是非常高,代码逻辑也比较复杂,当然好处是,错误异常可控一些。

在 Linux 网络通信中,类似与 git 协议这样读取子进程输入输出的服务程序的传统做法是,将 子进程的 IO 重定向到 socket, 值得注意的是 boost 中 socket 是异步非阻塞的,然而,git 命令的标准输入标准错误标准输出都是同步的,所以在 fork 子进程之 前,需要将 socket 设置为同步阻塞,当 fork 失败时,要设置回来。

socket_.native_non_blocking(false);

另外,为了记录子进程是否异常退出,需要注册信号 SIGCHLD 并且使用 waitpid 函数去等待,boost 就有 boost::asio::signal_set::async_wait 当然,如果你开发这样一个服务,会发现,频繁的启动子进程,响应信号,管理连接,这些操作才是性能的短板。

一般而言,Windows 平台的 IO 并不能重定向到 socket,实际上,你如果使用 IOCP 也可以达到相应的效率。还有,Windows 的 socket API WSASocket WSADuplicateSocket 复制句柄 DuplicateHandle ,这些可以好好利用。

其他

对于非代码托管平台的从业者来说,上面的相关内容可能显得无足轻重,不过,网络编程都是殊途同归,最后核心理念都是类似的。关于 git-daemon 如果笔者有时间会实现一个跨平台的简易版并开源。


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
이전 기사:C/C++에 Python 포함다음 기사:C/C++에 Python 포함