Inital
This commit is contained in:
		
						commit
						da117507f2
					
				
					 5 changed files with 914 additions and 0 deletions
				
			
		
							
								
								
									
										355
									
								
								Socket.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										355
									
								
								Socket.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,355 @@ | ||||||
|  | 
 | ||||||
|  | #include "Socket.h" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   #include <sys/types.h>       // For data types
 | ||||||
|  |   #include <sys/socket.h>      // For socket(), connect(), send(), and recv()
 | ||||||
|  |   #include <netdb.h>           // For gethostbyname()
 | ||||||
|  |   #include <arpa/inet.h>       // For inet_addr()
 | ||||||
|  |   #include <unistd.h>          // For close()
 | ||||||
|  |   #include <netinet/in.h>      // For sockaddr_in
 | ||||||
|  |   #include <netinet/tcp.h>     // TCP_KEEPCNT
 | ||||||
|  |   #include <fcntl.h> | ||||||
|  |   typedef void raw_type;       // Type used for raw data on this platform
 | ||||||
|  | 
 | ||||||
|  | #include <errno.h>             // For errno
 | ||||||
|  | 
 | ||||||
|  | using namespace std; | ||||||
|  | 
 | ||||||
|  | // SocketException Code
 | ||||||
|  | 
 | ||||||
