我的朋友和我在C中编写了一个小型下载管理器,它将目标文件分成几个部分,并使用一个posix线程下载每个部分。一切似乎都运行良好,除了它与其他下载管理器(如wget)相比非常慢(据我所知,它不会将文件分成几个块)。在每个线程中,我们使用一个简单的循环从套接字下载每个部分:
while ((nrecv = recv(sockfd, downbuf, sizeof(downbuf), 0)) > 0)
{
if ((nwrite = write(fd, downbuf, nrecv)) != nrecv)
die("write");
totalrw += nwrite;
}
/* ... */
我尝试过几种不同尺寸的“downbuf”,比如2014,2048,4096和8192,但差别不大。下载270 MB文件大约需要45秒,而wget只需5秒即可下载相同的文件。服务器和客户端都在同一主机上。为什么差异如此之大?你能告诉我wget使用什么技巧吗?
这是我向服务器发出请求的方式:
sockfd = make_conn(website);
hdr.rq_buf = headerbuf; /* buffer to save response header */
hdr.rq_bufsize = sizeof(headerbuf);
hdr.rq_host = website;
hdr.rq_fpath = filepath; /* target file */
hdr.rq_flags = S_HEADFLAG; /* use head method at this moment
to get the total file size */
error = headerinit(hdr);
if (error)
{
die("headerinit()");
}
send(sockfd, headerbuf, strlen(headerbuf), 0); /* send the initial request */
recv(sockfd, respbuf, sizeof(respbuf), 0);
if (-1 == response_proc(respbuf, strlen(respbuf), &resp))
{
myperror("response_proc()");
exit(EXIT_FAILURE);
} /* process the header */
size_t sz = (size_t)strtol(resp.rs_content_length, NULL, 10);
divide(sz, chunks, numcons); /* divide the file into several parts */
for (int i = 0; i < numcons; i++)
{
/* populate data needed for threads */
args[i].t_hdr.rq_offset.c_start = chunks[i].c_start; /* where to start */
args[i].t_hdr.rq_offset.c_end = chunks[i].c_end; /* download up to this point */
args[i].t_hdr.rq_host = strdup(website);
args[i].t_hdr.rq_fpath = strdup(filepath);
snprintf(args[i].t_fname, BUFSIZ, "%sp%i", outfile, i);
args[i].t_order = i;
}
for (i = 0; i < numcons; i++)
{
if (0 != pthread_create(&threads[i], NULL, thread_main,
&args[i]))
{
die("pthread_create()");
}
}
for (i = 0; i < numcons; i++)
{
if (0 != pthread_join(threads[i], &thread_status))
{
die("pthread_join()");
}
}
http_request_header_t定义为:
typedef struct {
void *rq_buf;
size_t rq_bufsize;
char *rq_host;
char *rq_fpath;
chunk_t rq_offset;
int rq_flags;
} http_request_header_t;
和http_response_header_t定义为:
typedef struct {
#ifdef WITH_EXTRA_HEADERS
char *rs_version;
#endif
char *rs_status;
char *rs_date;
char *rs_server;
char *rs_last_modified;
char *rs_accept_ranges;
char *rs_content_length;
char *rs_connection;
char *rs_content_type;
} http_response_header_t;
这是每个线程使用的主要例程:
void *
thread_main(void *arg_orig)
{
thr_arg_t *arg = (thr_arg_t*)arg_orig;
int fd, sockfd;
http_response_header_t resp;
size_t totalrw = 0;
ssize_t nrecv;
char *line = malloc(BUFSIZ * sizeof(char));
char hdrbuf[BUFSIZ];
char respbuf[BUFSIZ];
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP;
ssize_t nwrite = 0;
void *downbuf = malloc(DOWNBUF * sizeof(char));
sockfd = make_conn(arg->t_hdr.rq_host);
fd = open(arg->t_fname, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode);
if (-1 == fd)
{
die("thread_open(): fd");
}
arg->t_hdr.rq_flags = S_OFFSET;
arg->t_hdr.rq_buf = hdrbuf;
arg->t_hdr.rq_bufsize = sizeof(hdrbuf);
headerinit(arg->t_hdr);
//printf("%s\n", arg->t_hdr.rq_buf);
sendn(sockfd, hdrbuf, strlen(hdrbuf), 0);
/* first, read the header */
while ((nrecv = readheader(sockfd, &line, BUFSIZ)) > 0)
{
strncpy(respbuf + nwrite, line, sizeof(respbuf) - nwrite);
nwrite += nrecv;
}
nwrite = 0;
//printf("\n\n%s\n\n", respbuf);
if (-1 == response_proc(respbuf, strlen(respbuf), &resp))
{
myperror("thread_response_proc()");
exit(EXIT_FAILURE);
}
if (strncmp(resp.rs_status, "416", 3) == 0)
{
fprintf(stderr, "Partial content is not supported by the server\n");
exit(EXIT_FAILURE);
}
/* now read the actual data */
while ((nrecv = recv(sockfd, downbuf, sizeof(downbuf), 0)) > 0)
{
if ((nwrite = write(fd, downbuf, nrecv)) != nrecv)
die("write");
totalrw += nwrite;
}
if(-1 == nrecv)
{
die("recv()");
}
close(sockfd);
close(fd);
idxwr(arg->t_fname, arg->t_order, totalrw);
return ((void*)0);
}
答案 0 :(得分:0)
你在这里发布的不够,但通常TCP的意外放缓的原因是Nagle's algorithm。将大块数据写入套接字时会触发此操作。这些在自己上线时是低效的,因此TCP堆栈等待用户程序在发送数据包之前添加更多数据。只有在“暂时”没有添加任何内容的情况下,它才会实际发送不完整的数据包。
这可以被禁用,但由于您的目标是有效的批量转移,您可能不应该这样做。