Javier Luis Lopez Ответов: 2

Передача UDP с использованием select () в windows занимает больше времени, чем в linux


Я сделал 2000 передач от клиента к серверу с паузами между ними. 2000 пауз занимают 6 секунд.

Я тестировал свой код в windows и занимал 17 секунд в 2000 передачах
В linux это занимает 6,2-6,4 секунды (зависит от того, блокируется или не блокируется)

Возникает вопрос: почему в windows это занимает гораздо больше времени, чем в linux?

Это функции приема и отправки:

void c_client::send_block(char *data_block, int size)
{
	int stat;
	stat = sendto(client_socket, data_block, size, 0, (struct sockaddr *) &si_other, sizeof(si_other)); //sends to server!!
	if (stat == -1)
	{
		char line[256] = "### c_client::send_block failed:"; strcat(line, strerror(errno)); 
		cout_mtx.lock(); cout << line << endl; cout_mtx.unlock();
	}
}
int c_client::receive_block(char *data_block)
{
	si_length = sizeof(si_other);
	int stat1 = -1;

	fd_set tmp = select_fd_set0;
	int ret = select((int)client_socket + 1, &tmp, NULL, NULL, &select_timeval);
	switch (ret)
	{
	case 0: return 0;
	case -1: cout_mtx.lock(); cout << "WARNING AT c_client::receive_block() making select()" << endl; cout_mtx.unlock(); return -1;
	default: 
		stat1 = (int) recvfrom(client_socket, data_block, UDP1_BUFFER_LONG, 0, (struct sockaddr *) &si_other, &si_length);
	}
	return stat1;//if -1 no data in!
}



Внимание-1:
Некоторые люди возвращают recvfrom() как size_t, что совершенно неправильно, потому что если результаты терпят неудачу (-1), то его нельзя увидеть до тех пор, пока size_t не будет подписан

Внимание-2:
Чтобы правильно заставить работать эту программу (и любую другую, использующую сокеты), необходимо выполнить это, чтобы позволить увеличить память сокета:

sudo sysctl -w net.core.rmem_max=2304000


Что я уже пробовал:

Вот полный заголовок кода заголовка (работает в linux и windows): Protocol.hpp

#ifndef  __PROTOCOLO_HPP
#define  __PROTOCOLO_HPP
/*
Version V000: Bien 2000 paquetes ok. Tarda lo mismo con TIMEOUT_US=1-500us si bien tarda 20-28s en mandar los 2000 paquetes debiendo ser algo mas de 6 segundos
V001: No select():
	  non-blocking     los 2000 paquetes son OK pero va lento: 84 segundos en vez de 28 con select().
	  En modo blocking los 2000 paquetes son OK (en init_server y init _client unsigned long mode=0) pero tarda 106 segundos
V002: Con select y non-blocking: va bien pero tarda un 15% mas que en modo blocking
V003: Se puede seleccionar entre blocking/nonblocking en linux y windows
      Modo blocking: tarda 16 segundos en mandar los 2000 paquetes (en modos release y debug x64)
	  Modo nonblocking: Tarda 18 segundos pero pierde el servidor 8 de 2000 paquetes
	  Funciona en linux, tarda 6.37 segundos en modo blocking y en modo nonblocking 6.43 pero se encalla un poco para salir.

CAUTION!!: Increase receiver socket in Linux that is 8k long only do  :

    sudo sysctl -w net.core.rmem_max=2304000

	Compilation linux:

	gcc  -std=c++11 -lstdc++ -pthread -o protocol_exe Protocol_V003.cpp Protocol_V003.hpp
*/


#include <iostream> //cout
#include <stdio.h>  //getchar()
#include <stdlib.h> //srand
#include <string.h> //memcpy
#include <thread>   //threads, usleep
#include <chrono>	//usleep
#include <mutex>          // std::mutex

#ifdef __linux__ 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>    //usleep()
#include <sys/ioctl.h>
#define _BIND bind
#define TCPID_SERVER "10.130.20.187"  //linux. TCP_ID of the client. Used at the c_serverclass at send() and recvfrom()    MODIFY THIS!!!!!!!!!!!!!!!!!!!!!
#define _CLOSE close