|  | SocketException::SocketException(const string &message, bool inclSysMsg) | ||||||
|  |    : userMessage(message) { | ||||||
|  |   if (inclSysMsg) { | ||||||
|  |     userMessage.append(": "); | ||||||
|  |     userMessage.append(strerror(errno)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SocketException::~SocketException()  { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const char *SocketException::what(){ | ||||||
|  |   return userMessage.c_str(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Function to fill in address structure given an address and port
 | ||||||
|  | static void fillAddr(const string &address, unsigned short port,  | ||||||
|  |                      sockaddr_in &addr) { | ||||||
|  |   memset(&addr, 0, sizeof(addr));  // Zero out address structure
 | ||||||
|  |   addr.sin_family = AF_INET;       // Internet address
 | ||||||
|  | 
 | ||||||
|  |   hostent *host;  // Resolve name
 | ||||||
|  |   if ((host = gethostbyname(address.c_str())) == NULL) { | ||||||
|  |     // strerror() will not work for gethostbyname() and hstrerror() 
 | ||||||
|  |     // is supposedly obsolete
 | ||||||
|  |     throw SocketException("Failed to resolve name (gethostbyname())"); | ||||||
|  |   } | ||||||
|  |   addr.sin_addr.s_addr = *((unsigned long *) host->h_addr_list[0]); | ||||||
|  | 
 | ||||||
|  |   addr.sin_port = htons(port);     // Assign port in network byte order
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Socket Code
 | ||||||
|  | 
 | ||||||
|  | Socket::Socket(int type, int protocol)  { | ||||||
|  | 
 | ||||||
|  |   // Make a new socket
 | ||||||
|  |   if ((sockDesc = socket(PF_INET, type, protocol)) < 0) { | ||||||
|  |     throw SocketException("Socket creation failed (socket())", true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Socket::Socket(int sockDesc) { | ||||||
|  |   this->sockDesc = sockDesc; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Socket::~Socket()  | ||||||
|  | { | ||||||
|  |   close(sockDesc); | ||||||
|  |   sockDesc = -1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | string Socket::getLocalAddress()  { | ||||||
|  |   sockaddr_in addr; | ||||||
|  |   unsigned int addr_len = sizeof(addr); | ||||||
|  | 
 | ||||||
|  |   if (getsockname(sockDesc, (sockaddr *) &addr, (socklen_t *) &addr_len) < 0) { | ||||||
|  |     throw SocketException("Fetch of local address failed (getsockname())", true); | ||||||
|  |   } | ||||||
|  |   return inet_ntoa(addr.sin_addr); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | unsigned short Socket::getLocalPort()  { | ||||||
|  |   sockaddr_in addr; | ||||||
|  |   unsigned int addr_len = sizeof(addr); | ||||||
|  | 
 | ||||||
|  |   if (getsockname(sockDesc, (sockaddr *) &addr, (socklen_t *) &addr_len) < 0) { | ||||||
|  |     throw SocketException("Fetch of local port failed (getsockname())", true); | ||||||
|  |   } | ||||||
|  |   return ntohs(addr.sin_port); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Socket::setLocalPort(unsigned short localPort)  { | ||||||
|  |   // Bind the socket to its port
 | ||||||
|  |   sockaddr_in localAddr; | ||||||
|  |   memset(&localAddr, 0, sizeof(localAddr)); | ||||||
|  |   localAddr.sin_family = AF_INET; | ||||||
|  |   localAddr.sin_addr.s_addr = htonl(INADDR_ANY); | ||||||
|  |   localAddr.sin_port = htons(localPort); | ||||||
|  | 
 | ||||||
|  |   if (bind(sockDesc, (sockaddr *) &localAddr, sizeof(sockaddr_in)) < 0) { | ||||||
|  |     throw SocketException("Set of local port failed (bind())", true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Socket::setLocalAddressAndPort(const string &localAddress, | ||||||
|  |     unsigned short localPort)  { | ||||||
|  |   // Get the address of the requested host
 | ||||||
|  |   sockaddr_in localAddr; | ||||||
|  |   fillAddr(localAddress, localPort, localAddr); | ||||||
|  | 
 | ||||||
|  |   if (bind(sockDesc, (sockaddr *) &localAddr, sizeof(sockaddr_in)) < 0) { | ||||||
|  |     throw SocketException("Set of local address and port failed (bind())", true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Socket::setKeepalive() | ||||||
|  | { | ||||||
|  |       int optval = 1; | ||||||
|  |       setsockopt(sockDesc, SOL_SOCKET, SO_KEEPALIVE,&optval, sizeof(optval)); | ||||||
|  |        | ||||||
|  |       optval = 2; | ||||||
|  |       setsockopt(sockDesc, SOL_SOCKET, TCP_KEEPCNT, &optval, sizeof(optval)); | ||||||
|  |        | ||||||
|  |       optval = 10; | ||||||
|  |       setsockopt(sockDesc, SOL_SOCKET, TCP_KEEPIDLE, &optval, sizeof(optval)); | ||||||
|  |        | ||||||
|  |       optval = 5; | ||||||
|  |       setsockopt(sockDesc, SOL_SOCKET, TCP_KEEPINTVL, &optval, sizeof(optval)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Socket::cleanUp()  { | ||||||
|  |      | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | unsigned short Socket::resolveService(const string &service, | ||||||
|  |                                       const string &protocol) { | ||||||
|  |   struct servent *serv;        /* Structure containing service information */ | ||||||
|  | 
 | ||||||
|  |   if ((serv = getservbyname(service.c_str(), protocol.c_str())) == NULL) | ||||||
|  |     return atoi(service.c_str());  /* Service is port number */ | ||||||
|  |   else  | ||||||
|  |     return ntohs(serv->s_port);    /* Found port (network byte order) by name */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CommunicatingSocket Code
 | ||||||
|  | 
 | ||||||
|  | CommunicatingSocket::CommunicatingSocket(int type, int protocol)   | ||||||
|  |      : Socket(type, protocol) { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | CommunicatingSocket::CommunicatingSocket(int newConnSD) : Socket(newConnSD) { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CommunicatingSocket::connect(const string &foreignAddress, | ||||||
|  |     unsigned short foreignPort)  { | ||||||
|  |   // Get the address of the requested host
 | ||||||
|  |   sockaddr_in destAddr; | ||||||
|  |   fillAddr(foreignAddress, foreignPort, destAddr); | ||||||
|  | 
 | ||||||
|  |   // Try to connect to the given port
 | ||||||
|  |   if (::connect(sockDesc, (sockaddr *) &destAddr, sizeof(destAddr)) < 0) { | ||||||
|  |     throw SocketException("Connect failed (connect())", true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CommunicatingSocket::send(const void *buffer, int bufferLen)  | ||||||
|  |      { | ||||||
|  |   if (::send(sockDesc, (raw_type *) buffer, bufferLen, 0) < 0) { | ||||||
|  |     throw SocketException("Send failed (send())", true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int CommunicatingSocket::recv(void *buffer, int bufferLen)  | ||||||
|  | { | ||||||
|  |   int rtn; | ||||||
|  |   if ((rtn = ::recv(sockDesc, (raw_type *) buffer, bufferLen, MSG_DONTWAIT)) < 0 )  | ||||||
|  |   { | ||||||
|  |     if(errno == EWOULDBLOCK || errno == EAGAIN) return -1; | ||||||
|  |     else throw SocketException("Received failed (recv())", true); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return rtn; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | string CommunicatingSocket::getForeignAddress()  | ||||||
|  |      { | ||||||
|  |   sockaddr_in addr; | ||||||
|  |   unsigned int addr_len = sizeof(addr); | ||||||
|  | 
 | ||||||
|  |   if (getpeername(sockDesc, (sockaddr *) &addr,(socklen_t *) &addr_len) < 0) { | ||||||
|  |     throw SocketException("Fetch of foreign address failed (getpeername())", true); | ||||||
|  |   } | ||||||
|  |   return inet_ntoa(addr.sin_addr); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | unsigned short CommunicatingSocket::getForeignPort()  { | ||||||
|  |   sockaddr_in addr; | ||||||
|  |   unsigned int addr_len = sizeof(addr); | ||||||
|  | 
 | ||||||
|  |   if (getpeername(sockDesc, (sockaddr *) &addr, (socklen_t *) &addr_len) < 0) { | ||||||
|  |     throw SocketException("Fetch of foreign port failed (getpeername())", true); | ||||||
|  |   } | ||||||
|  |   return ntohs(addr.sin_port); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TCPSocket Code
 | ||||||
|  | 
 | ||||||
|  | TCPSocket::TCPSocket()  | ||||||
|  |      : CommunicatingSocket(SOCK_STREAM,  | ||||||
|  |     IPPROTO_TCP) { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | TCPSocket::TCPSocket(const string &foreignAddress, unsigned short foreignPort, bool keepalive) | ||||||
|  |      : CommunicatingSocket(SOCK_STREAM, IPPROTO_TCP) { | ||||||
|  |   connect(foreignAddress, foreignPort); | ||||||
|  |   if(keepalive) setKeepalive(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | TCPSocket::TCPSocket(int newConnSD) : CommunicatingSocket(newConnSD) { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TCPServerSocket Code
 | ||||||
|  | 
 | ||||||
|  | TCPServerSocket::TCPServerSocket(unsigned short localPort, int queueLen, bool keepaliveIN)  | ||||||
|  |      : Socket(SOCK_STREAM, IPPROTO_TCP)  | ||||||
|  | { | ||||||
|  |   keepalive = keepaliveIN; | ||||||
|  |   setLocalPort(localPort); | ||||||
|  |   setListen(queueLen); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | TCPServerSocket::TCPServerSocket(const string &localAddress, unsigned short localPort, int queueLen, bool keepaliveIN)  | ||||||
|  |      : Socket(SOCK_STREAM, IPPROTO_TCP)  | ||||||
|  | { | ||||||
|  |   keepalive = keepaliveIN; | ||||||
|  |   setLocalAddressAndPort(localAddress, localPort); | ||||||
|  |   setListen(queueLen); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | TCPSocket* TCPServerSocket::accept()   | ||||||
|  | { | ||||||
|  |   int newConnSD; | ||||||
|  |   if ((newConnSD = ::accept(sockDesc, NULL, 0)) < 0  ) | ||||||
|  |   { | ||||||
|  |     throw SocketException("Accept failed (accept())", true); | ||||||
|  |   } | ||||||
|  |   if(keepalive) setKeepalive(); | ||||||
|  |   return new TCPSocket(newConnSD); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void TCPServerSocket::setListen(int queueLen)  { | ||||||
|  |   if (listen(sockDesc, queueLen) < 0) { | ||||||
|  |     throw SocketException("Set listening socket failed (listen())", true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UDPSocket Code
 | ||||||
|  | 
 | ||||||
|  | UDPSocket::UDPSocket()  : CommunicatingSocket(SOCK_DGRAM, | ||||||
|  |     IPPROTO_UDP) { | ||||||
|  |   setBroadcast(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | UDPSocket::UDPSocket(unsigned short localPort)   :  | ||||||
|  |     CommunicatingSocket(SOCK_DGRAM, IPPROTO_UDP) { | ||||||
|  |   setLocalPort(localPort); | ||||||
|  |   setBroadcast(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | UDPSocket::UDPSocket(const string &localAddress, unsigned short localPort)  | ||||||
|  |       : CommunicatingSocket(SOCK_DGRAM, IPPROTO_UDP) { | ||||||
|  |   setLocalAddressAndPort(localAddress, localPort); | ||||||
|  |   setBroadcast(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void UDPSocket::setBroadcast() { | ||||||
|  |   // If this fails, we'll hear about it when we try to send.  This will allow 
 | ||||||
|  |   // system that cannot broadcast to continue if they don't plan to broadcast
 | ||||||
|  |   int broadcastPermission = 1; | ||||||
|  |   setsockopt(sockDesc, SOL_SOCKET, SO_BROADCAST,  | ||||||
|  |              (raw_type *) &broadcastPermission, sizeof(broadcastPermission)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void UDPSocket::disconnect()  { | ||||||
|  |   sockaddr_in nullAddr; | ||||||
|  |   memset(&nullAddr, 0, sizeof(nullAddr)); | ||||||
|  |   nullAddr.sin_family = AF_UNSPEC; | ||||||
|  | 
 | ||||||
|  |   // Try to disconnect
 | ||||||
|  |   if (::connect(sockDesc, (sockaddr *) &nullAddr, sizeof(nullAddr)) < 0) { | ||||||
|  |     if (errno != EAFNOSUPPORT) { | ||||||
|  |       throw SocketException("Disconnect failed (connect())", true); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void UDPSocket::sendTo(const void *buffer, int bufferLen,  | ||||||
|  |     const string &foreignAddress, unsigned short foreignPort)  | ||||||
|  |      { | ||||||
|  |   sockaddr_in destAddr; | ||||||
|  |   fillAddr(foreignAddress, foreignPort, destAddr); | ||||||
|  | 
 | ||||||
|  |   // Write out the whole buffer as a single message.
 | ||||||
|  |   if (sendto(sockDesc, (raw_type *) buffer, bufferLen, 0, | ||||||
|  |              (sockaddr *) &destAddr, sizeof(destAddr)) != bufferLen) { | ||||||
|  |     throw SocketException("Send failed (sendto())", true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int UDPSocket::recvFrom(void *buffer, int bufferLen, string &sourceAddress, | ||||||
|  |     unsigned short &sourcePort)  { | ||||||
|  |   sockaddr_in clntAddr; | ||||||
|  |   socklen_t addrLen = sizeof(clntAddr); | ||||||
|  |   int rtn; | ||||||
|  |   if ((rtn = recvfrom(sockDesc, (raw_type *) buffer, bufferLen, MSG_DONTWAIT, (sockaddr *) &clntAddr, (socklen_t *) &addrLen)) < 0)  | ||||||
|  |   { | ||||||
|  |     throw SocketException("Receive failed (recvfrom())", true); | ||||||
|  |   } | ||||||
|  |   sourceAddress = inet_ntoa(clntAddr.sin_addr); | ||||||
|  |   sourcePort = ntohs(clntAddr.sin_port); | ||||||
|  | 
 | ||||||
|  |   return rtn; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void UDPSocket::setMulticastTTL(unsigned char multicastTTL)  { | ||||||
|  |   if (setsockopt(sockDesc, IPPROTO_IP, IP_MULTICAST_TTL,  | ||||||
|  |                  (raw_type *) &multicastTTL, sizeof(multicastTTL)) < 0) { | ||||||
|  |     throw SocketException("Multicast TTL set failed (setsockopt())", true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void UDPSocket::joinGroup(const string &multicastGroup)  { | ||||||
|  |   struct ip_mreq multicastRequest; | ||||||
|  | 
 | ||||||
|  |   multicastRequest.imr_multiaddr.s_addr = inet_addr(multicastGroup.c_str()); | ||||||
|  |   multicastRequest.imr_interface.s_addr = htonl(INADDR_ANY); | ||||||
|  |   if (setsockopt(sockDesc, IPPROTO_IP, IP_ADD_MEMBERSHIP,  | ||||||
|  |                  (raw_type *) &multicastRequest,  | ||||||
|  |                  sizeof(multicastRequest)) < 0) { | ||||||
|  |     throw SocketException("Multicast group join failed (setsockopt())", true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void UDPSocket::leaveGroup(const string &multicastGroup)  { | ||||||
|  |   struct ip_mreq multicastRequest; | ||||||
|  | 
 | ||||||
|  |   multicastRequest.imr_multiaddr.s_addr = inet_addr(multicastGroup.c_str()); | ||||||
|  |   multicastRequest.imr_interface.s_addr = htonl(INADDR_ANY); | ||||||
|  |   if (setsockopt(sockDesc, IPPROTO_IP, IP_DROP_MEMBERSHIP,  | ||||||
|  |                  (raw_type *) &multicastRequest,  | ||||||
|  |                  sizeof(multicastRequest)) < 0) { | ||||||
|  |     throw SocketException("Multicast group leave failed (setsockopt())", true); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										324
									
								
								Socket.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								Socket.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,324 @@ | ||||||
|  | #ifndef __SOCKET_INCLUDED__ | ||||||
|  | #define __SOCKET_INCLUDED__ | ||||||
|  | 
 | ||||||
|  | #include <string>            // For std::string | ||||||
|  | #include <exception>         // For exception class | ||||||
|  | #include <errno.h> | ||||||
|  | #include <string.h> | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  *   Signals a problem with the execution of a socket call. | ||||||
|  |  */ | ||||||
|  | class SocketException : public std::exception { | ||||||
|  | public: | ||||||
|  |   /**
 | ||||||
|  |    *   Construct a SocketException with a explanatory message. | ||||||
|  |    *   @param message explanatory message | ||||||
|  |    *   @param incSysMsg true if system message (from strerror(errno)) | ||||||
|  |    *   should be postfixed to the user provided message | ||||||
|  |    */ | ||||||
|  |   SocketException(const std::string &message, bool inclSysMsg = false) ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Provided just to guarantee that no exceptions are thrown. | ||||||
|  |    */ | ||||||
|  |   ~SocketException() ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Get the exception message | ||||||
|  |    *   @return exception message | ||||||
|  |    */ | ||||||
|  |   const char *what(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |   std::string userMessage;  // Exception message
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  *   Base class representing basic communication endpoint | ||||||
|  |  */ | ||||||
|  | class Socket { | ||||||
|  | public: | ||||||
|  |   /**
 | ||||||
|  |    *   Close and deallocate this socket | ||||||
|  |    */ | ||||||
|  |   ~Socket(); | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Get the local address | ||||||
|  |    *   @return local address of socket | ||||||
|  |    *   @exception SocketException thrown if fetch fails | ||||||
|  |    */ | ||||||
|  |   std::string getLocalAddress(); | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Get the local port | ||||||
|  |    *   @return local port of socket | ||||||
|  |    *   @exception SocketException thrown if fetch fails | ||||||
|  |    */ | ||||||
|  |   unsigned short getLocalPort() ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Set the local port to the specified port and the local address | ||||||
|  |    *   to any interface | ||||||
|  |    *   @param localPort local port | ||||||
|  |    *   @exception SocketException thrown if setting local port fails | ||||||
|  |    */ | ||||||
|  |   void setLocalPort(unsigned short localPort) ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Set the local port to the specified port and the local address | ||||||
|  |    *   to the specified address.  If you omit the port, a random port  | ||||||
|  |    *   will be selected. | ||||||
|  |    *   @param localAddress local address | ||||||
|  |    *   @param localPort local port | ||||||
|  |    *   @exception SocketException thrown if setting local port or address fails | ||||||
|  |    */ | ||||||
|  |   void setLocalAddressAndPort(const std::string &localAddress,  | ||||||
|  |     unsigned short localPort = 0) ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   If WinSock, unload the WinSock DLLs; otherwise do nothing.  We ignore | ||||||
|  |    *   this in our sample client code but include it in the library for | ||||||
|  |    *   completeness.  If you are running on Windows and you are concerned | ||||||
|  |    *   about DLL resource consumption, call this after you are done with all | ||||||
|  |    *   Socket instances.  If you execute this on Windows while some instance of | ||||||
|  |    *   Socket exists, you are toast.  For portability of client code, this is  | ||||||
|  |    *   an empty function on non-Windows platforms so you can always include it. | ||||||
|  |    *   @param buffer buffer to receive the data | ||||||
|  |    *   @param bufferLen maximum number of bytes to read into buffer | ||||||
|  |    *   @return number of bytes read, 0 for EOF, and -1 for error | ||||||
|  |    *   @exception SocketException thrown WinSock clean up fails | ||||||
|  |    */ | ||||||
|  |   static void cleanUp() ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Resolve the specified service for the specified protocol to the | ||||||
|  |    *   corresponding port number in host byte order | ||||||
|  |    *   @param service service to resolve (e.g., "http") | ||||||
|  |    *   @param protocol protocol of service to resolve.  Default is "tcp". | ||||||
|  |    */ | ||||||
|  |   static unsigned short resolveService(const std::string &service, | ||||||
|  |                                        const std::string &protocol = "tcp"); | ||||||
|  |    | ||||||
|  |   void setKeepalive(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |   // Prevent the user from trying to use value semantics on this object
 | ||||||
|  |   Socket(const Socket &sock); | ||||||
|  |   void operator=(const Socket &sock); | ||||||
|  | 
 | ||||||
|  | protected: | ||||||
|  |   int sockDesc;              // Socket descriptor
 | ||||||
|  |   Socket(int type, int protocol) ; | ||||||
|  |   Socket(int sockDesc); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  *   Socket which is able to connect, send, and receive | ||||||
|  |  */ | ||||||
|  | class CommunicatingSocket : public Socket { | ||||||
|  | public: | ||||||
|  |   /**
 | ||||||
|  |    *   Establish a socket connection with the given foreign | ||||||
|  |    *   address and port | ||||||
|  |    *   @param foreignAddress foreign address (IP address or name) | ||||||
|  |    *   @param foreignPort foreign port | ||||||
|  |    *   @exception SocketException thrown if unable to establish connection | ||||||
|  |    */ | ||||||
|  |   void connect(const std::string &foreignAddress, unsigned short foreignPort) | ||||||
|  |     ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Write the given buffer to this socket.  Call connect() before | ||||||
|  |    *   calling send() | ||||||
|  |    *   @param buffer buffer to be written | ||||||
|  |    *   @param bufferLen number of bytes from buffer to be written | ||||||
|  |    *   @exception SocketException thrown if unable to send data | ||||||
|  |    */ | ||||||
|  |   void send(const void *buffer, int bufferLen) ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Read into the given buffer up to bufferLen bytes data from this | ||||||
|  |    *   socket.  Call connect() before calling recv() | ||||||
|  |    *   @param buffer buffer to receive the data | ||||||
|  |    *   @param bufferLen maximum number of bytes to read into buffer | ||||||
|  |    *   @return number of bytes read, 0 for EOF, and -1 for error | ||||||
|  |    *   @exception SocketException thrown if unable to receive data | ||||||
|  |    */ | ||||||
|  |   int recv(void *buffer, int bufferLen) ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Get the foreign address.  Call connect() before calling recv() | ||||||
|  |    *   @return foreign address | ||||||
|  |    *   @exception SocketException thrown if unable to fetch foreign address | ||||||
|  |    */ | ||||||
|  |   std::string getForeignAddress() ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Get the foreign port.  Call connect() before calling recv() | ||||||
|  |    *   @return foreign port | ||||||
|  |    *   @exception SocketException thrown if unable to fetch foreign port | ||||||
|  |    */ | ||||||
|  |   unsigned short getForeignPort() ; | ||||||
|  | 
 | ||||||
|  | protected: | ||||||
|  |   CommunicatingSocket(int type, int protocol) ; | ||||||
|  |   CommunicatingSocket(int newConnSD); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  *   TCP socket for communication with other TCP sockets | ||||||
|  |  */ | ||||||
|  | class TCPSocket : public CommunicatingSocket { | ||||||
|  | public: | ||||||
|  |   /**
 | ||||||
|  |    *   Construct a TCP socket with no connection | ||||||
|  |    *   @exception SocketException thrown if unable to create TCP socket | ||||||
|  |    */ | ||||||
|  |   TCPSocket() ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Construct a TCP socket with a connection to the given foreign address | ||||||
|  |    *   and port | ||||||
|  |    *   @param foreignAddress foreign address (IP address or name) | ||||||
|  |    *   @param foreignPort foreign port | ||||||
|  |    *   @exception SocketException thrown if unable to create TCP socket | ||||||
|  |    */ | ||||||
|  |   TCPSocket(const std::string &foreignAddress, unsigned short foreignPort, bool keepalive = false)  | ||||||
|  |       ; | ||||||
|  | 
 | ||||||
|  |   bool isOpen(); | ||||||
|  |      | ||||||
|  | private: | ||||||
|  |   // Access for TCPServerSocket::accept() connection creation
 | ||||||
|  |   friend class TCPServerSocket; | ||||||
|  |   TCPSocket(int newConnSD); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  *   TCP socket class for servers | ||||||
|  |  */ | ||||||
|  | class TCPServerSocket : public Socket { | ||||||
|  | public: | ||||||
|  |   /**
 | ||||||
|  |    *   Construct a TCP socket for use with a server, accepting connections | ||||||
|  |    *   on the specified port on any interface | ||||||
|  |    *   @param localPort local port of server socket, a value of zero will | ||||||
|  |    *                   give a system-assigned unused port | ||||||
|  |    *   @param queueLen maximum queue length for outstanding  | ||||||
|  |    *                   connection requests (default 5) | ||||||
|  |    *   @exception SocketException thrown if unable to create TCP server socket | ||||||
|  |    */ | ||||||
|  |   TCPServerSocket(unsigned short localPort, int queueLen = 5, bool keepaliveIN = false); | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Construct a TCP socket for use with a server, accepting connections | ||||||
|  |    *   on the specified port on the interface specified by the given address | ||||||
|  |    *   @param localAddress local interface (address) of server socket | ||||||
|  |    *   @param localPort local port of server socket | ||||||
|  |    *   @param queueLen maximum queue length for outstanding  | ||||||
|  |    *                   connection requests (default 5) | ||||||
|  |    *   @exception SocketException thrown if unable to create TCP server socket | ||||||
|  |    */ | ||||||
|  |   TCPServerSocket(const std::string &localAddress, unsigned short localPort, | ||||||
|  |       int queueLen = 5, bool keepaliveIN = false); | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Blocks until a new connection is established on this socket or error | ||||||
|  |    *   @return new connection socket | ||||||
|  |    *   @exception SocketException thrown if attempt to accept a new connection fails | ||||||
|  |    */ | ||||||
|  |   TCPSocket *accept() ; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |   void setListen(int queueLen) ; | ||||||
|  |   bool keepalive; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |   *   UDP socket class | ||||||
|  |   */ | ||||||
|  | class UDPSocket : public CommunicatingSocket { | ||||||
|  | public: | ||||||
|  |   /**
 | ||||||
|  |    *   Construct a UDP socket | ||||||
|  |    *   @exception SocketException thrown if unable to create UDP socket | ||||||
|  |    */ | ||||||
|  |   UDPSocket() ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Construct a UDP socket with the given local port | ||||||
|  |    *   @param localPort local port | ||||||
|  |    *   @exception SocketException thrown if unable to create UDP socket | ||||||
|  |    */ | ||||||
|  |   UDPSocket(unsigned short localPort) ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Construct a UDP socket with the given local port and address | ||||||
|  |    *   @param localAddress local address | ||||||
|  |    *   @param localPort local port | ||||||
|  |    *   @exception SocketException thrown if unable to create UDP socket | ||||||
|  |    */ | ||||||
|  |   UDPSocket(const std::string &localAddress, unsigned short localPort)  | ||||||
|  |       ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Unset foreign address and port | ||||||
|  |    *   @return true if disassociation is successful | ||||||
|  |    *   @exception SocketException thrown if unable to disconnect UDP socket | ||||||
|  |    */ | ||||||
|  |   void disconnect() ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Send the given buffer as a UDP datagram to the | ||||||
|  |    *   specified address/port | ||||||
|  |    *   @param buffer buffer to be written | ||||||
|  |    *   @param bufferLen number of bytes to write | ||||||
|  |    *   @param foreignAddress address (IP address or name) to send to | ||||||
|  |    *   @param foreignPort port number to send to | ||||||
|  |    *   @return true if send is successful | ||||||
|  |    *   @exception SocketException thrown if unable to send datagram | ||||||
|  |    */ | ||||||
|  |   void sendTo(const void *buffer, int bufferLen, const std::string &foreignAddress, | ||||||
|  |             unsigned short foreignPort) ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Read read up to bufferLen bytes data from this socket.  The given buffer | ||||||
|  |    *   is where the data will be placed | ||||||
|  |    *   @param buffer buffer to receive data | ||||||
|  |    *   @param bufferLen maximum number of bytes to receive | ||||||
|  |    *   @param sourceAddress address of datagram source | ||||||
|  |    *   @param sourcePort port of data source | ||||||
|  |    *   @return number of bytes received and -1 for error | ||||||
|  |    *   @exception SocketException thrown if unable to receive datagram | ||||||
|  |    */ | ||||||
|  |   int recvFrom(void *buffer, int bufferLen, std::string &sourceAddress,  | ||||||
|  |                unsigned short &sourcePort) ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Set the multicast TTL | ||||||
|  |    *   @param multicastTTL multicast TTL | ||||||
|  |    *   @exception SocketException thrown if unable to set TTL | ||||||
|  |    */ | ||||||
|  |   void setMulticastTTL(unsigned char multicastTTL) ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Join the specified multicast group | ||||||
|  |    *   @param multicastGroup multicast group address to join | ||||||
|  |    *   @exception SocketException thrown if unable to join group | ||||||
|  |    */ | ||||||
|  |   void joinGroup(const std::string &multicastGroup) ; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    *   Leave the specified multicast group | ||||||
|  |    *   @param multicastGroup multicast group address to leave | ||||||
|  |    *   @exception SocketException thrown if unable to leave group | ||||||
|  |    */ | ||||||
|  |   void leaveGroup(const std::string &multicastGroup) ; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |   void setBroadcast(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
							
								
								
									
										150
									
								
								main.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								main.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,150 @@ | ||||||
|  | #include <iostream> | ||||||
|  | #include <string.h> | ||||||
|  | #include <sys/types.h> | ||||||
|  | #include <sys/socket.h> | ||||||
|  | #include <sys/un.h> | ||||||
|  | #include <string> | ||||||
|  | #include <cstdlib> | ||||||
|  | #include <vector> | ||||||
|  | #include <thread> | ||||||
|  | 
 | ||||||
|  | #include "serial_io.h" | ||||||
|  | #include "Socket.h" | ||||||
|  | 
 | ||||||
|  | bool stop = false; | ||||||
|  | 
 | ||||||
|  | void intHandler(int dummy)  | ||||||
|  | { | ||||||
|  |     stop = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void printUsage() | ||||||
|  | { | ||||||
|  |     std::cout<<"usage mulitplexer [option]-\n\
 | ||||||
|  |                 Available options:\n\ | ||||||
|  |                 -h, --help       print this help\n\ | ||||||
|  |                 -p, --serialport serial port device to use\n\ | ||||||
|  |                 -P, --port       tcp port to use\n\ | ||||||
|  |                 -b, --baud       set baud rate with termios id\n"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class Config | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |     std::string portFileName = "/dev/ttyUSB0"; | ||||||
|  |     unsigned short port = 6856; | ||||||
|  |     int baud = 0000017; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static int parseCmdArgs(int argc, char** argv, Config *config) | ||||||
|  | { | ||||||
|  |     for (int i = 1; i < argc; i++) | ||||||
|  |     { | ||||||
|  |         if (std::string(argv[i]) == "--help" || std::string(argv[i]) == "-h") | ||||||
|  |         { | ||||||
|  |             printUsage(); | ||||||
|  |             return -1; | ||||||
|  |         } | ||||||
|  |         else if (std::string(argv[i]) == "--serialport" || std::string(argv[i]) == "-p") | ||||||
|  |         { | ||||||
|  |             if(argc > i+1) config->portFileName = argv[i+1]; | ||||||
|  |             else return -1; | ||||||
|  |         } | ||||||
|  |         else if (std::string(argv[i]) == "--port" || std::string(argv[i]) == "-P") | ||||||
|  |         { | ||||||
|  |             if(argc > i+1) config->portFileName = atoi(argv[i+1]); | ||||||
|  |             else return -1; | ||||||
|  |         } | ||||||
|  |          else if (std::string(argv[i]) == "--baud" || std::string(argv[i]) == "-b") | ||||||
|  |         { | ||||||
|  |             if(argc > i+1) config->baud = atoi(argv[i+1]); | ||||||
|  |             else return -1; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void acceptThreadFunction( TCPServerSocket* servSock,  std::vector<TCPSocket*>* clientSockets, volatile bool* stop ) | ||||||
|  | { | ||||||
|  |     while(!(*stop))  | ||||||
|  |     {   // Run forever
 | ||||||
|  |         clientSockets->push_back(servSock->accept());       // Wait for a client to connect
 | ||||||
|  |         clientSockets->back()->send("UVOS serial port multiplexer v0.1\n", sizeof("uvos serial port multiplexer v0.1\n")-1); | ||||||
|  |         std::cout<<"got client\n"; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int main(int argc, char* argv[]) | ||||||
|  | { | ||||||
|  |     Config config; | ||||||
|  |      | ||||||
|  |     if(parseCmdArgs(argc, argv, &config) != 0) return -1; | ||||||
|  |      | ||||||
|  |     std::cout<<"UVOS serial mulitplexer v0.1\n"; | ||||||
|  |      | ||||||
|  |     int serial = serialport_init(config.portFileName.c_str(), config.baud); | ||||||
|  | 
 | ||||||
|  |     std::vector<TCPSocket*> clientSockets; | ||||||
|  |      | ||||||
|  |     std::cout<<"opening TCP socet on port "<<config.port<<'\n'; | ||||||
|  |     TCPServerSocket servSock(config.port, 5, true);     // Server Socket object
 | ||||||
|  |      | ||||||
|  |     volatile bool threadStopper = false; | ||||||
|  |     std::thread acceptThread(acceptThreadFunction, &servSock, &clientSockets,  &threadStopper); | ||||||
|  |      | ||||||
|  |     char buffer[256]; | ||||||
|  |      | ||||||
|  |     std::cout<<"starting loop\n"; | ||||||
|  |      | ||||||
|  |     while(!stop) | ||||||
|  |     { | ||||||
|  |         int readlen = sRead(serial, buffer, 256); | ||||||
|  |         //std::cout<<clientSockets.size()<<std::endl;
 | ||||||
|  |         for(unsigned int i = 0; i < clientSockets.size(); i++) | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  | 
 | ||||||
|  |                 if(readlen > 0)  | ||||||
|  |                 { | ||||||
|  |                     std::cout<<"sending: "; | ||||||
|  |                     for( int j = 0; j < readlen; j++  )std::cout<<buffer[j]; | ||||||
|  |                     clientSockets[i]->send(buffer, readlen); | ||||||
|  |                     std::cout<<std::endl; | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |                 char inBuffer[256]; | ||||||
|  |                 int reclen = clientSockets[i]->recv(inBuffer, 256); | ||||||
|  |                 if( reclen > 0 )  | ||||||
|  |                 { | ||||||
|  |                     std::cout<<"rec "<<reclen<<'\n'; | ||||||
|  |                     for( int j = 0; j < reclen; j++  )std::cout<<inBuffer[j]; | ||||||
|  |                     sWrite(serial, inBuffer, reclen); | ||||||
|  |                 } | ||||||
|  |                 else if(reclen == 0) | ||||||
|  |                 { | ||||||
|  |                     std::cout<<"disconnect client "<<i<<'\n'; | ||||||
|  |                     clientSockets[i]->cleanUp(); | ||||||
|  |                     clientSockets.erase(clientSockets.begin()+i); | ||||||
|  |                     i--; | ||||||
|  |                     if(i < 0) i=0; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             catch(SocketException e) | ||||||
|  |             { | ||||||
|  |                 std::cout<<e.what()<<std::endl; | ||||||
|  |                 clientSockets[i]->cleanUp(); | ||||||
|  |                 clientSockets.erase(clientSockets.begin()+i); | ||||||
|  |                 i--; | ||||||
|  |                 if(i < 0) i=0; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         std::this_thread::sleep_for(std::chrono::milliseconds(20)); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     threadStopper = true; | ||||||
|  |     acceptThread.join(); | ||||||
|  |     for(unsigned int i = 0; i < clientSockets.size(); i++) clientSockets[i]->cleanUp(); | ||||||
|  |     servSock.cleanUp(); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										65
									
								
								serial_io.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								serial_io.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | ||||||
|  | #include "serial_io.h" | ||||||
|  | 
 | ||||||
|  | struct termios toptions; | ||||||
|  | 
 | ||||||
|  | void sWrite(int port, char string[], size_t length) | ||||||
|  | { | ||||||
|  |     if(port != -1) write(port, string, length); | ||||||
|  |     else for( int j = 0; j < length; j++  )std::cout<<string[j]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void sWrite(int port, const char string[], size_t length) | ||||||
|  | { | ||||||
|  |     if(port != -1) write(port, string, length); | ||||||
|  |     else for( int j = 0; j < length; j++  )std::cout<<string[j]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ssize_t sRead(int port, void *buf, size_t count) | ||||||
|  | { | ||||||
|  |     return (port != -1) ? read(port, buf, count) : 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int serialport_init(const char* device, int baud) | ||||||
|  | { | ||||||
|  |     int fd; | ||||||
|  |     fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY); | ||||||
|  |     if (fd == -1) | ||||||
|  |         { | ||||||
|  |         perror("init_serialport: Unable to open port "); | ||||||
|  |         return -1; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     if (tcgetattr(fd, &toptions) < 0) | ||||||
|  |         { | ||||||
|  |         perror("init_serialport: Couldn't get term attributes"); | ||||||
|  |         return -1; | ||||||
|  |          | ||||||
|  |     cfsetispeed(&toptions, baud); | ||||||
|  |     cfsetospeed(&toptions, baud); | ||||||
|  |     // 8N1
 | ||||||
|  |     toptions.c_cflag &= ~PARENB; | ||||||
|  |     toptions.c_cflag &= ~CSTOPB; | ||||||
|  |     toptions.c_cflag &= ~CSIZE; | ||||||
|  |     toptions.c_cflag |= CS8; | ||||||
|  |     // no flow control
 | ||||||
|  |     toptions.c_cflag &= ~(CRTSCTS | CLOCAL); | ||||||
|  | 
 | ||||||
|  |     toptions.c_cflag |= IGNPAR | CREAD;  // turn on READ & ignore ctrl lines
 | ||||||
|  |     toptions.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl
 | ||||||
|  | 
 | ||||||
|  |     toptions.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOE); // make raw
 | ||||||
|  |     toptions.c_oflag &= ~OPOST; // make raw
 | ||||||
|  | 
 | ||||||
|  |     // see: http://unixwiz.net/techtips/termios-vmin-vtime.html
 | ||||||
|  |     /*toptions.c_cc[VMIN]  = 0;
 | ||||||
|  |     toptions.c_cc[VTIME] = 20; | ||||||
|  |     fcntl(fd, F_SETFL, FNDELAY);*/ | ||||||
|  | 
 | ||||||
|  |     if( tcsetattr(fd, TCSANOW, &toptions) < 0) | ||||||
|  |         { | ||||||
|  |         perror("init_serialport: Couldn't set term attributes"); | ||||||
|  |         return -1; | ||||||
|  |         } | ||||||
|  |     return fd; | ||||||
|  | } | ||||||
|  | 
 | ||||||
							
								
								
									
										20
									
								
								serial_io.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								serial_io.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | #ifndef SERIAL_H | ||||||
|  | #define SERIAL_H | ||||||
|  | #include <termios.h> | ||||||
|  | #include <fcntl.h> | ||||||
|  | #include <unistd.h> | ||||||
|  | #include <iostream> | ||||||
|  | 
 | ||||||
|  | #define BAUDRATE B38400 | ||||||
|  | 
 | ||||||
|  | void sWrite(int port, char string[], size_t length); | ||||||
|  | 
 | ||||||
|  | void sWrite(int port, const char string[], size_t length); | ||||||
|  | 
 | ||||||
|  | ssize_t sRead(int port, void *buf, size_t count); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | int serialport_init(const char* device, int baud = BAUDRATE); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #endif // SERIAL_H
 | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 IMback
						IMback