Qihoo360 / evpp

A modern C++ network library for developing high performance network services in TCP/UDP/HTTP protocols.
BSD 3-Clause "New" or "Revised" License
3.59k stars 943 forks source link

client socket 10053 error #87

Open skychips opened 7 years ago

skychips commented 7 years ago

I1010 16:19:44.820283 7676 event_loop.cc:11] evpp::EventLoop::EventLoop this=00646770 I1010 16:19:44.869289 7676 event_loop.cc:56] evpp::EventLoop::Init this=00646770

I1010 17:14:01.049772 7676 eventloop.cc:289] evpp::EventLoop::QueueInLoop this=00646770 call watcher->Nofity() notified_.store(true) I1010 17:14:01.049772 8216 event_watcher.cc:154] PipeEventWatcher::HandlerFn fd=244 v=00645718 I1010 17:14:01.050272 8216 event_loop.cc:303] evpp::EventLoop::DoPendingFunctors this=00646770 pending_functorcount=1 PendingQueueSize=1 notified_=1 I1010 17:14:01.050272 8216 event_loop.cc:326] evpp::EventLoop::DoPendingFunctors this=00646770 pending_functorcount=1 PendingQueueSize=0 notified_=0 I1010 17:14:01.050272 8216 event_loop.cc:328] evpp::EventLoop::DoPendingFunctors this=00646770 pending_functorcount=1 PendingQueueSize=0 notified_=0 I1010 17:14:01.050272 8216 event_loop.cc:333] evpp::EventLoop::DoPendingFunctors this=00646770 pending_functorcount=0 PendingQueueSize=0 notified_=0 I1010 17:14:01.398816 8216 fd_channel.cc:157] evpp::FdChannel::HandleEvent this=006439C8 fd=348 kReadable

I1010 17:14:01.399317 8216 tcp_conn.cc:206] evpp::TCPConn::HandleRead this=0066FD30 errno=10053 您的主机中的软件中止了一个已建立的连接。

We are closing this connection now. I1010 17:14:01.399317 8216 tcp_conn.cc:287] evpp::TCPConn::HandleError this=0066FD30 fd=348 status=kConnected I1010 17:14:01.399317 8216 tcpconn.cc:247] evpp::TCPConn::HandleClose this=0066FD30 addr=(192.168.1.2:4931(local)->43.241.48.144:12345) fd=348 status=kDisconnecting I1010 17:14:01.399317 8216 fd_channel.cc:116] evpp::FdChannel::DetachFromLoop this=006439C8 fd=348 detach from event loop I1010 17:14:01.399317 8216 fd_channel.cc:28] evpp::FdChannel::Close this=006439C8 fd=348 W1010 17:14:01.399317 8216 Client.cpp:67] Disconnected from 43.241.48.144:12345 I1010 17:14:01.399317 8216 tcpconn.cc:282] evpp::TCPConn::HandleClose this=0066FD30 addr=(192.168.1.2:4931(local)->43.241.48.144:12345) fd=348 status=kDisconnecting use_count=1 I1010 17:14:01.399317 8216 tcp_conn.cc:36] evpp::TCPConn::~TCPConn this=0066FD30 name=Client channel=006439C8 fd=348 type=1 status=kDisconnected addr=(192.168.1.2:4931(local)->43.241.48.144:12345)

请问,为什么会出现这种情况。

1个小时多点,就会出现。服务端承载200+用户数量。

skychips commented 7 years ago

经过了长达几个小时的抓包。 使用 Wireshark 抓包 当客户端连续5次读取数据失败(tcp retransmission) 就会与服务端主动断开连接。 这样服务端与客户端同时报出 10053 异常

I1010 17:14:01.399317 8216 tcp_conn.cc:206] evpp :: TCPConn :: HandleRead this = 0066FD30 errno = 10053您的主机中的软件中止了一个已建立的连接

