WebServer传输大文件致客户端自动关闭

news2024/12/26 23:45:46

程序运行在云服务器上, Ubuntu 20.04LTS系统,用浏览器测试能正常打开页面,请求一般的html文本和几十kb的小图片无问题,接着放了一个1.63MB( 1714387字节)的网上找的图过去,客户端图没加载完就自动断连了,应用上看没问题,但我的设计是响应头默认Connection: keep-alive, 客户端自动断连了明显跟设计不符,遂开始debug。

image-20230113191218513

首先排除SIGPIPE,因为自己strace并没有看到有sigpipe,再者客端断后服务器依然正常。strace一看是客户端自己断连触发了服务端EventLoop上的EPOLLRDHUP事件,到这就开始盲区搞不懂了。

自己之后瞎搞了半天,改函数打日志什么的就不说了。

自己还了写客户端模拟发送HTTP报文测试,显示normal close,读了80000多字节,喂喂你都没有读完耶,怎么就读到0了?

int main(int argc, char *argv[]){
    const char *ip = argv[1];
    int port = atoi(argv[2]);
    if(argc < 3){
        printf("usage:%s ip port\n", argv[0]);
        return;
    }
    int sockfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(sockfd != -1);
    struct sockaddr_in serv_addr;
    socklen_t serv_addr_len = sizeof(serv_addr); 
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(ip);
    serv_addr.sin_port = htons(port);
    assert(connect(sockfd, (sockaddr*)&serv_addr, serv_addr_len) != -1);
    
    char req[1024] = {0}; 
    const char *path = "image.png";
    snprintf(req, 45 + strlen(path),"GET /%s HTTP/1.1\r\nConnection: keep-alive\r\n\r\n",path);
    char buf[1024] = {0};
    size_t wlen = -1;
    size_t nread = 0;
    do{
        size_t wlen = write(sockfd,req, strlen(req));
        printf("write: %s\n", req);
        size_t rlen = read(sockfd, buf, sizeof(buf));
        buf[rlen] = 0;
        nread += rlen;
        printf("read: %s\n", buf);
        if(rlen > 0){
        }
        else if(rlen == 0){
            perror("normal close");
            break;
        }
        else{
            perror("read error:");
            break;
        }
    }while(wlen > 0);
	printf("total read: %d\n", nread);
    close(sockfd);
    return 0;
}

把服务端和客户端分别抽到两个虚拟机排除SSL的因素, 然后两个端设tcpdump抓包输出文件,结果wireshark一看还是客户端自己给服务端发了FIN报文,始终搞不懂为什么。希望有高人给出建议打破我的unknown unknown。

整个写响应流程涉及的代码如下:

void WebServer::EventLoop(){
    int timeoutMS = -1;
    while(1){
        int nevent = epoll_wait(epollfd_, events_, MAX_EVENTS, timeoutMS);
        for(int i = 0; i < nevent; ++i){
            int sockfd = events_[i].data.fd;
            uint32_t events = events_[i].events;
            if(sockfd == listenfd_){
                //add new connection
                dealNewConn();
            }
            else if(events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)){
                assert(connMap_[sockfd]);
                dealCloseConn(connMap_[sockfd]);
            }
            else if(events & EPOLLIN){
                //read request/close connection
                assert(connMap_[sockfd]);
                dealRead(connMap_[sockfd]);
            }
            else if(events & EPOLLOUT){
                //send response
                assert(connMap_[sockfd]);
                dealWrite_debug(connMap_[sockfd]);
            }
        }

    }
}

void WebServer::dealWrite_debug(const HttpConnectionPtr& client){
    //正常这里应该是扔线程池,这里debug排除了多线程干扰
    onWrite_debug(client);
}

void WebServer::onWrite_debug(const HttpConnectionPtr& client){
    //确保用于写的连接还在,防止SF,连接用智能指针管理时尤其注意!
    assert(client);
    int writeErrno = 0;
    ssize_t nwrite = client->writeOut_debug(&writeErrno);
    const string& clientAddr = client->getClientAddr().getIpPort();
    printf("write %ld byte to %s\n",nwrite,clientAddr.c_str());
    if(client->toWriteBytes() == 0){
        //传输完成
        if(client->isKeepAlive()){
            onProcess(client);
            return;
        }
    }
    else{
        //传输未完成, 场景包括:正在写的时候对端断开连接
        if(writeErrno == EAGAIN || writeErrno == EWOULDBLOCK){
            epoll_modfd(epollfd_, client->getSocket(), EPOLLOUT | connEvent_);
            return;
        }
    }
    dealCloseConn(client);
}