#else
#include <winsock2.h>
#define snprintf _snprintf
#pragma comment(lib, "Ws2_32.lib")
#pragma warning(disable:4996)     //to allow using linux standard compatible functions
#define _BIND ::bind
//internal:"127.0.0.1" net A:  10.121.18.193  net B:10:130:20:187
#define TCPID_SERVER "10.121.18.193"   //TCP_ID of the server. Used at the c_client class   MODIFY THIS!!!!!!!!!!!!!!!!!!!!!
#define _CLOSE closesocket
#endif

using namespace std;
typedef unsigned char uchar;
typedef unsigned short ushort;

//=========================== The UDP zone =================================================================
#define BLOCK_HEADER 0xBEADFEAC   //ADA1DEAD
#define UDP1_BUFFER_LONG 62000  //max UDP=65507. (1280*720+10)/15=61440.66 usamos 61441 y asi se aprovecha mejor
#define PORT_NUM   2450			//PORT for listening data, used by both
#define TIMEOUT_US 100			//TIMEOUT to be used in select()
#define CMD_SENT 2000			//Max number of c_client commands to be sent for testing purposes
//#define NONBLOCKING				//undef for making blocking. It is 15% slower in nonblocking!!!!!!!!!!!

void sleep_us(long microseconds){ this_thread::sleep_for(std::chrono::microseconds(microseconds)); }

uchar message1_long[UDP1_BUFFER_LONG], message2_long[UDP1_BUFFER_LONG];
uchar message1_short[100], message2_short[100];
void fill_messages();
std::mutex cout_mtx;

class c_server
{
public:
	c_server(); //no tcp_id needed at the server side!
	~c_server();
	void server_run_state(); //main function. pixels4=number of pixels (that are divided by 4 before being sent)
	void set_end_state(bool state){ flag_end_server = state; }//if false makes server_run_state() to exit!!
private:
	//socket variables:
#ifdef __linux__ 
	socklen_t si_length;
	int server_socket;//server side
	int clientToServer_socket; //client side
#else
	int si_length;
	SOCKET server_socket; //server side
	//SOCKET clientToServer_socket; //client side
#endif
	struct sockaddr_in  anyclient;
	//the select side!
	fd_set select_fd_set0;//select_fd_set0 is initiated once again server socket, then select_fd_set=select_fd_set0 every time select() is called
	struct timeval select_timeval;

	//private functions:
	void init_server();
	void send_block(char *data_block, int size);
	int receive_block(char *data_block);

	uchar state123;
	static bool flag_end_server;
};


class c_client
{
public:
	c_client(const char *tcp_id){ init_client(tcp_id); }
	~c_client() { }
	void client_run_state();
private:
	void init_client(const char *tcp_id);
	void send_block(char *data_block, int size);
	int receive_block(char *data_block);
	
	
	//private data to be used in sockets:
	struct sockaddr_in si_other;//this is the sockaddr_in needed at the client side to send to server
	//struct sockaddr_in si_client;//this is the sockaddr_in used to receivefrom server
#ifdef __linux__ 
	socklen_t si_length;
	int client_socket; //client side
#else
	int si_length;
	SOCKET client_socket; //Connected to server tcpid & port. Used at recvfrom() and sendto()
#endif
	//the select side!
	fd_set select_fd_set0;//to be initiated!
	struct timeval select_timeval;
};
#endif



И вот это самое Protocol.cpp файл:
#include "Protocol_V003.hpp"

void fill_messages()
{ 
	for (int i = 0; i < UDP1_BUFFER_LONG; i++)
	{ 
		message1_long[i] = rand() % 256; message2_long[i] = rand() % 256; 
		if (i < 100) 
		{ 
			message1_short[i] = rand() % 256; message2_short[i] = rand() % 256; 
		} 
	} 
	message1_long[0] =  1;//message1 long
	message2_long[0] =  2;//message2 long
	message1_short[0] = 3;//message1 short
	message2_short[0] = 4;//message2 short

	message1_long[1] = 1;//RUN
	message2_long[1] = 1;//RUN
	message1_short[1] = 1;//RUN
	message2_short[1] = 1;//RUN
}


//================================== SERVER =================================
c_server::c_server()
{
	init_server();
}

void c_server::init_server()
{
	flag_end_server = false;
	state123 = 1;//RUN
	printf("initializing server\n");
	sockaddr_in server; //used in this function only to initiate socket
	memset((uchar*)&server, 0, sizeof(server));
#ifdef __linux__ 
	/*Fill in server's sockaddr_in*/
#define SOCKADDR struct sockaddr   
#else
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2, 0);
	if (WSAStartup(wVersionRequested, &wsaData) != 0)
	{
		cout << "c_server::init_server()  WSAStartup() error" << endl; getchar();
		return;
	}
