(ps: 就自己留着以后看,对大家的帮助不大)
问题描述:
在用浏览器连接 webserver 服务器,并选择查看图片或者视频时发现图片传一半,视频传不过来。(准确说,当时发现的问题是小图片能正确传输,图片大了就传不过来)
尝试了一下 tcpdump
(其实感觉没看出来啥),但是当看到传输过程中 seq 和 ack 以及 length 之间没有很好的对应,又确认连接没有被断开。已经察觉的是系统调用 writev()
这一个阶段出现了问题。检查代码:
bool http_conn::write(){
int temp = 0;
if( bytes_to_send == 0 ){
modfd( m_epollfd, m_sockfd, EPOLLIN );
init();
return true;
}
while( 1 ){
// iovec 结构体封装了一块内存的地址起始位置和长度
// struct iovec {
// ptr_t iov_base; /* Starting address */
// size_t iov_len; /* Length in bytes */
// }; */
temp = writev( m_sockfd, m_iv, m_iv_count ); // #include <sys/uio.h>
if( temp <= -1 ){
/* 如果 TCP 写缓冲没有空间。则等待下一轮 EPOLLOUT 事件。虽然在此期间,服务器无法立即接收到同一
客户的下一个请求,但这可以保证连接的完整性 */
if( errno == EAGAIN ){
modfd( m_epollfd, m_sockfd, EPOLLOUT );
return true;
}
unmap();
return false;
}
// to_send 需要发送的
// have_send 已经发送的
// printf("bytes_to_send_before:%d\n", bytes_to_send);
// printf("bytes_have_send_after:%d\n", bytes_have_send);
bytes_to_send -= temp;
bytes_have_send += temp;
// printf("bytes_to_send:%d\n", bytes_to_send);
// printf("bytes_have_send:%d\n", bytes_have_send);
if( bytes_have_send >= m_iv[0].iov_len){
m_iv[0].iov_len = 0;
m_iv[1].iov_base = m_file_address + (bytes_have_send - m_write_idx);
m_iv[1].iov_len = bytes_to_send;
}
else{
m_iv[0].iov_base = m_write_buf + bytes_have_send;
m_iv[0].iov_len = m_iv[0].iov_len - bytes_have_send;
}
if( bytes_to_send <= 0 ){
unmap();
modfd(m_epollfd, m_sockfd, EPOLLIN);
if( m_linger ){
init();
modfd( m_epollfd, m_sockfd, EPOLLIN );
return true;
}
else{
modfd( m_epollfd, m_sockfd, EPOLLIN );
return false;
}
}
}
}
这已经是修改后的代码,关键点就在注释处:
-
// iovec 结构体封装了一块内存的地址起始位置和长度 // struct iovec { // ptr_t iov_base; /* Starting address */ // size_t iov_len; /* Length in bytes */ // };
-
bytes_to_send bytes_have_send
之前的代码找不到不好做对比了,这里就简单记一下当时问题是什么原因。
简单来说就是在进行写操作时,没有将图片和视频的数据大小传递在 bytes_to_send
也就是 http_conn::process_write( HTTP_CODE ret)
这个方法中的这一步:
case FILE_REQUEST:
{
add_status_line( 200, ok_200_title );
if( m_file_stat.st_size != 0 ){
add_headers( m_file_stat.st_size );
m_iv[0].iov_base = m_write_buf; // 这里是自己写的响应的内存地址
m_iv[0].iov_len = m_write_idx; // 响应数据长度
m_iv[1].iov_base = m_file_address; // 文件内存地址(照片或者视频)
m_iv[1].iov_len = m_file_stat.st_size; // 文件大小
m_iv_count = 2;
bytes_to_send = m_write_idx + m_file_stat.st_size; // 之前没有这一步
return true;
}
当然其他地方也产生了影响,但是核心是这里。