void TCPConn::HandleRead() { assert(loop_->IsInLoopThread()); int serrno = 0; ssize_t n = inputbuffer.ReadFromFD(chan_->fd(), &serrno); if (n > 0) { msgfn(shared_from_this(), &inputbuffer); } else if (n == 0) { if (type() == kOutgoing) { // This is an outgoing connection, we own it and it's done. so close it DLOGTRACE << "fd=" << fd << ". We read 0 bytes and close the socket."; status_ = kDisconnecting; HandleClose(); } else { // Fix the half-closing problem : https://github.com/chenshuo/muduo/pull/117

        chan_->DisableReadEvent();
        if (close_delay_.IsZero()) {
            DLOG_TRACE << "channel (fd=" << chan_->fd() << ") DisableReadEvent. delay time " << close_delay_.Seconds() << "s. We close this connection immediately";
            DelayClose();
        } else {
            // This is an incoming connection, we need to preserve the
            // connection for a while so that we can reply to it.
            // And we set a timer to close the connection eventually.
            DLOG_TRACE << "channel (fd=" << chan_->fd() << ") DisableReadEvent. And set a timer to delay close this TCPConn, delay time " << close_delay_.Seconds() << "s";
            delay_close_timer_ = loop_->RunAfter(close_delay_, std::bind(&TCPConn::DelayClose, shared_from_this())); // TODO leave it to user layer close.
        }
    }
} else {
    if (EVUTIL_ERR_RW_RETRIABLE(serrno)) {
        DLOG_TRACE << "errno=" << serrno << " " << strerror(serrno);
    } else {
        DLOG_TRACE << "errno=" << serrno << " " << strerror(serrno) << " We are closing this connection now.";
        HandleError();
    }
}

}

百度查阅了点资料

相关资料:TCP协议是一个可靠的协议。它通过重新发送(retransmission)来实现TCP片段传输的可靠性。简单的说,TCP会不断重复发送TCP片段,直到片段被正确接收。

还望作者指条客户端的明路。

skychips commented 7 years ago

最后一个请求。还望能够支持xp、IP转换的函数并没有实现XP的支持!

zieckey commented 7 years ago

很抱歉回复这么晚才回复。请参考 https://zhuanlan.zhihu.com/p/20752519 方便我这边重现你的问题。 xp的事情,我看看怎么解决(当前没有xp环境)

skychips commented 7 years ago

已经将客户端上传至Git。

https://github.com/intlinfo/Client/blob/master/Client/WindowsMain.cpp https://github.com/intlinfo/Client/blob/master/Client/Client.cpp https://github.com/intlinfo/Client/blob/master/Client/WorkServer.cpp

测试环境 Windows7 x64 Sp1 测试IDE:VS2013 UP5

服务端暂时没发现任问题!

这边主要咨询一下客户端在收取数据时(详见Evpp->tcp_conn.cc->178行->WSARecv) 外层返回

`void TCPConn::HandleRead() {
    assert(loop_->IsInLoopThread());
    int serrno = 0;
    ssize_t n = input_buffer_.ReadFromFD(chan_->fd(), &serrno);  // WSARecv
    if (n > 0) {
        msg_fn_(shared_from_this(), &input_buffer_);
    } else if (n == 0) {
        if (type() == kOutgoing) {
            // This is an outgoing connection, we own it and it's done. so close it
            DLOG_TRACE << "fd=" << fd_ << ". We read 0 bytes and close the socket.";
            status_ = kDisconnecting;
            HandleClose();
        } else {
            // Fix the half-closing problem : https://github.com/chenshuo/muduo/pull/117

            chan_->DisableReadEvent();
            if (close_delay_.IsZero()) {
                DLOG_TRACE << "channel (fd=" << chan_->fd() << ") DisableReadEvent. delay time " << close_delay_.Seconds() << "s. We close this connection immediately";
                DelayClose();
            } else {
                // This is an incoming connection, we need to preserve the
                // connection for a while so that we can reply to it.
                // And we set a timer to close the connection eventually.
                DLOG_TRACE << "channel (fd=" << chan_->fd() << ") DisableReadEvent. And set a timer to delay close this TCPConn, delay time " << close_delay_.Seconds() << "s";
                delay_close_timer_ = loop_->RunAfter(close_delay_, std::bind(&TCPConn::DelayClose, shared_from_this())); // TODO leave it to user layer close.
            }
        }
    } else {
        if (EVUTIL_ERR_RW_RETRIABLE(serrno)) {
            DLOG_TRACE << "errno=" << serrno << " " << strerror(serrno);
        } else {
            DLOG_TRACE << "errno=" << serrno << " " << strerror(serrno) << " We are closing this connection now.";
            HandleError(); // 5次重复读取数据失败 Close 。
        }
    }
}`