void WebServer::dealCloseConn(const HttpConnectionPtr& client){
    int connfd = client->getSocket();
    perror("status");
    epoll_delfd(epollfd_, connfd);
    connMap_.erase(connfd);
}

ssize_t HttpConnection::writeOut_debug(int *saveErrno){
    printf("Buffer write: \n");
    cout << string(outbuffer_.peek(), outbuffer_.readableBytes()) << endl;
    ssize_t len = -1;
    ssize_t nwrite = 0;
    do{
        len = writev(sockfd_, iov_, iovCnt_);
        if(iov_[0].iov_len + iov_[1].iov_len == 0){ break; }
        if(len <= 0){ saveErrno = &errno; }
        else if(static_cast<size_t>(len) <= iov_[0].iov_len){
            iov_[0].iov_base = (uint8_t*) iov_[0].iov_base + len;
            iov_[0].iov_len -= len;
            outbuffer_.retrieve(len);
        }
        else{
            iov_[1].iov_base = (uint8_t*) iov_[1].iov_base + (len - iov_[0].iov_len);
            iov_[1].iov_len -= (len - iov_[0].iov_len);
            if(iov_[0].iov_len) {
                outbuffer_.retrieveAll();
                iov_[0].iov_len = 0;
            }
        }
        nwrite += len;
        cout << "write: " << len << " " << "write bytes left: " << toWriteBytes() << endl;
    }while(HttpConnection::isET && toWriteBytes() > 1024);
    return nwrite;
}


//write_debug写响应操作前置的流程函数
bool HttpConnection::process(){
    if(inbuffer_.readableBytes() <= 0)
        return false;
    HttpRequest req;
    if(!httpParser_.parseRequest(inbuffer_,req))
        return false;
    assert(inbuffer_.readableBytes() == 0);
    inbuffer_.retrieveAll();

    string headkey = req.getHeader("Connection");
    if(headkey == string("keep-alive")){
        keepAlive_ = true;
    }
    else if(headkey == string("close")){
        close_ = true;
    }

    HttpResponse resp(close_);
    makeResponse(req,resp);
    return true;
}

void HttpConnection::makeResponse(HttpRequest& req,HttpResponse& resp){
    string path(srcDir_);
    string file = (req.getPath() == "/") ? "/index.html" : req.getPath();
    iovCnt_ = 1;

    cout << "path: " << path + file << endl;
    struct stat statbuf;
    if(stat(string(path + file).data(),&statbuf) >= 0 && !S_ISDIR(statbuf.st_mode)){
        iovCnt_++;    
        int filefd = open(string(path + file).data(), O_RDONLY);
        fileLen_ = statbuf.st_size;
        mmFile_ = mmap(0, fileLen_, PROT_READ, MAP_PRIVATE, filefd, 0);
        assert(mmFile_ != (void*)-1);
        close(filefd);
        resp.setFile(true);
    }

    if(resp.isHaveFile()){
        resp.setLine(HttpResponse::k200Ok);
        resp.setContentType(MimeType::getFileType(file));
        resp.setContentLength(fileLen_);
    }   
    else{
        resp.setLine(HttpResponse::k404NotFound);
        resp.setCloseConnection(true);
    }
    resp.appendAllToBuffer(outbuffer_);
    iov_[0].iov_base = const_cast<char*>(outbuffer_.peek());
    iov_[0].iov_len = outbuffer_.readableBytes();
    iov_[1].iov_base = mmFile_;
    iov_[1].iov_len = fileLen_;
}

HttpConnection类头部涉及部分:

class HttpConnection : noncopyable{
    public:
        HttpConnection(int sockfd, const INetAddress& peerAddr, const INetAddress& hostAddr);
        ~HttpConnection() { onClose();}
        ssize_t readIn(int* saveErrno);
        bool process();
       	。。。。
    public:
        static bool isET;
        static const char* srcDir_;
    private:
        void makeResponse(HttpRequest&,HttpResponse&);
   		。。。
        void *mmFile_;
        size_t fileLen_;
        
        struct iovec iov_[2];
        size_t iovCnt_;

        Buffer inbuffer_;
        Buffer outbuffer_;
        HttpParser httpParser_;
};

输出效果如下:

# Buffer read 的输出函数没包括在给出的代码里面 算补充吧
Buffer read: 
GET /image.png HTTP/1.1
Host: sss.sss.sss.sss:9006
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1

