我正在尝试在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数据...但我真的不知道我可能处理它的任何其他方式。无论读取什么(无论&lt; 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 ""
答案 0 :(得分:2)
是的,根据您以前的帖子,这实际上就是我期待的代码问题。这是邪恶的根源:
let _ = Unix.read read_fd buff 0 buff_size in
您无法忽略阅读结果,因为无法保证,read
调用将准确读取buff_size
,它可以返回更少的数据(所谓的&#34;短读&#34)。 write
调用也是同样的问题。因此,您需要仔细使用缓冲区,以便在短读取后重建数据。另一个问题是,呼叫可以被信号中断,但我认为你现在没有打到这个。