同样朋友写了一个测试代码。没有任何添加。

https://github.com/intlinfo/Client/blob/master/Console/Console.cpp

挂机测试24小时不会触发10053错误。

当然也有Tcp Retransmission的情况,但是数据都在尝试几次之后正确收取。而不会Close。 这种情况本地无法测试出来,远程服务端测试出问题,也得不少时间,有时1-3个小时内出现,有时10几个小时出现。

当然还有个问题:服务端将客户端连接 Close 后 客户端在Debug情况下会触发: error

skychips commented 7 years ago

该图片为Console的抓包图片。 test

该图片为Client抓包图片。 gkmvlkk7oo2o pp5tltpi x

skychips commented 7 years ago

default

这是 Wireshark 的抓包文件。

下载后 将文件后缀名更名为 .pcapng 用Wireshark 打开帮忙分析一下。

连续抓包几天了。测试了很多方法。依然出现10053!

skychips commented 7 years ago

我在找资料时,看见有人能重现10053错误。 http://www.cnblogs.com/zhcncn/articles/3048807.html 我也尝试了该方法,确实可以重现10053错误!

zieckey commented 7 years ago

周末我再看看,谢谢反馈啊

发自我的 iPhone

在 2017年10月12日,00:07,小小随 notifications@github.com 写道:

我在找资料时,看见有人能重现10053错误。 http://www.cnblogs.com/zhcncn/articles/3048807.html 我也尝试了该方法,确实可以重现10053错误!

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

skychips commented 7 years ago

非常感谢!

shgxwxl commented 7 years ago

1、retransmission 五次连接被rst是系统层面的行为 请关注下为什么包一直重传,是否是网络原因? http://blog.jobbole.com/71427/ 2、debug下assertion fail的原因是因为你在stop的时候调了 m_tcp_client->Disconnect(); 此时clien中的tcp连接已经关闭了,conn_已经被reset掉了,因此走入了else分支 这里的assert处理不够准确,没有考虑到tcp连接已被关闭时外部调用disconnect的场景

引发assert失败的代码 assert(connector && !connector->IsConnected()); 可以暂时注掉,后面我们考虑下怎么修复

skychips commented 7 years ago

我在调用Stop时 当服务端Close后,不能使用conn->IsConnected()函数,使用即触发断言。 或 当客户端主动退出,需使用m_tcp_client->Disconnect();先断开连接。不然m_loop->Stop(true);会触发断言。

所以m_tcp_client->Disconnect();这个函数不管怎样都必须调用。

skychips commented 7 years ago

Retransmission 我不确定是否为网络原因。我也很清楚系统默认是5次。 这问题困扰多日。无法解决,还望作者大佬们,能给出一个小小的方案,或者建议加入到逻辑层处理一下。 让我们使用者更为傻瓜式的去使用。

shgxwxl commented 7 years ago