#endif

	/*Fill in server's sockaddr_in*/
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;//inet_addr(tcp_id);
	server.sin_port = htons(PORT_NUM);

	memset((char *)&anyclient, 0, sizeof(anyclient));
	anyclient.sin_family = AF_INET;
	anyclient.sin_addr.s_addr = INADDR_ANY;
	anyclient.sin_port = htons(0000);//any client port!
	int slen = sizeof(anyclient);

	/*Create socket */
	server_socket = socket(AF_INET, SOCK_DGRAM, 0);
	if (server_socket == -1)
	{
		cout << "Error in c_server::init_server(): socket failed"; getchar();
		return;
	}
	//bind(server_socket, (SOCKADDR*)&server_address, sizeof(SOCKADDR));
	if (_BIND(server_socket, (SOCKADDR *)&server, sizeof(server)) == -1)
	{
		cout<<"Fail bind()"; getchar();
		_CLOSE(server_socket); return;
	}
	int buffsize = 24 * UDP1_BUFFER_LONG;
	//Increasing the socket buffer:
	if (setsockopt(server_socket, SOL_SOCKET, SO_RCVBUF, (char*)&buffsize, sizeof(int)) == -1)
	{
		cout << "Warning at c_server::init_server() could not increase receiver socket size" << endl; getchar();
	}
	else
		cout << "Increased server socket bufer size" << endl;

	//the select side!
	//fd_set select_fd_set;//to be initiated!
	//struct timeval select_timeval;
	select_timeval.tv_sec = 0;
	select_timeval.tv_usec = TIMEOUT_US;
	FD_ZERO(&select_fd_set0);
	FD_SET(server_socket, &select_fd_set0);

	//BLOCKING/NON BLOCKING:
#ifdef NONBLOCKING
#ifdef __linux__
	int opt = 1; ioctl(server_socket, FIONBIO, &opt);
#else
	unsigned long opt; ioctlsocket(server_socket, FIONBIO, &opt);
#endif
#endif

}

void c_server::send_block(char *data_block, int size)
{
	int stat;
	stat = sendto(server_socket, data_block, size, 0, (struct sockaddr *) &anyclient, sizeof(anyclient)); //sends to any client!!
	if (stat == -1)
	{
		char line[256] = "### c_server::send_block failed:"; strcat(line, strerror(errno));
		cout_mtx.lock(); cout << line << endl; cout_mtx.unlock();
	}
}
int c_server::receive_block(char *data_block)
{
	si_length = sizeof(anyclient);
	int stat1 = -1;

	fd_set tmp = select_fd_set0;
	int ret = select((int)server_socket + 1, &tmp, NULL, NULL, &select_timeval);    
	switch (ret)
	{
	case 0: return 0;
	case -1: cout_mtx.lock(); cout << "WARNING AT c_server::receive_block() making select()" << endl; cout_mtx.unlock(); return -1;
	default: 
		stat1 = (int) recvfrom(server_socket, data_block, UDP1_BUFFER_LONG, 0, (struct sockaddr *) &anyclient, &si_length);
	}
	return stat1;//if -1 no data in!
}

bool c_server::flag_end_server;

void c_server::server_run_state()
{
	uchar message[UDP1_BUFFER_LONG];
	ushort *num_cmd = (ushort *)&message[2];
	uchar *cmd = &message[1];
	int num_cmd_sent = 0, num_cmd_in = 0, num_cmd_err = 0;

	while (!flag_end_server)
	{
		int i1 = receive_block((char *)message);
		if (i1 > 0)
		{
			num_cmd_in++;
			cout_mtx.lock();
			cout << "server in cmd:" << (int) message[0] << " cmd_num=" << *num_cmd << endl;
			cout_mtx.unlock();

			//send any cmd:
			if (*num_cmd == 10) state123 = 2;
			else if (*num_cmd == 20) state123 = 3;
			else if (*num_cmd == 30) state123 = 1;

			ushort num_cmd1 = *num_cmd;
			if (num_cmd1 % 2 == 0)
				memcpy(message, message1_short, 100);
			else
				memcpy(message, message2_short, 100);
			*num_cmd = num_cmd1;
			*cmd = state123;
			send_block((char *)message, 100); 
			num_cmd_sent++;
			cout_mtx.lock();
			cout << "server out cmd:" << (int) message[0] << " cmd_num=" << *num_cmd << endl;
			if ((i1 != 100) && (i1 != UDP1_BUFFER_LONG))
			{
				cout << "====== ERROR sercer received command length=" << i1 << endl;
				num_cmd_err++;
			}
			cout_mtx.unlock();
		}
	}
	cout_mtx.lock();
	cout << "===== END c_server::server_run_state() =====" << endl;
	cout << "Server sent commands=" << num_cmd_sent << " received=" << num_cmd_in << " wrong commands=" << num_cmd_err << endl;
	cout_mtx.unlock();
}