# 正式打印部分
path: /home/LinuxC++/Project/re_webserver/root/image.png
Buffer write: 
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 1714387
Content-Type: image/png


write: 143080 to write bytes left: 1571400
write 143080 byte to ccc.ccc.ccc.ccc:35521
status: Invalid argument
connection ccc.ccc.ccc.ccc:35521 -> sss.sss.sss.sss:9006 closed
connection ccc.ccc.ccc.ccc:35550 -> sss.sss.sss.sss:9006

抓包截图:(192.168.200.129为服务端 192.168.200.130为客户端)

image-20230113192941429

Note: 发现不同浏览器请求的行为不一样,Chrome除了请求页面还请求favicon.ico,而且似乎会自动检测是不是静态资源,是就自动断连,估计是我Chrome上插件的影响,Firefox就不会,后面的输出结果以Firefox为准。注意是Linux上的Firefox,试过Windows上的Firefox,不知为什么原因请求完资源后客端也会自动断连。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/160923.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何搭建一个专业的企业知识库

当客户跟你达成合作关系后&#xff0c;需要持续的关系维护&#xff0c;在一定的销售点&#xff0c;定期和客户沟通&#xff0c;据调查&#xff0c;赢得一个新客户的成本可能是保留一个现有客户的5到25倍&#xff0c;作为营销策略&#xff0c;客户服务支持必须满足他们的期望。建…

Linux小黑板(7):再谈动静态

"我看到&#xff0c;久违的晴朗啊"一、什么是动静态库在本栏目前面的篇幅也提到过这个概念&#xff0c;因此本小节就小小地回顾一番。在linux下:静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。动态库(.so):程序在运行的时候才去链接动态库的代码&am…

【npm报错】解决invalid json response body at https://registry.npmjs.org

报错信息&#xff1a; npm ERR! code FETCH_ERROR npm ERR! errno FETCH_ERROR npm ERR! invalid json response body at https://registry.npmjs.org/riophae%2fvue-treeselect reason: Invalid response body while trying to fetch https://registry.npmjs.org/riophae%2f…

从粪便菌群移植到下一代有益菌:Anaerobutyricum soehngenii为例

谷禾健康 我们知道&#xff0c;肠道微生物群对人类健康和福祉很重要&#xff0c;调节宿主代谢&#xff0c;塑造免疫系统并防止病原体定植。 通过粪便微生物群移植&#xff08;FMT&#xff09;恢复平衡多样的微生物群&#xff0c;已成为研究疾病发病机制中微生物群因果关系的潜在…

Spring Cloud Gateway 之限流

文章目录一、常见的限流场景1.1 限流的对象1.2 限流的处理方式1.3 限流的架构二、常见的限流算法2.1 固定窗口算法&#xff08;Fixed Window&#xff09;2.2 滑动窗口算法&#xff08;Rolling Window 或 Sliding Window&#xff09;2.3 漏桶算法&#xff08;Leaky Bucket&#…

CSS3属性之text-overflow:ellipsis详解

1.text-overflow: 默认值:clip; 适用于:所有元素 clip:当前对象内文本溢出时不显示省略标记(…),而是将溢出部分裁剪。 ellipsis:当对象内文本一处时显示省略标记(…)。 当然这还是不够的&#xff0c;需要加点调料才能出现效果: 那就是配合 overflow:hidden white-space:…

高级树结构之红黑树初识

文章目录一 红黑树简介二 探究变色、旋转操作的时机三 总结一 红黑树简介 通过在插入几点时维护数的平衡&#xff0c;这样就不会出现极端情况&#xff0c;使得整棵树的查找效率急剧降低。但是这样造成系统开销过大&#xff0c;因为一旦平衡因子的绝对值超过一就失衡&#xff0…

iOS 界面尺寸居然跟实际机型不符!

0x00 前言 日常搬砖过程中&#xff0c;一条日志&#xff0c;让我对手里的 iPhone 6 Plus 产生了怀疑&#xff1f; 这是 6P&#xff0c;怎么尺寸变成 6 了呢&#xff1f; 0x01 对比 手机连上电脑&#xff0c;通过 Xcode 查看 Window 菜单&#xff1a;Devices and Simulators …

Insight Enterprises EDI 855 采购订单确认报文详解

本文着重讲述Insight EDI项目中Insight回复给采购商的X12 855报文&#xff08;采购订单确认&#xff09;。 在此前的文章如何读懂X12报文中&#xff0c;我们对X12报文的结构已经做了详细的介绍&#xff0c;本文将带大家深入了解X12 855采购订单确认报文。 下图为Insight X12 …