tcp client的auto reconnect就是为了让使用者不关心底层的连接情况 你目前的实现是又基于tcp_client封装了自己的client,里边有一个map,管理了一组servers对象 这不符合client的设计初衷 逻辑上建议这么实现 你的WorkServer应该是一条逻辑上的连接,底层究竟是哪条TCP通道都不影响,究竟TCP连接是否出错等情况交给框架来处理 tcpclient->set_auto_reconnect(true)

1、一个CClient对应一个TCPClient对象和一个WorkServer对象 2、在OnConnection事件中 如果是如果是IsConnected事件,基于conn构造server对象 如果是其他事件,释放worker server对象,如下类似的代码

LOG_DEBUG << "OnTcpConnection called";
if (!tcp_conn->IsConnected()) {
    assert(status_ == kConnected);
    server_.reset();
    return;
}
server_.reset(new WorkServer(tcp_conn, this));
tcp_conn->set_context(evpp::Any(server_));
if (conn_fn_) {
    conn_fn_(status_);
}

3、server对象不应该暴露出去,应该在CClient封装一个sendmessage方法,将对server对象的处理都放在CClient的loop中,大概代码如下

LOG_DEBUG << "SendMessage called";
if (!server_) {
    LOG_ERROR << "connect not ok";
    return false;
}
loop_->RunInLoop(std::bind(
    &CClient::SendMessageInLoop,
    this,
    req_msg,
    cb)
);
return true;

}

skychips commented 7 years ago

我还是没解决。 有些想法我很认同。 但是开启tcpclient->set_auto_reconnect(true) 我并不认同。 至少要给出1个回调。 不然怎么处理断线、重连的一些动作。 假设一个游戏重连后还要提交用户名和密码对吧。

feihongmeilian commented 7 years ago

我觉得这样的设计并没有问题,值得商榷的可能是“CClient”这个类名,应该是一个管理类,管理着对多个服务端的链接,只是在这个客户端程序里刚好只有一个 “m_tcp_client->Connect();” 导致了你的误解。

而且我看你的描述应该不是在针对“evpp::TCPConn::HandleRead this=0066FD30 errno=10053 您的主机中的软件中止了一个已建立的连接。”这个错误,更多的可能是关注这个程序的设计,不过我想一段时间后10053的报错可能才是该关注的焦点

skychips commented 7 years ago

是的,只是个简单的测试客户端。 就像EVPP官方的例子,写的并不完善。 都是用于测试,所以不必关心设计本身的问题。我承认设计的不合理。

但问题不在于设计,而在于为什么频繁断线。

请问我需要把服务端的代码也git吗。或者方便加个QQ,这样我们可以很方便的商讨。

shgxwxl commented 7 years ago

之所以这样建议是因为断线的问题我们没有办法帮你们详细排查,因为可能引起tcp连接断开的场景太多了,这种问题的处理非常依赖开发人员的经验,并且也比较难排查 业务逻辑不能依赖tcp连接是否稳定,无论连接是否稳定,业务逻辑都应该能处理,做到这一点比保持一个非常稳定的tcp连接要简单一些

skychips commented 7 years ago

@shgxwxl 建议增加重连触发回调。(业务处理会增强许多) 建议考虑支持XP系统。(在中国XP用户还是绝大多数的) 将IP处理那里加入 sockets.cc 103行

> `
#if _WIN32_WINNT >= _WIN32_WINNT_VISTA
InetPton
#else
inet_addr(CT2A())
#endif

同样 180 行
#if _WIN32_WINNT >= _WIN32_WINNT_VISTA
inet_ntop
#else
WSAAddressToString
#endif

` 当然是基于 H_OS_WINDOWS 下

断线还望同志多加测试(不要局限于本地)。自从使用evpp我从没有怀疑过它、evpp是优秀的作品。我也曾向很多朋友推荐。 他们都很赞扬evpp。 希望作者们重视问题,谢谢。给你们带来的不便我很抱歉!

sunwangme commented 7 years ago

@intlinfo

你这个问题是个非常常见的问题,和evpp库的质量和架构没什么关系。

