C++:字符数组在发送过程中删除了一些值
C++: char array dropping some values during send
我在我的 C++ 项目中遇到了一个非常奇怪的问题。我有一个客户端和一个服务器,它们相互发送字符数组数据。虽然初始 sending/reception 运行良好(请参阅:MVE 中的 Client.firstConnection()
),但在以下消息中,客户端在将其发送到服务器时似乎丢弃了数组中除第 12 到第 15 个字符之外的所有字符。然后服务器只接收这个部分填充的数组。
示例:
- 我想将缓冲区
4_user53:6134;*0/
从客户端发送到服务器。
- 在发送之前,我的变量在逐字符打印时显示
4_user53:6134;*0/
- 在服务器端,接收到缓冲区,但是当逐个字符打印时,显示
4;*0
- 在客户端,发送缓冲区后,变量在逐字符打印时立即显示
4;*0
。
我不知道为什么这些字符会消失。感谢所有建议,谢谢。
请在下面找到最小的可行示例,我已尽力注释兴趣点并说明打印功能的结果。
MVE:
使用 VS 2017、C++ 11 编译。
//Header file information
// all imports:
#include <cstdlib>
#include <iostream>
#include <map>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <random>
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
// Global vars
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "6000"
#define MAX_CONS 1 //Total No of Clients allowed
//Client
class Client{
bool Client::firstConnection()
{
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
struct addrinfo *result = NULL,
*ptr = NULL,
hints;
int iResult;
// Initialize Winsock
iResult = WSAStartup( MAKEWORD( 2, 2 ), &wsaData );
if( iResult != 0 )
{
printf( "WSAStartup failed with error: %d\n", iResult );
return 1;
}
ZeroMemory( &hints, sizeof( hints ) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
iResult = getaddrinfo( "127.0.0.1", DEFAULT_PORT, &hints, &result );
if( iResult != 0 )
{
printf( "getaddrinfo failed with error: %d\n", iResult );
WSACleanup();
return 1;
}
// Attempt to connect to an address until one succeeds
for( ptr = result; ptr != NULL; ptr = ptr->ai_next )
{
// Create a SOCKET for connecting to server
this->ConnectSocket = socket( ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol );
if( this->ConnectSocket == INVALID_SOCKET )
{
printf( "socket failed with error: %ld\n", WSAGetLastError() );
WSACleanup();
return 1;
}
// Connect to server.
iResult = connect( this->ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen );
if( iResult == SOCKET_ERROR )
{
closesocket( this->ConnectSocket );
this->ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo( result );
if( this->ConnectSocket == INVALID_SOCKET )
{
printf( "Unable to connect to server!\n" );
WSACleanup();
return 1;
}
std::cout << "Connection to server successful!\n";
// Send an initial buffer
int iResult = send( this->ConnectSocket, ( this->username ).c_str(), (int)strlen( ( this->username ).c_str() ), 0 ); //username is a perfectly fine string
// -> This instance of send does NOT drop any data! <- //
if( iResult == SOCKET_ERROR )
{
printf( "send failed with error: %d\n", WSAGetLastError() );
closesocket( this->ConnectSocket );
WSACleanup();
return false;
}
return true;
}
bool Client::sendCommand( std::string command )
{
// Send a buffer
char* tmp = this->gameinfo.SerializeToArray( DEFAULT_BUFLEN ); // see below
const char* send_buf = tmp; // have tried with and without const, results are the same
for( int i = 0; i < (int)strlen( send_buf ); ++i )
{
printf( "Buffer[%d] = [%c]\n", i, send_buf[i] );
}
// Output (literally copy/pasted):
/*
Buffer[0] = [4]
Buffer[1] = [_]
Buffer[2] = [u]
Buffer[3] = [s]
Buffer[4] = [e]
Buffer[5] = [r]
Buffer[6] = [5]
Buffer[7] = [3]
Buffer[8] = [:]
Buffer[9] = [6]
Buffer[10] = [1]
Buffer[11] = [3]
Buffer[12] = [4]
Buffer[13] = [;]
Buffer[14] = [*]
Buffer[15] = [0]
Buffer[16] = [/]
*/
// This is correct and the desired output!!! ^
printf( "Sending: %s, length = %d\n", send_buf, (int)strlen( send_buf ) );
// output (literally copy/pasted):
/*
Sending: 4_user53:6134;*0/, length = 17
*/
// This is correct and the desired output!!!
int iResult = send( this->ConnectSocket, send_buf, (int)strlen( send_buf ), 0 );
for( int i = 0; i < iResult; ++i )
{
printf( "Buffer[%d] = [%c]\n", i, send_buf[i] );
}
//Output (literally copy/pasted):
/*
Buffer[0] = [ ]
Buffer[1] = [ ]
Buffer[2] = [ ]
Buffer[3] = [ ]
Buffer[4] = [ ]
Buffer[5] = [ ]
Buffer[6] = [ ]
Buffer[7] = [ ]
Buffer[8] = [ ]
Buffer[9] = [ ]
Buffer[10] = [ ]
Buffer[11] = [ ]
Buffer[12] = [4]
Buffer[13] = [;]
Buffer[14] = [*]
Buffer[15] = [0]
Buffer[16] = [ ]
*/
// Here we only see the values 12-15. This is not the desired output. This is the issue I want to solve.
printf( "Iresult = %d\n", iResult );
//output= 17. This is correct.
if( iResult == SOCKET_ERROR )
{
printf( "\nSend failed with error: %d\n", WSAGetLastError() );
closesocket( ConnectSocket );
WSACleanup();
return false;
}
return true;
}
bool Client::receiveMessage()
{
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;
int iResult = recv( this->ConnectSocket, recvbuf, recvbuflen, 0 );
if( iResult > 0 )
{
printf("Message received OK!\n");
}
else if( iResult == 0 )
{
printf( "\nConnection closed\n" );
}
else
{
printf( "\nrecv failed with error: %d\n", WSAGetLastError() );
}
return true;
}
}
int main()
{
std::string in = "";
int n = 0;
bool rec = false;
if( this->firstConnection() )
{
while( in != "Q" )
{
std::cin >> in;
this->sendCommand( in );
while( !rec )
{
rec=this->receiveMessage();
}
}
return this->clientClose();
}
else
{
return false;
}
}
//Server
class Server {
Server::Server()
{
this->currentConnections = 0;
}
void Server::accept_clients()
{
for( int i = 0; i < MAX_CONS; i++ )
{
if( !this->client[i].con ) //i.e a client has not connected to this slot
{
if( this->accept_client( &( this->client[i] ) ) )
{
//update server status
this->Server_Status( CLIENT_CON );
}
}
}
}
int Server::accept_client( _client* x )
{
x->i = sizeof( sockaddr );
x->cs = accept( this->ListenSocket, (sockaddr*)&x->addr, &x->i );
if( x->cs == 0 || x->cs == SOCKET_ERROR )
{
printf( "SOCKET ERROR\n" );
return false;
}
x->con = true;
FD_ZERO( &x->set );
FD_SET( x->cs, &x->set );
printf( "Client accepted \n" );
// declaring character array
char char_array[DEFAULT_BUFLEN];
strcpy_s( char_array, result.c_str() );
this->send_clients( char_array );
return true;
}
void Server::send_clients( char* s )
{
int len = (int)strlen( s );
for( int i = 0; i < MAX_CONS; i++ )
{
if( this->client[i].con ) //valid slot,i.e a client has parked here
{
this->send_client( &( this->client[i] ), s, len );
}
}
}
int Server::send_client( _client* x, char* buffer, int sz )
{
char* send_buf;
if( strchr( buffer, ';' ) == NULL )
{
send_buf = x->gameinfo.SerializeToArray( DEFAULT_BUFLEN ); // see below
}
else
{
send_buf = buffer;
}
x->i = send( x->cs, send_buf, DEFAULT_BUFLEN, 0 );
if( x->i == SOCKET_ERROR || x->i == 0 )
{
printf( "Error: disconnecting client\n" );
disconnect_client( x );
return ( false );
}
else
{
return ( true );
}
}
void Server::recv_clients()
{
char buffer[DEFAULT_BUFLEN] = { 0 };
for( int i = 0; i < MAX_CONS; i++ )
{
if( client[i].con ) //valid slot,i.e a client has parked here
{
try
{
if( !this->recv_client( &( this->client[i] ), buffer, DEFAULT_BUFLEN ) )
{
//Error receiving
printf( "Error receiving message from client!\n" );
}
}
catch( ... )
{
printf( "Error\n" );
}
}
}
}
int Server::recv_client( _client* x, char* buffer, int sz )
{
if( FD_ISSET( x->cs, &x->set ) )
{
x->i = recv( x->cs, buffer, sz, 0 );
// checking contents
for( int i = 0; i < sz+1; ++i )
{
printf( "Buffer[%d] = [%c]\n", i, send_buf[i] );
}
// output is (literally copy/pasted):
/*
Buffer[0] = [ ]
Buffer[1] = [ ]
Buffer[2] = [ ]
Buffer[3] = [ ]
Buffer[4] = [ ]
Buffer[5] = [ ]
Buffer[6] = [ ]
Buffer[7] = [ ]
Buffer[8] = [ ]
Buffer[9] = [ ]
Buffer[10] = [ ]
Buffer[11] = [ ]
Buffer[12] = [4]
Buffer[13] = [;]
Buffer[14] = [*]
Buffer[15] = [0]
Buffer[16] = [ ]
*/
if( x->i == 0 )
{
disconnect_client( x );
return false;
}
std::string result = this->update( x, buffer, sz ); // returns a perfectly normal std::string - no issues here!!!
// declaring character array
char char_array[DEFAULT_BUFLEN];
strcpy_s( char_array, result.c_str() );
this->send_clients( char_array );
return true;
}
return false;
}
}
int main()
{
Server gameServer = Server();
while( true )
{
gameServer.accept_clients(); //Receive connections
gameServer.recv_clients(); //Receive and respond to data from clients
}
}
//Game info functions shared between client and server (only relevant ones)
char* GameInformation::SerializeToArray( int buflen )
{
std::string s_out = "Hello world;50*0/";
char out[512]; // more than enough space, no overflow issues here I promise
strcpy_s( out, s_out.c_str() );
return out;
}
如果你仍然不相信我,这里是打印的屏幕截图:
客户:
(调试时我将第一个迭代器长度设置为 20,这就是为什么有 20 个值,这当然是不准确的,对于给您带来的不便,我深表歉意)
服务器:
函数 GameInformation::SerializeToArray
不好,因为它 return 指向非静态局部数组。当从函数 returning 和在 returning 之后取消引用指向数组元素的指针是非法的时,数组的生命结束。
取而代之的是,您应该在堆上分配一个缓冲区并 return 一个指向该缓冲区的指针。
//Game info functions shared between client and server (only relevant ones)
char* GameInformation::SerializeToArray( int buflen )
{
std::string s_out = "Hello world;50*0/";
char* out = new char[512]; // more than enough space, no overflow issues here I promise
strcpy_s( out, 512, s_out.c_str() );
return out;
}
请注意,现在您应该在使用完后释放(通过 delete[]
)returned 数组。
另一种选择是 returning std::vector
而不是普通数组。这将消除手动管理内存区域的需要。
//Game info functions shared between client and server (only relevant ones)
std::vector<char> GameInformation::SerializeToArray( int buflen )
{
std::string s_out = "Hello world;50*0/";
std::vector<char> out(512); // more than enough space, no overflow issues here I promise
strcpy_s( &out[0], 512, s_out.c_str() );
return out;
}
在这种情况下调用者将是这样的:
// Send a buffer
std::vector<char> tmp = this->gameinfo.SerializeToArray( DEFAULT_BUFLEN );
const char* send_buf = tmp.data();
在这种情况下GameInformation::SerializeToArray
的声明也应该改为returnstd::vector<char>
。
我在我的 C++ 项目中遇到了一个非常奇怪的问题。我有一个客户端和一个服务器,它们相互发送字符数组数据。虽然初始 sending/reception 运行良好(请参阅:MVE 中的 Client.firstConnection()
),但在以下消息中,客户端在将其发送到服务器时似乎丢弃了数组中除第 12 到第 15 个字符之外的所有字符。然后服务器只接收这个部分填充的数组。
示例:
- 我想将缓冲区
4_user53:6134;*0/
从客户端发送到服务器。 - 在发送之前,我的变量在逐字符打印时显示
4_user53:6134;*0/
- 在服务器端,接收到缓冲区,但是当逐个字符打印时,显示
4;*0
- 在客户端,发送缓冲区后,变量在逐字符打印时立即显示
4;*0
。
我不知道为什么这些字符会消失。感谢所有建议,谢谢。
请在下面找到最小的可行示例,我已尽力注释兴趣点并说明打印功能的结果。
MVE:
使用 VS 2017、C++ 11 编译。
//Header file information
// all imports:
#include <cstdlib>
#include <iostream>
#include <map>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <random>
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
// Global vars
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "6000"
#define MAX_CONS 1 //Total No of Clients allowed
//Client
class Client{
bool Client::firstConnection()
{
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
struct addrinfo *result = NULL,
*ptr = NULL,
hints;
int iResult;
// Initialize Winsock
iResult = WSAStartup( MAKEWORD( 2, 2 ), &wsaData );
if( iResult != 0 )
{
printf( "WSAStartup failed with error: %d\n", iResult );
return 1;
}
ZeroMemory( &hints, sizeof( hints ) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
iResult = getaddrinfo( "127.0.0.1", DEFAULT_PORT, &hints, &result );
if( iResult != 0 )
{
printf( "getaddrinfo failed with error: %d\n", iResult );
WSACleanup();
return 1;
}
// Attempt to connect to an address until one succeeds
for( ptr = result; ptr != NULL; ptr = ptr->ai_next )
{
// Create a SOCKET for connecting to server
this->ConnectSocket = socket( ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol );
if( this->ConnectSocket == INVALID_SOCKET )
{
printf( "socket failed with error: %ld\n", WSAGetLastError() );
WSACleanup();
return 1;
}
// Connect to server.
iResult = connect( this->ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen );
if( iResult == SOCKET_ERROR )
{
closesocket( this->ConnectSocket );
this->ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo( result );
if( this->ConnectSocket == INVALID_SOCKET )
{
printf( "Unable to connect to server!\n" );
WSACleanup();
return 1;
}
std::cout << "Connection to server successful!\n";
// Send an initial buffer
int iResult = send( this->ConnectSocket, ( this->username ).c_str(), (int)strlen( ( this->username ).c_str() ), 0 ); //username is a perfectly fine string
// -> This instance of send does NOT drop any data! <- //
if( iResult == SOCKET_ERROR )
{
printf( "send failed with error: %d\n", WSAGetLastError() );
closesocket( this->ConnectSocket );
WSACleanup();
return false;
}
return true;
}
bool Client::sendCommand( std::string command )
{
// Send a buffer
char* tmp = this->gameinfo.SerializeToArray( DEFAULT_BUFLEN ); // see below
const char* send_buf = tmp; // have tried with and without const, results are the same
for( int i = 0; i < (int)strlen( send_buf ); ++i )
{
printf( "Buffer[%d] = [%c]\n", i, send_buf[i] );
}
// Output (literally copy/pasted):
/*
Buffer[0] = [4]
Buffer[1] = [_]
Buffer[2] = [u]
Buffer[3] = [s]
Buffer[4] = [e]
Buffer[5] = [r]
Buffer[6] = [5]
Buffer[7] = [3]
Buffer[8] = [:]
Buffer[9] = [6]
Buffer[10] = [1]
Buffer[11] = [3]
Buffer[12] = [4]
Buffer[13] = [;]
Buffer[14] = [*]
Buffer[15] = [0]
Buffer[16] = [/]
*/
// This is correct and the desired output!!! ^
printf( "Sending: %s, length = %d\n", send_buf, (int)strlen( send_buf ) );
// output (literally copy/pasted):
/*
Sending: 4_user53:6134;*0/, length = 17
*/
// This is correct and the desired output!!!
int iResult = send( this->ConnectSocket, send_buf, (int)strlen( send_buf ), 0 );
for( int i = 0; i < iResult; ++i )
{
printf( "Buffer[%d] = [%c]\n", i, send_buf[i] );
}
//Output (literally copy/pasted):
/*
Buffer[0] = [ ]
Buffer[1] = [ ]
Buffer[2] = [ ]
Buffer[3] = [ ]
Buffer[4] = [ ]
Buffer[5] = [ ]
Buffer[6] = [ ]
Buffer[7] = [ ]
Buffer[8] = [ ]
Buffer[9] = [ ]
Buffer[10] = [ ]
Buffer[11] = [ ]
Buffer[12] = [4]
Buffer[13] = [;]
Buffer[14] = [*]
Buffer[15] = [0]
Buffer[16] = [ ]
*/
// Here we only see the values 12-15. This is not the desired output. This is the issue I want to solve.
printf( "Iresult = %d\n", iResult );
//output= 17. This is correct.
if( iResult == SOCKET_ERROR )
{
printf( "\nSend failed with error: %d\n", WSAGetLastError() );
closesocket( ConnectSocket );
WSACleanup();
return false;
}
return true;
}
bool Client::receiveMessage()
{
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;
int iResult = recv( this->ConnectSocket, recvbuf, recvbuflen, 0 );
if( iResult > 0 )
{
printf("Message received OK!\n");
}
else if( iResult == 0 )
{
printf( "\nConnection closed\n" );
}
else
{
printf( "\nrecv failed with error: %d\n", WSAGetLastError() );
}
return true;
}
}
int main()
{
std::string in = "";
int n = 0;
bool rec = false;
if( this->firstConnection() )
{
while( in != "Q" )
{
std::cin >> in;
this->sendCommand( in );
while( !rec )
{
rec=this->receiveMessage();
}
}
return this->clientClose();
}
else
{
return false;
}
}
//Server
class Server {
Server::Server()
{
this->currentConnections = 0;
}
void Server::accept_clients()
{
for( int i = 0; i < MAX_CONS; i++ )
{
if( !this->client[i].con ) //i.e a client has not connected to this slot
{
if( this->accept_client( &( this->client[i] ) ) )
{
//update server status
this->Server_Status( CLIENT_CON );
}
}
}
}
int Server::accept_client( _client* x )
{
x->i = sizeof( sockaddr );
x->cs = accept( this->ListenSocket, (sockaddr*)&x->addr, &x->i );
if( x->cs == 0 || x->cs == SOCKET_ERROR )
{
printf( "SOCKET ERROR\n" );
return false;
}
x->con = true;
FD_ZERO( &x->set );
FD_SET( x->cs, &x->set );
printf( "Client accepted \n" );
// declaring character array
char char_array[DEFAULT_BUFLEN];
strcpy_s( char_array, result.c_str() );
this->send_clients( char_array );
return true;
}
void Server::send_clients( char* s )
{
int len = (int)strlen( s );
for( int i = 0; i < MAX_CONS; i++ )
{
if( this->client[i].con ) //valid slot,i.e a client has parked here
{
this->send_client( &( this->client[i] ), s, len );
}
}
}
int Server::send_client( _client* x, char* buffer, int sz )
{
char* send_buf;
if( strchr( buffer, ';' ) == NULL )
{
send_buf = x->gameinfo.SerializeToArray( DEFAULT_BUFLEN ); // see below
}
else
{
send_buf = buffer;
}
x->i = send( x->cs, send_buf, DEFAULT_BUFLEN, 0 );
if( x->i == SOCKET_ERROR || x->i == 0 )
{
printf( "Error: disconnecting client\n" );
disconnect_client( x );
return ( false );
}
else
{
return ( true );
}
}
void Server::recv_clients()
{
char buffer[DEFAULT_BUFLEN] = { 0 };
for( int i = 0; i < MAX_CONS; i++ )
{
if( client[i].con ) //valid slot,i.e a client has parked here
{
try
{
if( !this->recv_client( &( this->client[i] ), buffer, DEFAULT_BUFLEN ) )
{
//Error receiving
printf( "Error receiving message from client!\n" );
}
}
catch( ... )
{
printf( "Error\n" );
}
}
}
}
int Server::recv_client( _client* x, char* buffer, int sz )
{
if( FD_ISSET( x->cs, &x->set ) )
{
x->i = recv( x->cs, buffer, sz, 0 );
// checking contents
for( int i = 0; i < sz+1; ++i )
{
printf( "Buffer[%d] = [%c]\n", i, send_buf[i] );
}
// output is (literally copy/pasted):
/*
Buffer[0] = [ ]
Buffer[1] = [ ]
Buffer[2] = [ ]
Buffer[3] = [ ]
Buffer[4] = [ ]
Buffer[5] = [ ]
Buffer[6] = [ ]
Buffer[7] = [ ]
Buffer[8] = [ ]
Buffer[9] = [ ]
Buffer[10] = [ ]
Buffer[11] = [ ]
Buffer[12] = [4]
Buffer[13] = [;]
Buffer[14] = [*]
Buffer[15] = [0]
Buffer[16] = [ ]
*/
if( x->i == 0 )
{
disconnect_client( x );
return false;
}
std::string result = this->update( x, buffer, sz ); // returns a perfectly normal std::string - no issues here!!!
// declaring character array
char char_array[DEFAULT_BUFLEN];
strcpy_s( char_array, result.c_str() );
this->send_clients( char_array );
return true;
}
return false;
}
}
int main()
{
Server gameServer = Server();
while( true )
{
gameServer.accept_clients(); //Receive connections
gameServer.recv_clients(); //Receive and respond to data from clients
}
}
//Game info functions shared between client and server (only relevant ones)
char* GameInformation::SerializeToArray( int buflen )
{
std::string s_out = "Hello world;50*0/";
char out[512]; // more than enough space, no overflow issues here I promise
strcpy_s( out, s_out.c_str() );
return out;
}
如果你仍然不相信我,这里是打印的屏幕截图:
客户:
(调试时我将第一个迭代器长度设置为 20,这就是为什么有 20 个值,这当然是不准确的,对于给您带来的不便,我深表歉意)
服务器:
函数 GameInformation::SerializeToArray
不好,因为它 return 指向非静态局部数组。当从函数 returning 和在 returning 之后取消引用指向数组元素的指针是非法的时,数组的生命结束。
取而代之的是,您应该在堆上分配一个缓冲区并 return 一个指向该缓冲区的指针。
//Game info functions shared between client and server (only relevant ones)
char* GameInformation::SerializeToArray( int buflen )
{
std::string s_out = "Hello world;50*0/";
char* out = new char[512]; // more than enough space, no overflow issues here I promise
strcpy_s( out, 512, s_out.c_str() );
return out;
}
请注意,现在您应该在使用完后释放(通过 delete[]
)returned 数组。
另一种选择是 returning std::vector
而不是普通数组。这将消除手动管理内存区域的需要。
//Game info functions shared between client and server (only relevant ones)
std::vector<char> GameInformation::SerializeToArray( int buflen )
{
std::string s_out = "Hello world;50*0/";
std::vector<char> out(512); // more than enough space, no overflow issues here I promise
strcpy_s( &out[0], 512, s_out.c_str() );
return out;
}
在这种情况下调用者将是这样的:
// Send a buffer
std::vector<char> tmp = this->gameinfo.SerializeToArray( DEFAULT_BUFLEN );
const char* send_buf = tmp.data();
在这种情况下GameInformation::SerializeToArray
的声明也应该改为returnstd::vector<char>
。