C++低吞吐量 winsock TCP 测试应用程序

C++ low throughput winsock TCP test application

本文关键字:测试 应用程序 TCP winsock 吞吐量 C++      更新时间:2023-10-16

我正在尝试构建一个在localhost中工作的快速服务器和客户端。这个想法是从另一个程序发送数据 blob,并且要快速处理。一次只有一个客户端连接到服务器。

我首先尝试使用 boost::asio 库实现这一点。 一切正常,除了吞吐量非常慢,415兆字节/秒。

然后我继续用winsock创建一个测试用例,它也具有非常相似的吞吐量,434兆字节/秒。

我期望在 40GB 或每秒至少几 GB 的范围内更多。

我同意任何建议,因为我已经脱离了我的元素,与网络编程。

我目前的客户端功能:

bool sendDataWin(size_t size, const size_t blocksize, size_t port)
{
int result;
struct addrinfo *addressinfo = nullptr, hints;
auto sport = std::to_string(port);
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the local address and port to be used by the server
result = getaddrinfo("localhost", sport.c_str(), &hints, &addressinfo);
if (result != 0) {
printf("Error at client socket(): %ldn", WSAGetLastError());
return false;
}
SOCKET connection = socket(
addressinfo->ai_family,
addressinfo->ai_socktype,
addressinfo->ai_protocol);
if (connection == INVALID_SOCKET) {
printf("Error at client socket(): %ldn", WSAGetLastError());
freeaddrinfo(addressinfo);
return false;
}
// Try to put loopback fast path on.
bool value;
DWORD dwBytesRet;
int status =
WSAIoctl(
connection,
SIO_LOOPBACK_FAST_PATH,
&value,
sizeof(bool),
NULL,
0,
&dwBytesRet,
0,
0);
if (status == SOCKET_ERROR) {
DWORD LastError = ::GetLastError();
if (LastError == WSAEOPNOTSUPP) {
printf("client call is not supported :: SIO_LOOPBACK_FAST_PATHn");
}
}
// Connect to server.
result = connect(connection, addressinfo->ai_addr, (int)addressinfo->ai_addrlen);
if (result == SOCKET_ERROR) {
printf("Error at client socket(): %ldn", WSAGetLastError());
closesocket(connection);
return false;
}
freeaddrinfo(addressinfo);
std::vector<uint8_t> buffer;
buffer.resize(blocksize);
size_t total = 0;
do {
size_t sendSize = blocksize;
size_t next = total + sendSize;
if (next > size)
{
sendSize -= next - size;
}
// Echo the buffer back to the sender
result = send(connection, (const char*)buffer.data(), (int)sendSize, 0);
if (result == SOCKET_ERROR) {
printf("client send failed: %dn", WSAGetLastError());
closesocket(connection);
return false;
}
total += sendSize;
} while (total < size);
result = shutdown(connection, SD_SEND);
if (result == SOCKET_ERROR) {
printf("client shutdown failed: %dn", WSAGetLastError());
closesocket(connection);
return false;
}
// cleanup
closesocket(connection);
return true;
}

服务器功能:

bool serverReceiveDataWin(size_t size, const size_t blocksize, size_t port)
{
int result;
struct addrinfo *addressinfo = nullptr, *ptr = nullptr, hints;
auto sport = std::to_string(port);
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// Resolve the local address and port to be used by the server
result = getaddrinfo(nullptr, sport.c_str(), &hints, &addressinfo);
if (result != 0) {
printf("Error at server socket(): %ldn", WSAGetLastError());
return false;
}
SOCKET ListenSocket = socket(addressinfo->ai_family, addressinfo->ai_socktype, addressinfo->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
printf("Error at server socket(): %ldn", WSAGetLastError());
freeaddrinfo(addressinfo);
return false;
}
// Try to put loopback fast path on.
bool value;
DWORD dwBytesRet;
int status =
WSAIoctl(
ListenSocket,
SIO_LOOPBACK_FAST_PATH,
&value,
sizeof(bool),
NULL,
0,
&dwBytesRet,
0,
0);
if (status == SOCKET_ERROR) {
DWORD LastError = ::GetLastError();
if (LastError == WSAEOPNOTSUPP) {
printf("server call is not supported :: SIO_LOOPBACK_FAST_PATHn");
}
}
// Setup the TCP listening socket
result = bind(ListenSocket, addressinfo->ai_addr, (int)addressinfo->ai_addrlen);
if (result == SOCKET_ERROR) {
printf("server bind failed with error: %dn", WSAGetLastError());
freeaddrinfo(addressinfo);
closesocket(ListenSocket);
return false;
}
if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
printf("Listen failed with error: %ldn", WSAGetLastError());
closesocket(ListenSocket);
return false;
}
// Accept a client socket
SOCKET ClientSocket = accept(ListenSocket, nullptr, nullptr);
if (ClientSocket == INVALID_SOCKET) {
printf("server accept failed: %dn", WSAGetLastError());
closesocket(ListenSocket);
return false;
}
std::vector<uint8_t> buffer;
buffer.resize(blocksize);
size_t total = 0;
// Receive until the peer shuts down the connection
do {
size_t currentSize = blocksize;
size_t next = total + currentSize;
if (next > size)
{
currentSize -= next - size;
}
result = recv(ClientSocket, (char*)buffer.data(), (int)currentSize, 0);
if (result > 0)
{
total += result;
}
else if (result == 0)
{
printf("server Connection closing...n");
return false;
}
else {
printf("server recv failed: %dn", WSAGetLastError());
closesocket(ClientSocket);
return false;
}
} while (total < size);
result = shutdown(ClientSocket, SD_SEND);
if (result == SOCKET_ERROR) {
printf("server shutdown failed: %dn", WSAGetLastError());
closesocket(ClientSocket);
return false;
}
// cleanup
closesocket(ClientSocket);
return true;
}

