我已经编写了一个小型套接字程序,可以快速连续地将500个客户端连接到localhost
上的所有服务器。我正在使用Xcode 8.0运行macOS 10.12,并且我注意到该程序在大约50%的时间内都失败了。输出显示正确数量的客户端连接尝试调用,但之后永远不会进行连接。就像我说的,有时它工作得很好。
我的预期输出是输出应包含CLIENT TRYCONNECT
,CLIENT CONNECT
和SERVER 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同时启动太多连接?
答案 0 :(得分:0)
您的客户有内存泄漏
addrinfo
的呼叫是从getaddrinfo
动态分配的。{。}
您需要在使用后致电freeaddrinfo
。
尝试重新使用端口号时,无法清除addrinfo
可能会导致问题。