我想获得一个文件的并行下载,例如,如果文件大小为54 kb,我想以10kb的块为单位下载文件的内容。
另外,我一次不超过 5 个请求。但如何?我想过用fork(),但不是很明白怎么用。
1-10 第一个请求
11-20 第二个请求
21-30 第三个请求
31-40 第四个请求
41-50 第五个请求
51-54 等到过去的一个请求结束。然后它将被执行。
我不关心获取数据的方法(recv 等)。我只想知道如何实现并发方法?(如果我可以使用 fork() 会更好)
我想获得一个文件的并行下载,例如,如果文件大小为54 kb,我想以10kb的块为单位下载文件的内容。
另外,我一次不超过 5 个请求。但如何?我想过用fork(),但不是很明白怎么用。
1-10 第一个请求
11-20 第二个请求
21-30 第三个请求
31-40 第四个请求
41-50 第五个请求
51-54 等到过去的一个请求结束。然后它将被执行。
我不关心获取数据的方法(recv 等)。我只想知道如何实现并发方法?(如果我可以使用 fork() 会更好)
有一些现成的软件库可以提供此功能。我能想到的主要是curl。您可以在此处找到curl 多库的简单介绍。
通常最好避免重新发明轮子,除非您有充分的理由(例如改善技术世界或进行学术研究)。
为了学术研究,并且由于没有“仅链接”的答案就足够了,我将详细说明可以使用多路复用套接字的许多可能方法之一。
非阻塞套接字
第一种也是目前最可移植的方法是使用非阻塞套接字和/或非阻塞套接字调用,但重要的是要实现(尤其是在使用非阻塞套接字调用而不是设置O_NONBLOCK
文件描述符时):事情还是会堵。例如,connect
除非您将文件描述符设置为非阻塞模式,否则您无法立即返回,而且您getaddrinfo
(以及类似的标准名称解析函数)当然也会阻塞。
当您使用非阻塞文件或调用函数时,函数将立即返回。如果没有准备好数据,他们将通过返回值来表明这一点。如果有数据准备好处理,再次通过返回值显示。
有两种方法(据我所知)来确保非阻塞套接字调用(包括connect
)。
fcntl(socket_fd, fcntl(socket_fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK)
. 之后,所有对read
、和的调用都会立即返回write
,不会有任何延迟。有一些专门用于此的错误代码(in ),例如, ,并且您需要检查它们,因为当您启用非阻塞时,一些错误返回值实际上是伪装的成功返回值;你需要检查。accept
connect
connect
errno
EALREADY
EINPROGRESS
EISCONN
EWOULDBLOCK
errno
对于 Windows 系统,调用ioctlsocket(socket_fd, FIONBIO, (u_long[]){1})
. 将出现与上述相同的语义,除了errno
代码不会是errno
代码(相反,它们将是GetLastError()
代码)并且它们......可能是不同的值,我不知道。但是,它们中的许多名称相似,因此在我的项目中,我通常使用以下名称:
#ifdef _WIN32
#define set_nonblock(fd) (ioctlsocket(fd, FIONBIO, (u_long[]){1}) == 0)
#define EAGAIN WSAEWOULDBLOCK
#define EWOULDBLOCK WSAEWOULDBLOCK
#define EISCONN WSAEISCONN
#define EINPROGRESS WSAEINPROGRESS
#define EINVAL WSAEINVAL
#define EALREADY WSAEALREADY
#else
#define set_nonblock(fd) (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) != -1)
#endif
不值一提
单独使用非阻塞套接字,如果您设法在各种系统上使用单个线程维护数千个连接,而无需进行少量调整,我一点也不感到惊讶。但是,这种模型并不理想,因为您需要一个繁忙的循环来循环遍历每个套接字,并在每个循环中即时测试它们的事件;例如,而不是在事件到达时触发您的代码以由操作系统唤醒。
我们知道,为了向应用程序发送事件,内核需要处理这些事件,因此我们可以给它一些时间sleep(0);
,例如,作为快速修复。这肯定会看到 CPU 使用率从接近 100% 下降到 10% 以下。但是,存在另一种方法,将多个(阻塞或非阻塞)套接字与非阻塞(或超时中断)函数多路复用,这样该函数将在某些数据可用时立即返回,或者将等到时间到期接收数据。
select
有明显的优势,但也有缺点;也就是说,这些集合通常仅限于少量的套接字;为了支持大量的套接字,你需要一个循环中的循环,因为你会发现64 个套接字限制(或任何它)很快就会用完。此外,它并不能解决connect
阻塞问题(就像O_NONBLOCK
and ~FIONBIO` 方法所做的那样)。
因此,我将不再谈论select
; 我将描述您可以使用的其他选项。另一个具有类似限制的例子是poll
;我也不会谈论这个。如果你想知道,网上有很多关于它的...
请注意,从这一点开始的所有内容都非常不可移植(尽管您可能会找到将它们全部包装到一个通用接口中的方法,例如curl multi所做的)。
异步套接字调用将开始一个连接,然后像非阻塞套接字调用一样立即返回,除了它们还会在连接完成时引发信号或调用您指定的函数。这是让操作系统控制在事件到达时通知您的代码,而不是让操作系统等待您。应该清楚的是,就优化而言,异步套接字是理想的,但它们不是可移植的。每个操作系统有多种选择:
epoll
对于 Linuxkqueue
对于 FreeBSD(可能还有 OS X?)WSAAsyncSelect
。所有这些都有一个共同点,那就是它们在成功或失败时调用一个函数(或引发一个信号,您可以将其转换为对函数的调用)。但是,它们的界面并不是那么熟悉。
通常,我没有费心为它们编写任何类型的包装器,因为我发现我在这个答案开头提到的非阻塞套接字现在已经绰绰有余了。重要的是我不需要将它移植到每个系统,因为……我懒得做那个!只有当有人向我展示该系统运行缓慢时,我才会针对该系统进行优化。否则,我们最终会陷入人们甚至可能永远不会在...上使用我们的软件的系统洪流中。