而测试程序本身是:

int main()
{
int width = 1920;
int height = 1080;
const size_t totalBpp = 3;
const size_t totalSize = width * height * totalBpp;
size_t port = 27140;
size_t times = 1000;
size_t expectedData = totalSize * times;
// Initialize Winsock
int iResult;
WSADATA wsaData;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
std::cout << "WSAStartup failed: " << iResult << std::endl;
return EXIT_FAILURE;
}
std::atomic_bool serverOk{ false };
std::atomic_bool clientOk{ false };
auto start = std::chrono::high_resolution_clock::now();
std::thread server = std::thread([&] {
serverOk = serverReceiveDataWin(expectedData, totalSize, port);
});
std::thread client = std::thread([&] {
clientOk = sendDataWin(expectedData, totalSize, port);
});
client.join();
server.join();
auto end = std::chrono::high_resolution_clock::now();
WSACleanup();
if (!(clientOk && serverOk))
{
if (!serverOk) std::cout << "Server was not OK." << std::endl;
if (!clientOk) std::cout << "Client was not OK." << std::endl;
return EXIT_FAILURE;
}
std::chrono::duration<double> diff = end - start;
double frameTime = diff.count() / times;
double fps = 1.0 / frameTime;
std::cout << "Sent: " << width << "x" << height << "_" << totalBpp << "(" << totalSize << "). times: " << times << std::endl;
std::cout << "frameTime: " << frameTime << "s." << std::endl;
std::cout << "fps: " << fps << "." << std::endl;
std::cout << "transfer rate : " << ((expectedData / diff.count()) / 1048576) << " mebibytes/s." << std::endl;
std::cout << "transfer rate : " << ((expectedData / diff.count()) / 1000000) << " megabytes/s." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds{ 60 });
return EXIT_SUCCESS;
}

在我的机器上,我得到以下结果:

Sent: 1920x1080_3(6220800).times : 1000
frameTime : 0.0138217s.
fps : 72.35.
transfer rate : 429.225 mebibytes / s.
transfer rate : 450.075 megabytes / s.

似乎问题有点出在我的防火墙/防病毒软件上,我首先卸载了F-secure,速度增加到500兆字节/秒,卸载和重新启动后,速度增加到4000兆字节/秒。

几点。

  1. 未正确调用 IOCTL。

    bool value;
    DWORD dwBytesRet;
    int status =
    WSAIoctl(
    ListenSocket,
    SIO_LOOPBACK_FAST_PATH,
    &value,
    sizeof(bool),
    NULL,
    0,
    &dwBytesRet,
    0,
    0);
    

这应该传入设置为 1 的 DWORD(32 位整数(。上面的代码传递了一个从未初始化为任何内容的C++ bool(可能只有 1 个字节(。

  1. 由于这只是一个插槽,这将受到CPU的限制,因为它实际上只是从用户/内核和单个线程上的内存复制。这不太可能通过单个连接达到 40 Gbps。特别是因为这只发送了6GB的数据。

在发送大量此类数据时,您应该关闭 Nagle 算法。在发送套接字上设置TCP_NODELAY