mistserver/util/ddv_socket.cpp
2011-04-18 15:42:14 +02:00

355 lines
12 KiB
C++

/// \file ddv_socket.cpp
/// Holds all code for the DDV namespace.
#include "ddv_socket.h"
/// Create a new base socket. This is a basic constructor for converting any valid socket to a DDV::Socket.
/// \param sockNo Integer representing the socket to convert.
DDV::Socket::Socket(int sockNo){
sock = sockNo;
Error = false;
Blocking = false;
}//DDV::Socket basic constructor
/// Create a new disconnected base socket. This is a basic constructor for placeholder purposes.
/// A socket created like this is always disconnected and should/could be overwritten at some point.
DDV::Socket::Socket(){
sock = -1;
Error = false;
Blocking = false;
}//DDV::Socket basic constructor
/// Close connection. The internal socket is closed and then set to -1.
void DDV::Socket::close(){
#if DEBUG >= 3
fprintf(stderr, "Socket closed.\n");
#endif
::close(sock);
sock = -1;
}//DDV::Socket::close
/// Returns internal socket number.
int DDV::Socket::getSocket(){return sock;}
/// Create a new Unix Socket. This socket will (try to) connect to the given address right away.
/// \param address String containing the location of the Unix socket to connect to.
/// \param nonblock Whether the socket should be nonblocking. False by default.
DDV::Socket::Socket(std::string address, bool nonblock){
sock = socket(PF_UNIX, SOCK_STREAM, 0);
if (sock < 0){
#if DEBUG >= 1
fprintf(stderr, "Could not create socket! Error: %s\n", strerror(errno));
#endif
return;
}
Error = false;
Blocking = false;
sockaddr_un addr;
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, address.c_str(), address.size()+1);
int r = connect(sock, (sockaddr*)&addr, sizeof(addr));
if (r == 0){
if (nonblock){
int flags = fcntl(sock, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sock, F_SETFL, flags);
}
}else{
#if DEBUG >= 1
fprintf(stderr, "Could not connect to %s! Error: %s\n", address.c_str(), strerror(errno));
#endif
close();
}
}//DDV::Socket Unix Contructor
/// Returns the ready-state for this socket.
/// \returns 1 if data is waiting to be read, -1 if not connected, 0 otherwise.
signed int DDV::Socket::ready(){
if (sock < 0) return -1;
char tmp;
int preflags = fcntl(sock, F_GETFL, 0);
int postflags = preflags | O_NONBLOCK;
fcntl(sock, F_SETFL, postflags);
int r = recv(sock, &tmp, 1, MSG_PEEK);
fcntl(sock, F_SETFL, preflags);
if (r < 0){
if (errno == EAGAIN || errno == EWOULDBLOCK){
return 0;
}else{
#if DEBUG >= 2
fprintf(stderr, "Socket ready error! Error: %s\n", strerror(errno));
#endif
close();
return -1;
}
}
if (r == 0){
close(); return -1;
}
return r;
}
/// Returns the connected-state for this socket.
/// Note that this function might be slightly behind the real situation.
/// The connection status is updated after every read/write attempt, when errors occur
/// and when the socket is closed manually.
/// \returns True if socket is connected, false otherwise.
bool DDV::Socket::connected(){
return (sock >= 0);
}
/// Writes data to socket. This function blocks if the socket is blocking and all data cannot be written right away.
/// If the socket is nonblocking and not all data can be written, this function sets internal variable Blocking to true
/// and returns false.
/// \param buffer Location of the buffer to write from.
/// \param len Amount of bytes to write.
/// \returns True if the whole write was succesfull, false otherwise.
bool DDV::Socket::write(const void * buffer, int len){
int sofar = 0;
if (sock < 0){return false;}
while (sofar != len){
int r = send(sock, (char*)buffer + sofar, len-sofar, 0);
if (r <= 0){
Error = true;
#if DEBUG >= 2
fprintf(stderr, "Could not write data! Error: %s\n", strerror(errno));
#endif
close();
return false;
}else{
sofar += r;
}
}
return true;
}//DDv::Socket::write
/// Reads data from socket. This function blocks if the socket is blocking and all data cannot be read right away.
/// If the socket is nonblocking and not all data can be read, this function sets internal variable Blocking to true
/// and returns false.
/// \param buffer Location of the buffer to read to.
/// \param len Amount of bytes to read.
/// \returns True if the whole read was succesfull, false otherwise.
bool DDV::Socket::read(void * buffer, int len){
int sofar = 0;
if (sock < 0){return false;}
while (sofar != len){
int r = recv(sock, (char*)buffer + sofar, len-sofar, 0);
if (r <= 0){
Error = true;
#if DEBUG >= 2
fprintf(stderr, "Could not read data! Error: %s\n", strerror(errno));
#endif
close();
return false;
}else{
sofar += r;
}
}
return true;
}//DDV::Socket::read
/// Read call that is compatible with file access syntax. This function simply calls the other read function.
bool DDV::Socket::read(void * buffer, int width, int count){return read(buffer, width*count);}
/// Write call that is compatible with file access syntax. This function simply calls the other write function.
bool DDV::Socket::write(void * buffer, int width, int count){return write(buffer, width*count);}
/// Write call that is compatible with std::string. This function simply calls the other write function.
bool DDV::Socket::write(const std::string data){return write(data.c_str(), data.size());}
/// Incremental write call. This function tries to write len bytes to the socket from the buffer,
/// returning the amount of bytes it actually wrote.
/// \param buffer Location of the buffer to write from.
/// \param len Amount of bytes to write.
/// \returns The amount of bytes actually written.
int DDV::Socket::iwrite(void * buffer, int len){
int r = send(sock, buffer, len, 0);
if (r < 0){
switch (errno){
case EWOULDBLOCK: return 0; break;
default:
Error = true;
#if DEBUG >= 2
fprintf(stderr, "Could not iwrite data! Error: %s\n", strerror(errno));
#endif
close();
return 0;
break;
}
}
if (r == 0){close();}
return r;
}//DDV::Socket::iwrite
/// Incremental read call. This function tries to read len bytes to the buffer from the socket,
/// returning the amount of bytes it actually read.
/// \param buffer Location of the buffer to read to.
/// \param len Amount of bytes to read.
/// \returns The amount of bytes actually read.
int DDV::Socket::iread(void * buffer, int len){
int r = recv(sock, buffer, len, 0);
if (r < 0){
switch (errno){
case EWOULDBLOCK: return 0; break;
default:
Error = true;
#if DEBUG >= 2
fprintf(stderr, "Could not iread data! Error: %s\n", strerror(errno));
#endif
close();
return 0;
break;
}
}
if (r == 0){close();}
return r;
}//DDV::Socket::iread
/// Read call that is compatible with std::string.
/// Data is read using iread (which is nonblocking if the DDV::Socket itself is),
/// then appended to end of buffer.
/// \param buffer std::string to append data to.
/// \return True if new data arrived, false otherwise.
bool DDV::Socket::read(std::string & buffer){
char cbuffer[5000];
if (!read(cbuffer, 1)){return false;}
int num = iread(cbuffer+1, 4999);
if (num > 0){
buffer.append(cbuffer, num+1);
}else{
buffer.append(cbuffer, 1);
}
return true;
}//read
/// Create a new base ServerSocket. The socket is never connected, and a placeholder for later connections.
DDV::ServerSocket::ServerSocket(){
sock = -1;
}//DDV::ServerSocket base Constructor
/// Create a new TCP ServerSocket. The socket is immediately bound and set to listen.
/// A maximum of 100 connections will be accepted between accept() calls.
/// Any further connections coming in will be dropped.
/// \param port The TCP port to listen on
/// \param hostname (optional) The interface to bind to. The default is 0.0.0.0 (all interfaces).
/// \param nonblock (optional) Whether accept() calls will be nonblocking. Default is false (blocking).
DDV::ServerSocket::ServerSocket(int port, std::string hostname, bool nonblock){
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0){
#if DEBUG >= 1
fprintf(stderr, "Could not create socket! Error: %s\n", strerror(errno));
#endif
return;
}
int on = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (nonblock){
int flags = fcntl(sock, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sock, F_SETFL, flags);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);//set port
inet_pton(AF_INET, hostname.c_str(), &addr.sin_addr);//set interface, 0.0.0.0 (default) is all
int ret = bind(sock, (sockaddr*)&addr, sizeof(addr));//do the actual bind
if (ret == 0){
ret = listen(sock, 100);//start listening, backlog of 100 allowed
if (ret == 0){
return;
}else{
#if DEBUG >= 1
fprintf(stderr, "Listen failed! Error: %s\n", strerror(errno));
#endif
close();
return;
}
}else{
#if DEBUG >= 1
fprintf(stderr, "Binding failed! Error: %s\n", strerror(errno));
#endif
close();
return;
}
}//DDV::ServerSocket TCP Constructor
/// Create a new Unix ServerSocket. The socket is immediately bound and set to listen.
/// A maximum of 100 connections will be accepted between accept() calls.
/// Any further connections coming in will be dropped.
/// The address used will first be unlinked - so it succeeds if the Unix socket already existed. Watch out for this behaviour - it will delete any file located at address!
/// \param address The location of the Unix socket to bind to.
/// \param nonblock (optional) Whether accept() calls will be nonblocking. Default is false (blocking).
DDV::ServerSocket::ServerSocket(std::string address, bool nonblock){
unlink(address.c_str());
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0){
#if DEBUG >= 1
fprintf(stderr, "Could not create socket! Error: %s\n", strerror(errno));
#endif
return;
}
if (nonblock){
int flags = fcntl(sock, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sock, F_SETFL, flags);
}
sockaddr_un addr;
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, address.c_str(), address.size()+1);
int ret = bind(sock, (sockaddr*)&addr, sizeof(addr));
if (ret == 0){
ret = listen(sock, 100);//start listening, backlog of 100 allowed
if (ret == 0){
return;
}else{
#if DEBUG >= 1
fprintf(stderr, "Listen failed! Error: %s\n", strerror(errno));
#endif
close();
return;
}
}else{
#if DEBUG >= 1
fprintf(stderr, "Binding failed! Error: %s\n", strerror(errno));
#endif
close();
return;
}
}//DDV::ServerSocket Unix Constructor
/// Accept any waiting connections. If the DDV::ServerSocket is blocking, this function will block until there is an incoming connection.
/// If the DDV::ServerSocket is nonblocking, it might return a DDV::Socket that is not connected, so check for this.
/// \param nonblock (optional) Whether the newly connected socket should be nonblocking. Default is false (blocking).
/// \returns A DDV::Socket, which may or may not be connected, depending on settings and circumstances.
DDV::Socket DDV::ServerSocket::accept(bool nonblock){
if (sock < 0){return DDV::Socket(-1);}
int r = ::accept(sock, 0, 0);
//set the socket to be nonblocking, if requested.
//we could do this through accept4 with a flag, but that call is non-standard...
if ((r >= 0) && nonblock){
int flags = fcntl(r, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(r, F_SETFL, flags);
}
if (r < 0){
if (errno != EWOULDBLOCK && errno != EAGAIN){close();}
}
return DDV::Socket(r);
}
/// Close connection. The internal socket is closed and then set to -1.
void DDV::ServerSocket::close(){
::close(sock);
sock = -1;
}//DDV::ServerSocket::close
/// Returns the connected-state for this socket.
/// Note that this function might be slightly behind the real situation.
/// The connection status is updated after every accept attempt, when errors occur
/// and when the socket is closed manually.
/// \returns True if socket is connected, false otherwise.
bool DDV::ServerSocket::connected(){
return (sock >= 0);
}//DDV::ServerSocket::connected
/// Returns internal socket number.
int DDV::ServerSocket::getSocket(){return sock;}