现象是:开个非阻塞socket发数据,数据先拷贝到协议栈,然后网络物理异常,服务器不可达+丢包协议栈单方面关闭连接,造成可读信号,然后读数据异常。

同类现象:服务器崩溃 路由器崩溃 网络拥挤 手机移动网络等等

说明一下: 1 不能以为客户端数据写到协议栈就成功了,重要的数据,都要在应用层自己做ack 2 你朋友的demo是阻塞调用,你自己写个select的非阻塞demo,跑跑就知道了。evpp的网络部分在windows上就是select

sunwangme commented 7 years ago

自己测试一下阻塞 nodelay的影响吧

sunwangme commented 7 years ago

我路人,不是做evpp的

skychips commented 7 years ago

@sunwangme 说的好,所有问题都推向了我这里。 行。我接了这个锅。 我问你。recv不是我能控制的(他在evpp内部很深的地方 难不成我要去改evpp核心代码?)。在接收完数据libevent还有很多东西已经改变了getlasterror的值,接下来我该怎么做呢?

我不知道我该怎么做ack了。还请兄弟继续批判我。

同样中国也有很多的开源网络库,据我了解的HPSocket他就不会有客户端与服务端断线的问题。除非客户端没有写保活功能。(话题扯的太远了,我并没有说HP多好,但我坚持的还是evpp)。

既然和evpp没关系,那就是我的问题了。

再说远一点。evpp有给我们异常的返回值函数没有。或者try啊。 我还能怎么测试,我测试的结果就是断线断线再断线。从10月1,到现在21天。我没有得到任何有价值的答复。 框架以外的东西我能处理就处理。我是要跟着evpp走的。框架内的代码不能修改,只能提建议或需求。如果我改了,官方更新或者出什么问题。那将会影响整体代码。

skychips commented 7 years ago

难道处理 socket的一系列异常不是库本身该做的事吗? 一旦出异常就直接close? 行,即便是你们不做异常的处理,至少你给抛出个异常,外面使用者也能去处理。

sunwangme commented 7 years ago

evpp的recv send buffer控制都是自己做的,很方便修改

recv很简单,在socket.cc最下面,里面用wsarecv实现的,原版libevent判断了recv的返回值,如果error=15003,返回n=0

send在fd-channel.cc,写之前设置监控写信号,写完后去写信号,和原版libevent一样的

evpp代码总共没多少行,自己随便改吧

sunwangme commented 7 years ago

1 你在服务端做个统计,统计下client掉线,是不是普遍情况 2 你写个select版的demo,和你朋友的同步版本对比 3 你可以跑个linux,跑两个demo,看看效果。evpp在windows上应该没人用过

skychips commented 7 years ago

还有9天一个月。我已经对比的很清楚了。两个代码都会断线。基于evpp的会断,简写的client demo 也会断线。

skychips commented 7 years ago

不用在写别的了,在写也是断线。

sunwangme commented 7 years ago

很多公司不用异常,特别是服务端,很多代码都是c的。google整个公司都不用异常。

都开源了,代码在手,自己改吧,改好了做个贡献

zieckey commented 7 years ago

@intlinfo 谢谢你这么长时间还一直在使用evpp,并一直在帮助我们改进evpp。最近因为个人原因,真的比较忙没时间仔细看这问题,今晚我仔细看看这个问题

@sunwangme 谢谢这位朋友参与讨论

skychips commented 7 years ago

这种问题,开发者一个团队都解决不掉。我只是个打杂的怎么会解决掉,不然我也不会跑过来一致追。 我的能力有限。我只是一个人,并不能和一个团队比。

sunwangme commented 7 years ago

libevent原版处理逻辑:buffer.c: evbuffer-read

#ifdef _WIN32
        {
            DWORD bytesRead;
            DWORD flags=0;
            if (WSARecv(fd, vecs, nvecs, &bytesRead, &flags, NULL, NULL)) {
                /* The read failed. It might be a close,
                 * or it might be an error. */
                if (WSAGetLastError() == WSAECONNABORTED)
                    n = 0;
                else
                    n = -1;
            } else
                n = bytesRead;
        }
