io changes and tweaks. Working on adding SOCK_STREAM (tcp) support. Still
needs some work, but is basically there.
This commit is contained in:
@@ -15,3 +15,11 @@ libsgio_a_SOURCES = \
|
|||||||
sg_socket.cxx
|
sg_socket.cxx
|
||||||
|
|
||||||
INCLUDES += -I$(top_srcdir)
|
INCLUDES += -I$(top_srcdir)
|
||||||
|
|
||||||
|
noinst_PROGRAMS = socktest
|
||||||
|
|
||||||
|
socktest_SOURCES = socktest.cxx
|
||||||
|
|
||||||
|
socktest_LDADD = \
|
||||||
|
$(top_builddir)/simgear/io/libsgio.a \
|
||||||
|
$(top_builddir)/simgear/debug/libsgdebug.a
|
||||||
@@ -39,7 +39,7 @@ SGIOChannel::~SGIOChannel()
|
|||||||
|
|
||||||
|
|
||||||
// dummy configure routine
|
// dummy configure routine
|
||||||
bool SGIOChannel::open( SGProtocolDir dir ) {
|
bool SGIOChannel::open( const SGProtocolDir d ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,13 +57,13 @@ int SGIOChannel::readline( char *buf, int length ) {
|
|||||||
|
|
||||||
|
|
||||||
// dummy process routine
|
// dummy process routine
|
||||||
int SGIOChannel::write( char *buf, int length ) {
|
int SGIOChannel::write( const char *buf, const int length ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// dummy process routine
|
// dummy process routine
|
||||||
int SGIOChannel::writestring( char *str ) {
|
int SGIOChannel::writestring( const char *str ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,21 +55,27 @@ enum SGChannelType {
|
|||||||
class SGIOChannel {
|
class SGIOChannel {
|
||||||
|
|
||||||
SGChannelType type;
|
SGChannelType type;
|
||||||
|
SGProtocolDir dir;
|
||||||
|
bool valid;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
SGIOChannel();
|
SGIOChannel();
|
||||||
virtual ~SGIOChannel();
|
virtual ~SGIOChannel();
|
||||||
|
|
||||||
virtual bool open( SGProtocolDir dir );
|
virtual bool open( const SGProtocolDir d );
|
||||||
virtual int read( char *buf, int length );
|
virtual int read( char *buf, int length );
|
||||||
virtual int readline( char *buf, int length );
|
virtual int readline( char *buf, int length );
|
||||||
virtual int write( char *buf, int length );
|
virtual int write( const char *buf, const int length );
|
||||||
virtual int writestring( char *str );
|
virtual int writestring( const char *str );
|
||||||
virtual bool close();
|
virtual bool close();
|
||||||
|
|
||||||
virtual void set_type( SGChannelType t ) { type = t; }
|
inline void set_type( SGChannelType t ) { type = t; }
|
||||||
virtual SGChannelType get_type() const { return type; }
|
inline SGChannelType get_type() const { return type; }
|
||||||
|
|
||||||
|
inline void set_dir( const SGProtocolDir d ) { dir = d; }
|
||||||
|
inline SGProtocolDir get_dir() const { return dir; }
|
||||||
|
inline bool isvalid() const { return valid; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -47,15 +47,17 @@ SGFile::~SGFile() {
|
|||||||
|
|
||||||
|
|
||||||
// open the file based on specified direction
|
// open the file based on specified direction
|
||||||
bool SGFile::open( SGProtocolDir dir ) {
|
bool SGFile::open( const SGProtocolDir d ) {
|
||||||
if ( dir == SG_IO_OUT ) {
|
set_dir( d );
|
||||||
|
|
||||||
|
if ( get_dir() == SG_IO_OUT ) {
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
int mode = 00666;
|
int mode = 00666;
|
||||||
#else
|
#else
|
||||||
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
||||||
#endif
|
#endif
|
||||||
fp = ::open( file_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode );
|
fp = ::open( file_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode );
|
||||||
} else if ( dir == SG_IO_IN ) {
|
} else if ( get_dir() == SG_IO_IN ) {
|
||||||
fp = ::open( file_name.c_str(), O_RDONLY );
|
fp = ::open( file_name.c_str(), O_RDONLY );
|
||||||
} else {
|
} else {
|
||||||
FG_LOG( FG_IO, FG_ALERT,
|
FG_LOG( FG_IO, FG_ALERT,
|
||||||
@@ -105,7 +107,7 @@ int SGFile::readline( char *buf, int length ) {
|
|||||||
|
|
||||||
|
|
||||||
// write data to a file
|
// write data to a file
|
||||||
int SGFile::write( char *buf, int length ) {
|
int SGFile::write( const char *buf, const int length ) {
|
||||||
int result = ::write( fp, buf, length );
|
int result = ::write( fp, buf, length );
|
||||||
if ( result != length ) {
|
if ( result != length ) {
|
||||||
FG_LOG( FG_IO, FG_ALERT, "Error writing data: " << file_name );
|
FG_LOG( FG_IO, FG_ALERT, "Error writing data: " << file_name );
|
||||||
@@ -116,7 +118,7 @@ int SGFile::write( char *buf, int length ) {
|
|||||||
|
|
||||||
|
|
||||||
// write null terminated string to a file
|
// write null terminated string to a file
|
||||||
int SGFile::writestring( char *str ) {
|
int SGFile::writestring( const char *str ) {
|
||||||
int length = strlen( str );
|
int length = strlen( str );
|
||||||
return write( str, length );
|
return write( str, length );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public:
|
|||||||
~SGFile();
|
~SGFile();
|
||||||
|
|
||||||
// open the file based on specified direction
|
// open the file based on specified direction
|
||||||
bool open( SGProtocolDir dir );
|
bool open( const SGProtocolDir dir );
|
||||||
|
|
||||||
// read a block of data of specified size
|
// read a block of data of specified size
|
||||||
int read( char *buf, int length );
|
int read( char *buf, int length );
|
||||||
@@ -65,10 +65,10 @@ public:
|
|||||||
int readline( char *buf, int length );
|
int readline( char *buf, int length );
|
||||||
|
|
||||||
// write data to a file
|
// write data to a file
|
||||||
int write( char *buf, int length );
|
int write( const char *buf, const int length );
|
||||||
|
|
||||||
// write null terminated string to a file
|
// write null terminated string to a file
|
||||||
int writestring( char *str );
|
int writestring( const char *str );
|
||||||
|
|
||||||
// close file
|
// close file
|
||||||
bool close();
|
bool close();
|
||||||
|
|||||||
@@ -47,7 +47,9 @@ SGSerial::~SGSerial() {
|
|||||||
|
|
||||||
|
|
||||||
// open the serial port based on specified direction
|
// open the serial port based on specified direction
|
||||||
bool SGSerial::open( SGProtocolDir dir ) {
|
bool SGSerial::open( const SGProtocolDir d ) {
|
||||||
|
set_dir( d );
|
||||||
|
|
||||||
if ( ! port.open_port( device ) ) {
|
if ( ! port.open_port( device ) ) {
|
||||||
FG_LOG( FG_IO, FG_ALERT, "Error opening device: " << device );
|
FG_LOG( FG_IO, FG_ALERT, "Error opening device: " << device );
|
||||||
return false;
|
return false;
|
||||||
@@ -127,7 +129,7 @@ int SGSerial::readline( char *buf, int length ) {
|
|||||||
|
|
||||||
|
|
||||||
// write data to port
|
// write data to port
|
||||||
int SGSerial::write( char *buf, int length ) {
|
int SGSerial::write( const char *buf, const int length ) {
|
||||||
int result = port.write_port( buf, length );
|
int result = port.write_port( buf, length );
|
||||||
|
|
||||||
if ( result != length ) {
|
if ( result != length ) {
|
||||||
@@ -139,7 +141,7 @@ int SGSerial::write( char *buf, int length ) {
|
|||||||
|
|
||||||
|
|
||||||
// write null terminated string to port
|
// write null terminated string to port
|
||||||
int SGSerial::writestring( char *str ) {
|
int SGSerial::writestring( const char *str ) {
|
||||||
int length = strlen( str );
|
int length = strlen( str );
|
||||||
return write( str, length );
|
return write( str, length );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public:
|
|||||||
~SGSerial();
|
~SGSerial();
|
||||||
|
|
||||||
// open the serial port based on specified direction
|
// open the serial port based on specified direction
|
||||||
bool open( SGProtocolDir dir );
|
bool open( const SGProtocolDir d );
|
||||||
|
|
||||||
// read a block of data of specified size
|
// read a block of data of specified size
|
||||||
int read( char *buf, int length );
|
int read( char *buf, int length );
|
||||||
@@ -70,10 +70,10 @@ public:
|
|||||||
int readline( char *buf, int length );
|
int readline( char *buf, int length );
|
||||||
|
|
||||||
// write data to port
|
// write data to port
|
||||||
int write( char *buf, int length );
|
int write( const char *buf, const int length );
|
||||||
|
|
||||||
// write null terminated string to port
|
// write null terminated string to port
|
||||||
int writestring( char *str );
|
int writestring( const char *str );
|
||||||
|
|
||||||
// close port
|
// close port
|
||||||
bool close();
|
bool close();
|
||||||
|
|||||||
+94
-20
@@ -182,7 +182,9 @@ int SGSocket::closesocket( int fd ) {
|
|||||||
// If specified as a server (in direction for now) open the master
|
// If specified as a server (in direction for now) open the master
|
||||||
// listening socket. If specified as a client (out direction), open a
|
// listening socket. If specified as a client (out direction), open a
|
||||||
// connection to a server.
|
// connection to a server.
|
||||||
bool SGSocket::open( SGProtocolDir dir ) {
|
bool SGSocket::open( const SGProtocolDir d ) {
|
||||||
|
set_dir( d );
|
||||||
|
|
||||||
if ( port_str == "" || port_str == "any" ) {
|
if ( port_str == "" || port_str == "any" ) {
|
||||||
port = 0;
|
port = 0;
|
||||||
} else {
|
} else {
|
||||||
@@ -191,14 +193,17 @@ bool SGSocket::open( SGProtocolDir dir ) {
|
|||||||
|
|
||||||
// client_connections.clear();
|
// client_connections.clear();
|
||||||
|
|
||||||
if ( dir == SG_IO_IN ) {
|
if ( get_dir() == SG_IO_IN ) {
|
||||||
// this means server for now
|
// this means server for now
|
||||||
|
|
||||||
// Setup socket to listen on. Set "port" before making this
|
// Setup socket to listen on. Set "port" before making this
|
||||||
// call. A port of "0" indicates that we want to let the os
|
// call. A port of "0" indicates that we want to let the os
|
||||||
// pick any available port.
|
// pick any available port.
|
||||||
sock = make_server_socket();
|
sock = make_server_socket();
|
||||||
// TODO: check for error.
|
if ( sock == INVALID_SOCKET ) {
|
||||||
|
FG_LOG( FG_IO, FG_ALERT, "socket creation failed" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
FG_LOG( FG_IO, FG_INFO, "socket is connected to port = " << port );
|
FG_LOG( FG_IO, FG_INFO, "socket is connected to port = " << port );
|
||||||
|
|
||||||
@@ -211,7 +216,7 @@ bool SGSocket::open( SGProtocolDir dir ) {
|
|||||||
listen( sock, SG_MAX_SOCKET_QUEUE );
|
listen( sock, SG_MAX_SOCKET_QUEUE );
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if ( dir == SG_IO_OUT ) {
|
} else if ( get_dir() == SG_IO_OUT ) {
|
||||||
// this means client for now
|
// this means client for now
|
||||||
|
|
||||||
sock = make_client_socket();
|
sock = make_client_socket();
|
||||||
@@ -221,7 +226,7 @@ bool SGSocket::open( SGProtocolDir dir ) {
|
|||||||
// Non-blocking UDP
|
// Non-blocking UDP
|
||||||
nonblock();
|
nonblock();
|
||||||
}
|
}
|
||||||
} else if ( dir == SG_IO_BI && sock_style == SOCK_STREAM ) {
|
} else if ( get_dir() == SG_IO_BI && sock_style == SOCK_STREAM ) {
|
||||||
// this means server for TCP sockets
|
// this means server for TCP sockets
|
||||||
|
|
||||||
// Setup socket to listen on. Set "port" before making this
|
// Setup socket to listen on. Set "port" before making this
|
||||||
@@ -247,6 +252,10 @@ bool SGSocket::open( SGProtocolDir dir ) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extra SOCK_STREAM stuff
|
||||||
|
msgsock = INVALID_SOCKET;
|
||||||
|
first_read = false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,8 +263,11 @@ bool SGSocket::open( SGProtocolDir dir ) {
|
|||||||
// read data from socket (server)
|
// read data from socket (server)
|
||||||
// read a block of data of specified size
|
// read a block of data of specified size
|
||||||
int SGSocket::read( char *buf, int length ) {
|
int SGSocket::read( char *buf, int length ) {
|
||||||
int result = 0;
|
if ( sock == INVALID_SOCKET ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = 0;
|
||||||
// check for potential input
|
// check for potential input
|
||||||
fd_set ready;
|
fd_set ready;
|
||||||
FD_ZERO(&ready);
|
FD_ZERO(&ready);
|
||||||
@@ -269,7 +281,20 @@ int SGSocket::read( char *buf, int length ) {
|
|||||||
select(32, &ready, 0, 0, &tv);
|
select(32, &ready, 0, 0, &tv);
|
||||||
|
|
||||||
if ( FD_ISSET(sock, &ready) ) {
|
if ( FD_ISSET(sock, &ready) ) {
|
||||||
result = readsocket( sock, buf, length );
|
// cout << "data ready" << endl;
|
||||||
|
|
||||||
|
if ( sock_style == SOCK_STREAM ) {
|
||||||
|
if ( msgsock == INVALID_SOCKET ) {
|
||||||
|
msgsock = accept(sock, 0, 0);
|
||||||
|
closesocket(sock);
|
||||||
|
sock = msgsock;
|
||||||
|
} else {
|
||||||
|
result = readsocket( sock, buf, length );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = readsocket( sock, buf, length );
|
||||||
|
}
|
||||||
|
|
||||||
if ( result != length ) {
|
if ( result != length ) {
|
||||||
FG_LOG( FG_IO, FG_INFO,
|
FG_LOG( FG_IO, FG_INFO,
|
||||||
"Warning: read() not enough bytes." );
|
"Warning: read() not enough bytes." );
|
||||||
@@ -282,7 +307,11 @@ int SGSocket::read( char *buf, int length ) {
|
|||||||
|
|
||||||
// read a line of data, length is max size of input buffer
|
// read a line of data, length is max size of input buffer
|
||||||
int SGSocket::readline( char *buf, int length ) {
|
int SGSocket::readline( char *buf, int length ) {
|
||||||
int result = 0;
|
if ( sock == INVALID_SOCKET ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cout << "sock = " << sock << endl;
|
||||||
|
|
||||||
// check for potential input
|
// check for potential input
|
||||||
fd_set ready;
|
fd_set ready;
|
||||||
@@ -294,19 +323,55 @@ int SGSocket::readline( char *buf, int length ) {
|
|||||||
|
|
||||||
// test for any input read on sock (returning immediately, even if
|
// test for any input read on sock (returning immediately, even if
|
||||||
// nothing)
|
// nothing)
|
||||||
int rc = select(32, &ready, 0, 0, &tv);
|
int result = select(32, &ready, 0, 0, &tv);
|
||||||
// FG_LOG( FG_IO, FG_DEBUG, "select returned " << rc );
|
// cout << "result = " << result << endl;
|
||||||
|
|
||||||
if ( FD_ISSET(sock, &ready) ) {
|
if ( FD_ISSET(sock, &ready) ) {
|
||||||
|
// cout << "fd change state\n";
|
||||||
// read a chunk, keep in the save buffer until we have the
|
// read a chunk, keep in the save buffer until we have the
|
||||||
// requested amount read
|
// requested amount read
|
||||||
|
|
||||||
char *buf_ptr = save_buf + save_len;
|
if ( sock_style == SOCK_STREAM ) {
|
||||||
result = readsocket( sock, buf_ptr, SG_IO_MAX_MSG_SIZE - save_len );
|
// cout << "sock_stream\n";
|
||||||
save_len += result;
|
if ( msgsock == INVALID_SOCKET ) {
|
||||||
|
// cout << "msgsock == invalid\n";
|
||||||
|
msgsock = accept(sock, 0, 0);
|
||||||
|
closesocket(sock);
|
||||||
|
sock = msgsock;
|
||||||
|
} else {
|
||||||
|
// cout << "ready to read\n";
|
||||||
|
char *buf_ptr = save_buf + save_len;
|
||||||
|
result = readsocket( sock, buf_ptr, SG_IO_MAX_MSG_SIZE
|
||||||
|
- save_len );
|
||||||
|
// cout << "read result = " << result << endl;
|
||||||
|
|
||||||
|
if ( result > 0 ) {
|
||||||
|
first_read = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
save_len += result;
|
||||||
|
|
||||||
|
// Try and detect that the remote end died. This
|
||||||
|
// could cause problems so if you see connections
|
||||||
|
// dropping for unexplained reasons, LOOK HERE!
|
||||||
|
if ( result == 0 && save_len == 0 && first_read == true ) {
|
||||||
|
FG_LOG( FG_IO, FG_ALERT,
|
||||||
|
"Connection closed by foreign host." );
|
||||||
|
closesocket(sock);
|
||||||
|
open( get_dir() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
char *buf_ptr = save_buf + save_len;
|
||||||
|
result = readsocket( sock, buf_ptr, SG_IO_MAX_MSG_SIZE - save_len );
|
||||||
|
save_len += result;
|
||||||
|
}
|
||||||
|
|
||||||
// cout << "current read = " << buf_ptr << endl;
|
// cout << "current read = " << buf_ptr << endl;
|
||||||
// cout << "current save_buf = " << save_buf << endl;
|
// cout << "current save_buf = " << save_buf << endl;
|
||||||
// cout << "save_len = " << save_len << endl;
|
// cout << "save_len = " << save_len << endl;
|
||||||
|
} else {
|
||||||
|
// cout << "no data ready\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// look for the end of line in save_buf
|
// look for the end of line in save_buf
|
||||||
@@ -339,7 +404,11 @@ int SGSocket::readline( char *buf, int length ) {
|
|||||||
|
|
||||||
|
|
||||||
// write data to socket (client)
|
// write data to socket (client)
|
||||||
int SGSocket::write( char *buf, int length ) {
|
int SGSocket::write( const char *buf, const int length ) {
|
||||||
|
if ( sock == INVALID_SOCKET ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool error_condition = false;
|
bool error_condition = false;
|
||||||
|
|
||||||
if ( writesocket(sock, buf, length) < 0 ) {
|
if ( writesocket(sock, buf, length) < 0 ) {
|
||||||
@@ -404,7 +473,11 @@ int SGSocket::write( char *buf, int length ) {
|
|||||||
|
|
||||||
|
|
||||||
// write null terminated string to socket (server)
|
// write null terminated string to socket (server)
|
||||||
int SGSocket::writestring( char *str ) {
|
int SGSocket::writestring( const char *str ) {
|
||||||
|
if ( sock == INVALID_SOCKET ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int length = strlen( str );
|
int length = strlen( str );
|
||||||
return write( str, length );
|
return write( str, length );
|
||||||
}
|
}
|
||||||
@@ -412,12 +485,9 @@ int SGSocket::writestring( char *str ) {
|
|||||||
|
|
||||||
// close the port
|
// close the port
|
||||||
bool SGSocket::close() {
|
bool SGSocket::close() {
|
||||||
#if 0
|
if ( sock == INVALID_SOCKET ) {
|
||||||
for ( int i = 0; i < (int)client_connections.size(); ++i ) {
|
return 0;
|
||||||
int msgsock = client_connections[i];
|
|
||||||
closesocket( msgsock );
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
closesocket( sock );
|
closesocket( sock );
|
||||||
return true;
|
return true;
|
||||||
@@ -426,6 +496,10 @@ bool SGSocket::close() {
|
|||||||
|
|
||||||
// configure the socket as non-blocking
|
// configure the socket as non-blocking
|
||||||
bool SGSocket::nonblock() {
|
bool SGSocket::nonblock() {
|
||||||
|
if ( sock == INVALID_SOCKET ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
u_long arg = 1;
|
u_long arg = 1;
|
||||||
if (ioctlsocket( sock, FIONBIO, &arg ) != 0) {
|
if (ioctlsocket( sock, FIONBIO, &arg ) != 0) {
|
||||||
|
|||||||
@@ -63,9 +63,12 @@ private:
|
|||||||
int save_len;
|
int save_len;
|
||||||
|
|
||||||
SocketType sock;
|
SocketType sock;
|
||||||
|
SocketType msgsock;
|
||||||
short unsigned int port;
|
short unsigned int port;
|
||||||
int sock_style; // SOCK_STREAM or SOCK_DGRAM
|
int sock_style; // SOCK_STREAM or SOCK_DGRAM
|
||||||
|
|
||||||
|
bool first_read;
|
||||||
|
|
||||||
// make a server (master listening) socket
|
// make a server (master listening) socket
|
||||||
SocketType make_server_socket();
|
SocketType make_server_socket();
|
||||||
|
|
||||||
@@ -93,7 +96,7 @@ public:
|
|||||||
// If specified as a server (in direction for now) open the master
|
// If specified as a server (in direction for now) open the master
|
||||||
// listening socket. If specified as a client (out direction),
|
// listening socket. If specified as a client (out direction),
|
||||||
// open a connection to a server.
|
// open a connection to a server.
|
||||||
bool open( SGProtocolDir dir );
|
bool open( const SGProtocolDir d );
|
||||||
|
|
||||||
// read data from socket
|
// read data from socket
|
||||||
int read( char *buf, int length );
|
int read( char *buf, int length );
|
||||||
@@ -102,10 +105,10 @@ public:
|
|||||||
int readline( char *buf, int length );
|
int readline( char *buf, int length );
|
||||||
|
|
||||||
// write data to a socket
|
// write data to a socket
|
||||||
int write( char *buf, int length );
|
int write( const char *buf, const int length );
|
||||||
|
|
||||||
// write null terminated string to a socket
|
// write null terminated string to a socket
|
||||||
int writestring( char *str );
|
int writestring( const char *str );
|
||||||
|
|
||||||
// close file
|
// close file
|
||||||
bool close();
|
bool close();
|
||||||
|
|||||||
Reference in New Issue
Block a user