c - 尽管轮询文件描述符,但不可靠的 http 客户端

标签 c sockets ocaml unix-socket

我正在尝试用 OCaml 编写一个简单的 HTTP 客户端。我知道使用 cohttp 等库会更容易。我这样做是为了我自己,所以不需要提出建议。

这是我的代码。

module Connection = struct
    let sock_fd =
        let s_fd = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
        Unix.setsockopt s_fd Unix.TCP_NODELAY true;
        s_fd

    let read_timeout = 10.0

    let read_from_sock () =
        let buff_size = 4096 in
        let buff = Bytes.create buff_size in
        let rec read_all response =
            let (read_fds, _, _) = Unix.select [sock_fd] [] [] read_timeout in
            match read_fds with
            | [] -> response
            | (read_fd :: _) -> begin
                let _ = Unix.read read_fd buff 0 buff_size in
                let current_response = response ^ buff in
                read_all current_response
            end in
        read_all ""

    let write_to_sock str =
        let len = String.length str in
        let _ = Unix.write sock_fd str 0 len in ()

    let make_request request serv_addr =
        Unix.connect sock_fd serv_addr;
        write_to_sock request

    class connection address port =
        object
            val serv_addr = Unix.ADDR_INET (Unix.inet_addr_of_string address, port)

            method get_response (request: string) =
                make_request request serv_addr;
                let response = read_from_sock () in
                Printf.printf "%s\n" response;
                Unix.shutdown sock_fd Unix.SHUTDOWN_ALL;
                Unix.close sock_fd
        end

    let create address port = new connection address port
end

let connection = Connection.create "54.175.219.8" 80;;
connection#get_response "GET / HTTP/1.1\r\nHost: www.httpbin.org\r\n\r\n"

正如我之前发布的那样 - 如果你觉得它有帮助 - 我想一个(非常粗略的)C 等价物是这样的:

int sock_fd = socket(PF_INET, SOCK_STREAM);
setsockopt(sock_fd, TCP_NODELAY, 1);

serv_addr addr {"54.175.219.8", 80};
connect(sock_fd, &serv_addr);
write(sock_fd, "GET / HTTP/1.1\r\nHost: www.httpbin.org\r\n\r\n");

char buffer[512];

while (sock_fd = select(sock_fd, 10.0)) {
    if (!sock_fd) break;
    read(sock_fd, &buffer);
    printf("%s\n", buffer);
    flush(stdout);
}

shutdown(sock_fd, SHUTDOWN_ALL);
close(sock_fd);

当我执行此操作时,会得到极其不同的结果。有一次,我确实得到了整个页面。但大多数时候,它会在页面的 80% 处被截断。我尝试增加超时时间但无济于事。

我想如果我轮询文件描述符,我就能可靠地知道什么时候没有像这个博客这样的数据了suggests .似乎这种方法是对循环的改进,直到读取大小小于 buffer_size,但我想不是吗?我错过了什么?

更新:

我编辑了代码以检查读取大小是否小于缓冲区大小。然而,这似乎是多余的。如果还有更多要读取,select 将返回文件描述符。如果没有更多可读的内容,则不会,我将返回已阅读的内容。这是新代码:

let r = Unix.read read_fd buff 0 buff_size in
let current_response = response ^ buff in
if r < buff_size
then current_response
else read_all response

但实际上这是错误的。这完全消除了轮询文件描述符的意义。也许问题仍然在于读取的数据少于 buff_size ......但我真的不知道我可以用任何其他方式处理它。无论读取什么(无论 < buff_size 与否)仍将附加到响应中。 read_all 将尝试完成读取,直到 select 不再返回文件描述符,此时,应该没有更多内容可读取。

最终解决方案(感谢@ivg):

let read_from_sock () =
    let buff_size = 4096 in
    let buff = Bytes.create buff_size in
    let rec read_all response =
        let (read_fds, _, _) = Unix.select [sock_fd] [] [] read_timeout in
        let rec read_all_helper current_response =
            match read_fds with
            | [] -> current_response
            | (read_fd :: _) -> begin
                let r = Unix.read read_fd buff 0 buff_size in
                let current_response = response ^ (String. sub buff 0 r) in
                if r < buff_size then read_all current_response
                else read_all_helper current_response
            end in
        read_all_helper response in
    read_all ""

最佳答案

是的,根据您之前的帖子,这实际上就是我期望从您的代码中得到的那种问题。这是邪恶的根源:

let _ = Unix.read read_fd buff 0 buff_size in

你不能忽略 read 的结果,因为不能保证 read 调用会准确读取 buff_size,它可以返回更少的数据(所谓的“短读”)。 write 调用也存在同样的问题。因此,您需要仔细处理缓冲区,以便在短暂读取后重建数据。另一个问题是,调用可能会被信号打断,但我认为您现在不会点击它。

关于c - 尽管轮询文件描述符,但不可靠的 http 客户端,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28674945/

相关文章:

c - 如何初始化指针指向的结构的成员?

sockets - 带有 Unix 模块的 ocaml 简单的 http 服务器

polymorphism - 我可以使用 pa_monad 来确保 η 扩展吗?

emacs - 如何在 Emacs 中执行一行 OCaml 代码?

c - 功能类似于 SYSTEM(const char *) 且不采用 const char 但采用动态生成的查询的函数是什么

c - 在 C 中始终强制转换变量是一个好习惯吗?

Android 安全/加密套接字

ocaml - 如何以毫秒或纳秒为单位获取当前系统时间?

c - GCC 编译错误 : format ‘%c’ expects argument of type ‘char *’ , 但参数 2 的类型为 ‘int’ [-Wformat]

java - Android 端口无法工作