简单的套接字程序偶尔会失败

时间:2016-10-25 02:56:56

标签: c++ c sockets

我已经编写了一个小型套接字程序,可以快速连续地将500个客户端连接到localhost上的所有服务器。我正在使用Xcode 8.0运行macOS 10.12,并且我注意到该程序在大约50%的时间内都失败了。输出显示正确数量的客户端连接尝试调用,但之后永远不会进行连接。就像我说的,有时它工作得很好。

我的预期输出是输出应包含CLIENT TRYCONNECTCLIENT CONNECTSERVER ACCEPT各500行。我总是得到正确数量的TRYCONNECT消息,但通常其他两个消息远远低于预期的500消息 - 程序只是锁定等待来自永不到达的客户端的连接。

知道会发生什么事吗?

这是来源(尽可能简单):

#include <arpa/inet.h>
#include <iostream>
#include <mutex>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/fcntl.h>
#include <sys/poll.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <thread>

const int PORT    = 1111;
const int CLIENTS = 500;

using namespace std;

mutex mtx;

#define thr throw runtime_error("error");

void server()
{
    int err;
    addrinfo hints    = {}, *res;
    hints.ai_family   = AF_INET6;
    hints.ai_flags    = AI_PASSIVE;
    hints.ai_socktype = SOCK_STREAM;

    err = getaddrinfo(nullptr, to_string(PORT).c_str(), &hints, &res);
    if (err != 0) thr;

    int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (fd == -1) thr;

    err = fcntl(fd, F_SETFL, O_NONBLOCK);
    if (err == -1) thr;

    socklen_t val;
    val = 0;
    err = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof val);
    val = 1;
    err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof val);
    val = 1;
    err = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof val);

    err = ::bind(fd, res->ai_addr, res->ai_addrlen);
    if (err == -1) thr;

    err = listen(fd, 0);
    if (err == -1) thr;

    freeaddrinfo(res);

    int connected = 0;

    for (;;) {
        sockaddr_storage client;
        socklen_t sz = sizeof client;
        int cfd = accept(fd, (sockaddr*)&client, &sz);
        if (cfd < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                continue;
            } else {
                thr;
            }
        } else {
            lock_guard<mutex> lk(mtx);
            cout << "SERVER ACCEPT ON " << cfd << endl;
            if (++connected == CLIENTS) return;
        }
    }
}

void client()
{
    addrinfo hints    = {}, *res;
    hints.ai_family   = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    int err;

    err = getaddrinfo("localhost", to_string(PORT).c_str(), &hints, &res);
    if (err == -1) thr;

    int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (fd == -1) thr;

    err = fcntl(fd, F_SETFL, O_NONBLOCK);
    if (err == -1) thr;

    socklen_t val;
    val = 1;
    err = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof val);

    err = connect(fd, res->ai_addr, res->ai_addrlen);
    if (err == -1 && errno != EINPROGRESS) thr;

    freeaddrinfo(res);

    {
        lock_guard<mutex> lk(mtx);
        cout << "CLIENT TRYCONNECT ON " << fd << endl;
    }

    for (;;) {
        this_thread::sleep_for(chrono::milliseconds(100));

        pollfd ufds = { 0 };
        ufds.fd = fd;
        ufds.events = POLLOUT;
        poll(&ufds, 1, 0);

        if (!(ufds.revents & POLLOUT)) {
            continue;
        }

        int val;
        socklen_t val_sz = sizeof val;
        getsockopt(fd, SOL_SOCKET, SO_ERROR, &val, &val_sz);

        if (val < 0) {
            if (errno == EINPROGRESS) {
                continue;
            }
        }

        lock_guard<mutex> lk(mtx);
        cout << "CLIENT CONNECT ON " << fd << endl;
        return;
    }
}

int main(int argc, char* argv[])
{
    thread t1(server);

    thread clients[CLIENTS];
    for (int i = 0; i < CLIENTS; ++i)
        clients[i] = thread(client);

    t1.join();
    for (int i = 0; i < CLIENTS; ++i)
        clients[i].join();
}

编辑:正确释放客户端中的地址信息

更新:通过在流程上运行lsof,我发现有几个&#34;缺少&#34;连接被标记为CLOSED而不是ESTABLISHED,因此它们会以某种方式被关闭。我还不确定会触发什么,但我正在研究它。

更新:通过在启动客户端连接之间添加一个短(1毫秒)的睡眠,程序运行完美。看起来可能是套接字操作系统实现中的一个错误,它阻止macOS同时启动太多连接?

1 个答案:

答案 0 :(得分:0)

您的客户有内存泄漏 addrinfo的呼叫是从getaddrinfo动态分配的。{。} 您需要在使用后致电freeaddrinfo

尝试重新使用端口号时,无法清除addrinfo可能会导致问题。