//====================================== c_client =========================================================
void c_client::init_client(const char *tcp_id)
{
	fill_messages();//initializes messages to be sent for testing
	//initiating socket:
#ifdef __linux__
	//client_socket = socket(AF_INET, SOCK_DGRAM, 0);
	if ((client_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
	{
		printf("client socket() failed"); getchar();
		exit(EXIT_FAILURE);
	}
#else
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2, 0);
	if (WSAStartup(wVersionRequested, &wsaData) != 0)
	{
		cout << "c_udp::init_client() WSAStartup() error" << endl; getchar();
		return;
	}

	//create socket
	if ((client_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
	{
		printf("client socket() failed with error code : %d", WSAGetLastError());
		exit(EXIT_FAILURE);
	}
#endif
	/*Create client socket*/
	if (client_socket == -1)
	{
		cout << "Warning: client socket not created trying again:" << endl; int i1 = 1;
		if (setsockopt(client_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&i1, sizeof(int)) < 0)
			perror("setsockopt(SO_REUSEADDR) failed");
	}

	/*Fill in client's sockaddr_in */
	memset(&si_other, 0, sizeof(si_other));
	si_other.sin_family = AF_INET;
	si_other.sin_port = htons(PORT_NUM);
	si_other.sin_addr.s_addr = inet_addr(tcp_id); // |= htonl(0x1ff);

	select_timeval.tv_sec = 0;
	select_timeval.tv_usec = TIMEOUT_US;
	FD_ZERO(&select_fd_set0);
	FD_SET(client_socket, &select_fd_set0);
	cout << "Connected c_client with server network ip: " << tcp_id << " Port=" << PORT_NUM << endl;

#ifdef NONBLOCKING
#ifdef __linux__
	int opt = 1;ioctl(client_socket, FIONBIO, &opt);
#else
	unsigned long opt; ioctlsocket(client_socket, FIONBIO, &opt);
#endif
#endif
}




void c_client::send_block(char *data_block, int size)
{
	int stat;
	stat = sendto(client_socket, data_block, size, 0, (struct sockaddr *) &si_other, sizeof(si_other)); //sends to server!!
	if (stat == -1)
	{
		char line[256] = "### c_client::send_block failed:"; strcat(line, strerror(errno)); 
		cout_mtx.lock(); cout << line << endl; cout_mtx.unlock();
	}
}
int c_client::receive_block(char *data_block)
{
	si_length = sizeof(si_other);
	int stat1 = -1;

	fd_set tmp = select_fd_set0;
	int ret = select((int)client_socket + 1, &tmp, NULL, NULL, &select_timeval);
	switch (ret)
	{
	case 0: return 0;
	case -1: cout_mtx.lock(); cout << "WARNING AT c_client::receive_block() making select()" << endl; cout_mtx.unlock(); return -1;
	default: 
		stat1 = (int) recvfrom(client_socket, data_block, UDP1_BUFFER_LONG, 0, (struct sockaddr *) &si_other, &si_length);
	}
	return stat1;//if -1 no data in!
}


void c_client::client_run_state()
{
	uchar message[UDP1_BUFFER_LONG];
	ushort *num_cmd = (ushort *)&message[2];
	int num_cmd_sent = 0, num_cmd_in = 0, num_cmd_err = 0;

	for (ushort i = 0; i < CMD_SENT; i++)
	{
		if (i % 8 == 0)
		{
			if ((i / 8) % 2 == 1)
				memcpy(message, message1_long, UDP1_BUFFER_LONG);
			else
				memcpy(message, message2_long, UDP1_BUFFER_LONG);
			*num_cmd = i;
			send_block((char *)message, UDP1_BUFFER_LONG);
			num_cmd_sent++;
			sleep_us(10000);
		}
		else
		{
			if (i % 2 == 1)
				memcpy(message, message1_short, 100);
			else
				memcpy(message, message2_short, 100);
			*num_cmd = i;
			send_block((char *)message, 100);
			num_cmd_sent++;
			sleep_us(2000);
		}
		cout_mtx.lock();
		cout << "client out cmd:" << (int) message[0] << " cmd_num=" << *num_cmd << endl;
		cout_mtx.unlock();

		int i1 = receive_block((char *)message);
		if (i1 > 0)
		{
			num_cmd_in++;
			cout_mtx.lock();
			cout << "client in cmd:" << (int)message[0] << " cmd_num=" << *num_cmd << endl;
			if ((i1 != 100) && (i1 != UDP1_BUFFER_LONG))
			{
				cout << "====== ERROR c_client received command length=" << i1 << endl;
				num_cmd_err++;
			}
			cout_mtx.unlock();
		}
	}
	cout_mtx.lock();
	cout << "===== END c_client::server_run_state() =====" << endl;
	cout << "Client sent commands=" << num_cmd_sent << " received=" << num_cmd_in << " wrong commands=" << num_cmd_err << endl;
	cout_mtx.unlock();
}


int main()
{
	c_client *client = new c_client(TCPID_SERVER);
	c_server *server = new c_server();
	//sending the server thread!!!
	cout << "Running threads" << endl;
	time_t ini, fin;

	ini = clock();
	thread th_server(&c_server::server_run_state, server);
	sleep_us(10000);
	thread th_client(&c_client::client_run_state, client);
	th_client.join();
	sleep_us(2000);//2ms
	server->set_end_state(true);
	th_server.join();
	fin = clock();
	cout << "\n============= END =============" << endl;
	cout << "Total time=" << 1.0*(fin - ini) / CLOCKS_PER_SEC << " seconds" << endl;
	cout << "Expected>=" << CMD_SENT / 8 * 10e-3 + (CMD_SENT - CMD_SENT / 8)*2e-3 << " seconds" << endl;
	getchar();
	return 1;
}

2 Ответов

Рейтинг:
6

Javier Luis Lopez

Я попытался удалить us_sleep и продержался 12-13 секунд (неблокирующий / блокирующий) и ожидал < 0,5 секунды.

Я прокомментировал мьютекс и cout, и это продолжалось 0,24 секунды. Проблема в том, что cout занимает 2-3 миллисекунды в windows, но намного меньше в linux.

Решение состоит в том, чтобы увеличить стандартный размер буфера cout, включив этот код в начало main() любой высокоскоростной программы, использующей cout:

#ifndef __linux__
	char buf[4000];setvbuf(stdout, buf, _IOFBF, sizeof buf);
#endif


С этим улучшением он работает аналогично в windows и linux в неблокирующих передачах, но, к сожалению, при создании блокирующих он работает медленнее в windows, чем в linux (7-8 секунд вместо 6 секунд), что примерно в 4-8 раз дольше в части передачи


Randor

Да,

Вот что сказал Стефан т. Лававей о низкой производительности cout в MS Visual C++:

https://connect.microsoft.com/VisualStudio/feedback/details/642876/std-wcout-is-ten-times-slower-than-wprintf-performance-bug-in-c-library

Похоже, что проблема все еще существует здесь в 2017 году :)

с наилучшими пожеланиями,
- Дэвид Делон

Рейтинг:
1

Jochen Arndt

Я не знаю как this_thread::sleep_for() реализуется вашим компилятором Windows. Но я предполагаю, что он использует системные часы, а не таймер высокого разрешения, как счетчик производительности.

Разница во времени составляет около 10 секунд. Для 2000 пакетов это соответствует 5 мс на пакет.

Таким образом, вероятный источник вашей проблемы заключается в том, что разрешение ваших системных часов Windows составляет 5 мс (вы звоните us_sleep(2000) 1750 раз и us_sleep(10000) 250 раз), так что 2000 американских звонков будут эффективно спать в течение 5000 американских.

Наилучшее доступное разрешение с системными часами Windows составляет 0,5 мс. Но это не выбрано по умолчанию. Значение по умолчанию-15,6 МС. Некоторые приложения настраивают разрешение в соответствии со своими потребностями (Firefox, например, AFAIK). Это затем объяснило бы значение 5 мс.