单绞机张力开环控制(绞臂行星差速机构)

PLC的开环和闭环张力控制算法,可以参看下面的文章链接: PLC张力控制(开环闭环算法分析)_plc张力控制程序_RXXW_Dor的博客-CSDN博客里工业控制张力控制无处不在,也衍生出很多张力控制专用控制器,磁粉制动器等,本篇博客主要讨论PLC的张力控制相关应用和算法,关于绕线机的…

Java学习之final关键字

目录 一、基本介绍 二、使用final的四种情况 第一种 第二种 第三种 第四种 三、细节 第一点 第二点 第三点 第四点 第五点 第六点 第七点 第八点 第九点 四、练习 第一题 第二题 分析 一、基本介绍 final:最终的&#xff0c;最后的 final 可以修饰类…

图形编辑器:旋转选中的元素

大家好&#xff0c;我是前端西瓜哥。 最近更文比较少&#xff0c;是因为本人在做个人开源项目&#xff0c;用 Canvas 做一个设计工具&#xff0c;做个乞丐版 figma。期间遇到了不少问题&#xff0c;在这里记录一下。 今天开始会恢复高频更新的&#xff0c;一两天一更。内容主…

Linux网络服务管理防火墙详解端口问题

每次配置访问服务器都会一团迷雾&#xff0c;今天来尝试弄清楚同时借鉴一下大佬的博文当做笔记 文章目录防火墙简介一、防火墙基础二、iptables防火墙策略iptables 命令格式&#xff1a;iptables案例1.查看开放的端口2.开放端口&#xff08;此处以80端口为例&#xff09;3.关闭…

超级详细的PMP复习方法,3A拿下考试不发愁!

如果问我是怎么一次性通过考试的&#xff0c;那绝对不只是运气&#xff0c;没有一点基本的实力怎么有底气通过考试呢&#xff0c;所以今天我们不讲什么刷题技巧&#xff0c;基础不牢靠将技巧都是没用的&#xff0c;今天我们先讲讲要怎么巩固基础&#xff0c;给到朋友们分享一些…

kaggle平台学习复习笔记 | 特征工程

目录数值字段roundBox/Bins类别字段onehotLabelEncoderOrdinal EncodingBinaryEncoderFrequency/Count EncodingMean/Target Encoding日期字段特征筛选feature_importances_利用方差利用相关性利用线性模型迭代消除排列重要性(Permutation Importance)特征工程决定了模型精度的…

MATLAB-RBF神经网络例1

采用所描述的系统中﹐假设真实质量为m2,在仿真中,初始值为,采用的自适应律为&#xff1a;设定参数为y0.5,10,25,6&#xff0c;分别设定参考位置为r(t)0,r(t )sin(4t) ,初始条件为&#xff0c;。图1.1和图1.2为指令r(t)0时控制效果,图1.3和图1.4为指令r(t)sin(4t )时的控制效果。…

verilog学习笔记- 12)触摸按键控制LED灯实验

目录 简介&#xff1a; 实验任务: 硬件设计: 程序设计: 下载验证&#xff1a; 简介&#xff1a; 触摸按键主要可分为四大类&#xff1a;电阻式、电容式、红外感应式以及表面声波式。根据其属性的不同&#xff0c;每种触摸按键都有其合适的使用领域。 电阻式触摸按键&#…

响应式与观察者模式

什么是响应式&#xff1f;响应式 是Vue 最独特的特性之一&#xff0c;是非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时&#xff0c;视图会进行更新。我们也叫他双向绑定。如果想要更改视图&#xff0c;只要直接更改对应属性的值即可&#xff…

从隔壁老王开始的信号处理入门

诸神缄默不语-个人CSDN博文目录 我是从GNN被扔到NLP然后又做起了GNN现在又被喊去搞时间序列分类&#xff0c;所以现在才开始看信号处理&#xff08;因为我开始做GNN以来&#xff0c;GNN就以图域而非谱域为主了&#xff0c;所以那时我没怎么看过信号处理&#xff09;。 所以写个…

RabbitMQ消息队列(三):任务分发机制

在上篇文章中&#xff0c;我们解决了从发送端&#xff08;Producer&#xff09;向接收端&#xff08;Consumer&#xff09;发送“Hello World”的问题。在实际的应用场景中&#xff0c;这是远远不够的。从本篇文章开始&#xff0c;我们将结合更加实际的应用场景来讲解更多的高级…