#else
        n = readv(fd, vecs, nvecs);
#endif
zieckey commented 7 years ago

@sunwangme libevent原版处理逻辑,最终会反馈到上层应用 recvn=0,应用层一般也是关闭连接。evpp里面的逻辑是 HandleError,最终也会关闭连接。

是否可以理解为两者是一致的?

feihongmeilian commented 7 years ago

@zieckey 我在想如果10053像@sunwangme 说的那样是一个非常常见的错误,这里是不是应该通过SetCloseCallback挂载CloseCallback,当不是客户端主动断开的时候实现重连登陆等逻辑,不知道是否算一个合适的解决方式。

zieckey commented 7 years ago

@feihongmeilian 嗯,你说的这个方案 正是我刚刚跟 @intlinfo 讨论时的结论,他正在做这个尝试

feihongmeilian commented 7 years ago

@zieckey 一般来说这种错误在公司是如何解决的呢,不好意思,因为我写的代码都是自己在本机娱乐,公司上还未做过这方面的处理,如果方便,可否麻烦给我一些提示,非常感谢。

zieckey commented 7 years ago

我个人认为与连接所有的用户层数据都应该与TCP连接的状态强相关。

例如本项目中自带的evnsq这个客户端库,是与NSQ消息队列交互的一个C++客户端,当连接断开时,所有的状态都重新开始,重头开始经过 kConnecting、kIdentifying、kAuthenticating、kConnected、kSubscribing、kReady这些状态,最终可用时(作为消费者可以消费数据、作为生产者可以生产数据)状态转换为kReady

具体代码参考:https://github.com/Qihoo360/evpp/blob/master/apps/evnsq/nsq_conn.cc

void NSQConn::OnTCPConnectionEvent(const evpp::TCPConnPtr& conn) {
    DLOG_TRACE << "status=" << StatusToString() << " TCPConn=" << conn.get() << " remote_addr=" << conn->remote_addr();
    if (conn->IsConnected()) {
        assert(tcp_client_->conn() == conn);
        if (status_ == kConnecting) {
            Identify();
        } else {
            // Maybe the user layer has close this NSQConn and then the underlying TCPConn established a connection with NSQD to invoke this callback
            assert(status_ == kDisconnecting);
        }
    } else {
        if (tcp_client_->auto_reconnect()) {
            // tcp_client_ will reconnect to remote NSQD again automatically
            status_ = kConnecting;
        } else {
            // the user layer close the connection
            assert(status_ == kDisconnecting);
            status_ = kDisconnected;
        }

        if (conn_fn_) {
            conn_fn_(shared_from_this());
        }
    }
}
sunwangme commented 7 years ago

@zieckey,赞成你说的

1 10053是被当close还是error处理,差不多,都可以接受 2 断网重连时候,就是要自动重来一遍,10053等各种天灾人祸需要这个重连逻辑,这种设计非常常见;看看qq 微信的自动重连表现 @feihongmeilian

zieckey commented 7 years ago

@shgxwxl 咱们还是写个简单的程序看看怎么重现这个问题,然后看看怎么修复,或给出建议的做法。

skychips commented 7 years ago

qq 20171022192653

一个临时的解决方案 这样一个过程完善了。 怎么没有表情。github好单调呀。

skychips commented 7 years ago

又有新的bug了。 就在刚才调试代码时,挂机测试重连功能,不久后我回头看一眼网络连接。居然卡在了 FIN_WAIT(使用 Process Hacker 工具查看的网络状态) 之后程序的CPU单核吃满占用率12%峰值。 究竟是卡在哪个循环里未知。

我当时调试没有开着vs调试器,编译后运行的。不然我就去找代码了。

我现在开着vs调试。