Axel之 -axel_do剖析
axel_do主体部分,尝试从多个连接select方式去读取数据,如果读取失败或者连接超时就重新连接。
下面是代码分析.
- //下载的主循环
- void axel_do( axel_t *axel )
- {
- fd_set fds[1];
- int hifd, i;
- long long int remaining,size;
- struct timeval timeval[1];
-
- /* Create statefile if necessary */
- //如果到了保存状态的时间,保存当前状态到状态文件
- if( gettime() > axel->next_state )
- {
- //把连接的个数,当前下载量,每个连接当前的下载进度保存起来,
- //如果状态文件已经存在,就清空重写,目的是只保存一份
- //这样的话,如果程序异常退出,下次开启任务时,就能从状态文件中
- //重新加载最近的下载状态(不是100%准确的状态,因为保存状态是有周期的),
- //接着下载
- save_state( axel );
- //计算下一次状态保存时间
- axel->next_state = gettime() + axel->conf->save_state_interval;
- }
-
- //采用多线程做连接,但是还是单线程select做传输
- /* Wait for data on (one of) the connections */
- FD_ZERO( fds );
- hifd = 0;
- //经典的select方式,把有用的socket描述符加入到集合中
- //而且,限于文件下载的特殊任务方式,基本上每个建立好的连接
- //都不会太空,有恒定持续的数据传输,因此在这里select的效率并不弱于epoll
- for( i = 0; i < axel->conf->num_connections; i ++ )
- {
- if( axel->conn[i].enabled )
- FD_SET( axel->conn[i].fd, fds );
- hifd = max( hifd, axel->conn[i].fd );
- }
- //没有任何连接,等待重试
- if( hifd == 0 )
- {
- /* No connections yet. Wait... */
- usleep( 100000 );
- goto conn_check;
- }
- else
- {
- //超时0.1秒
- timeval->tv_sec = 0;
- timeval->tv_usec = 100000;
- /* A select() error probably means it was interrupted
- by a signal, or that something else's very wrong... */
- //select等待数据到来
- if( select( hifd + 1, fds, NULL, NULL, timeval ) == -1 )
- {
- //值位ready为-1,这样,下载的主循环将退出,下载失败
- axel->ready = -1;
- return;
- }
- }
-
- /* Handle connections which need attention */
- //循环读取每个socket的数据
- for( i = 0; i < axel->conf->num_connections; i ++ )
- if( axel->conn[i].enabled ) {
- //检测,如果该socket有数据到来,就读取
- if( FD_ISSET( axel->conn[i].fd, fds ) )
- {
- //更新最后一次读取数据的时间
- axel->conn[i].last_transfer = gettime();
- //尝试读取数据
- size = read( axel->conn[i].fd, buffer, axel->conf->buffer_size );
- //读取失败
- if( size == -1 )
- {
- if( axel->conf->verbose )
- {
- axel_message( axel, _("Error on connection %i! "
- "Connection closed"), i );
- }
- //关闭当前连接,并不等于放弃,还可能重新连接。。
- axel->conn[i].enabled = 0;
- conn_disconnect( &axel->conn[i] );
- continue;
- }//当前连接的数据读取结束
- else if( size == 0 )
- {
- if( axel->conf->verbose )
- {
- /* Only abnormal behaviour if: */
- if( axel->conn[i].currentbyte
< axel->conn[i].lastbyte && axel->size != INT_MAX )
- {
- axel_message( axel, _("Connection %i unexpectedly closed"), i );
- }
- else
- {
- axel_message( axel, _("Connection %i finished"), i );
- }
- }
- //如果是不支持并发分片下载(也就是说是单连接下载),表明下载完成
- if( !axel->conn[0].supported )
- {
- axel->ready = 1;
- }
- axel->conn[i].enabled = 0;
- conn_disconnect( &axel->conn[i] );
- continue;
- }
- /* remaining == Bytes to go */
- remaining = axel->conn[i].lastbyte - axel->conn[i].currentbyte + 1;
- //需要填充的小于读取的,下载完成
- if( remaining < size )
- {
- if( axel->conf->verbose )
- {
- axel_message( axel, _("Connection %i finished"), i );
- }
- axel->conn[i].enabled = 0;
- //关闭连接
- conn_disconnect( &axel->conn[i] );
- //修改需要的数据量大小,比如需要20字节,下载了30字节,那么就只要20字节
- size = remaining;
- /* Don't terminate, still stuff to write! */
- }
- /* This should always succeed.. */
- //调整偏移,写文件
- lseek( axel->outfd, axel->conn[i].currentbyte, SEEK_SET );
- if( write( axel->outfd, buffer, size ) != size )
- {
- //写失败,退出
- axel_message( axel, _("Write error!") );
- axel->ready = -1;
- return;
- }
- //修改偏移
- axel->conn[i].currentbyte += size;
- axel->bytes_done += size;
- }
- else //当前socket描述符不在select中,检查超时
- {
- //传输超时,关闭连接
- if( gettime() > axel->conn[i].last_transfer + axel->conf->connection_timeout )
- {
- if( axel->conf->verbose )
- axel_message( axel, _("Connection %i timed out"), i );
- conn_disconnect( &axel->conn[i] );
- axel->conn[i].enabled = 0;
- }
- } }
- //如果一切ok,就返回
- if( axel->ready )
- return;
-
- conn_check:
- /* Look for aborted connections and attempt to restart them. */
- //检查有问题的连接,如果未下载完,并且出错了,重新启动线程,开始传输
- for( i = 0; i < axel->conf->num_connections; i ++ )
- {
- //连接无效并且未下载完
- if( !axel->conn[i].enabled && axel->conn[i].currentbyte < axel->conn[i].lastbyte )
- {
- //状态为0,表明setup_thread是成功执行了的,并且已经执行问,因此,连接的初始化没问题,调用join回收
- if( axel->conn[i].state == 0 )
- {
- // Wait for termination of this thread
- pthread_join(*(axel->conn[i].setup_thread), NULL);
-
- conn_set( &axel->conn[i], axel->url->text );
- axel->url = axel->url->next;
- /* axel->conn[i].local_if = axel->conf->interfaces->text;
- axel->conf->interfaces = axel->conf->interfaces->next; */
- if( axel->conf->verbose >= 2 )
- axel_message( axel, _("Connection %i downloading from %s:%i using interface %s"),
- i,
axel->conn[i].host, axel->conn[i].port, axel->conn[i].local_if
);
-
- //状态设置为1,表示setup_thread开始执行,设置为0,表示setup_thread线程执行结束
- axel->conn[i].state = 1;
- if( pthread_create(
axel->conn[i].setup_thread, NULL, setup_thread, &axel->conn[i]
) == 0 )
- {
- axel->conn[i].last_transfer = gettime();
- }
- else
- {
- axel_message( axel, _("pthread error!!!") );
- axel->ready = -1;
- }
- }
- else //setup_thread线程还未执行完,也就是说连接建立过程还未完成,需要检查连接超时
- {
- //超时了,就取消她...
- if( gettime() > axel->conn[i].last_transfer + axel->conf->reconnect_delay )
- {
- pthread_cancel( *axel->conn[i].setup_thread );
- axel->conn[i].state = 0;
- }
- }
- }
- }
- /* Calculate current average speed and finish_time */
- //计算平均速度
- axel->bytes_per_second = (int) ( (double) (
axel->bytes_done - axel->start_byte ) / ( gettime() -
axel->start_time ) );
- //估算结束时间
- axel->finish_time = (int) ( axel->start_time +
(double) ( axel->size - axel->start_byte ) /
axel->bytes_per_second );
- /* Check speed. If too high, delay for some time to slow things
- down a bit. I think a 5% deviation should be acceptable. */
- //速度调整
- if( axel->conf->max_speed > 0 )
- {
- //如果超速了
- if( (float) axel->bytes_per_second / axel->conf->max_speed > 1.05 )
- axel->delay_time += 10000;
- //速度太低,少休息会儿
- else if( ( (float) axel->bytes_per_second /
axel->conf->max_speed < 0.95 ) && ( axel->delay_time
>= 10000 ) )
- axel->delay_time -= 10000;
- //速度太低,干脆不休息
- else if( ( (float) axel->bytes_per_second / axel->conf->max_speed < 0.95 ) )
- axel->delay_time = 0;
- usleep( axel->delay_time );
- }
-
- /* Ready? */
- //下载完了?
- if( axel->bytes_done == axel->size )
- axel->ready = 1;
- }