Commits

spencercw  committed 7936985

#33 Add initial implementation of TCP client.

The client can connect to the server but no data is exchanged.

  • Participants
  • Parent commits 2a7d88c
  • Branches serial

Comments (0)

Files changed (12)

File gb_emulator/include/gb_emulator/gb_comms_serial.h

 	/**
 	 * \param gb The emulator context.
 	 * \param ioService The I/O service to use for communications.
-	 * \param host The hostname or IP (v4 or v6) address of the remote server.
-	 * \param port The port the server is listening on.
+	 * \param host The hostname or IP address (may be IPv4 or IPv6 where the operating system
+	 * supports it) of the remote server.
+	 * \param port The TCP port the server is listening on.
 	 */
 	GbCommsSerial(Gb &gb, boost::asio::io_service &ioService, const std::string &host,
 		uint16_t port);
 	// The networking interface if we are using it
 	boost::shared_ptr<gbnet::Networking> net_;
 
+	// Connects the networking callbacks below to net_
+	void connectNetworkSignals();
+
 	// Networking callbacks
-	boost::signals::scoped_connection listeningConnection_;
-	boost::signals::scoped_connection networkErrorConnection_;
-
-	void handleListening(const boost::asio::ip::tcp::endpoint &endpoint);
-	void handleNetworkError(gbnet::ErrorType type, const boost::system::error_code &ec);
+	boost::signals::scoped_connection onListeningConnection_;
+	boost::signals::scoped_connection onConnectedConnection_;
+	boost::signals::scoped_connection onNetworkErrorConnection_;
+	
+	void onListening(const boost::asio::ip::tcp::endpoint &endpoint);
+	void onConnected(const boost::asio::ip::tcp::endpoint &endpoint);
+	void onNetworkError(gbnet::ErrorType type, const boost::system::error_code &ec);
 
 	// Disabled operations
 	GbCommsSerial(const GbCommsSerial &);

File gb_emulator/src/gb_comms_serial.cpp

 
 #include <iostream>
 
+#include <gb_net/tcp_client.h>
 #include <gb_net/tcp_server.h>
 
 #include <gb_emulator/constants.hpp>
 transferStarted_(false),
 net_(new TcpServer(ioService, port))
 {
-	listeningConnection_ = net_->connectListening(
-		boost::bind(&GbCommsSerial::handleListening, this, _1));
-	networkErrorConnection_ = net_->connectNetworkError(
-		boost::bind(&GbCommsSerial::handleNetworkError, this, _1, _2));
+	connectNetworkSignals();
 }
 
 GbCommsSerial::GbCommsSerial(Gb &gb, io::io_service &ioService, const string &host, uint16_t port):
 gb_(gb),
-transferStarted_(false)
+transferStarted_(false),
+net_(new TcpClient(ioService, host, port))
 {
-	// TODO
-	(void) ioService;
-	(void) host;
-	(void) port;
+	connectNetworkSignals();
 }
 
 int GbCommsSerial::poll()
 	}
 }
 
-void GbCommsSerial::handleListening(const tcp::endpoint &endpoint)
+void GbCommsSerial::connectNetworkSignals()
+{
+	onListeningConnection_ = net_->onListening(boost::bind(&GbCommsSerial::onListening, this, _1));
+	onConnectedConnection_ = net_->onConnected(boost::bind(&GbCommsSerial::onConnected, this, _1));
+	onNetworkErrorConnection_ = net_->onNetworkError(
+		boost::bind(&GbCommsSerial::onNetworkError, this, _1, _2));
+}
+
+void GbCommsSerial::onListening(const tcp::endpoint &endpoint)
 {
 	clog << "Server listening on " << endpoint << "\n";
 }
 
-void GbCommsSerial::handleNetworkError(ErrorType type, const bs::error_code &ec)
+void GbCommsSerial::onConnected(const tcp::endpoint &endpoint)
+{
+	clog << "Connection established to " << endpoint << "\n";
+}
+
+void GbCommsSerial::onNetworkError(ErrorType type, const bs::error_code &ec)
 {
 	switch (type)
 	{
 		clog << "There was an error accepting client connections: ";
 		break;
 
+	case GBNET_ERR_CONNECT:
+		clog << "Failed to connect to the server: ";
+		break;
+
 	default:
 		clog << "An unknown networking error occurred: ";
 	}

File gb_net/gb_net.vcxproj

   <ItemGroup>
     <ClInclude Include="include\gb_net\defs.h" />
     <ClInclude Include="include\gb_net\networking.h" />
+    <ClInclude Include="include\gb_net\tcp_client.h" />
     <ClInclude Include="include\gb_net\tcp_server.h" />
     <ClInclude Include="include\gb_net\types.h" />
     <ClInclude Include="src\internal\networking.h" />
     <ClInclude Include="src\internal\server_session.h" />
     <ClInclude Include="src\internal\session.h" />
     <ClInclude Include="src\internal\single_tcp_server.h" />
+    <ClInclude Include="src\internal\tcp_client.h" />
     <ClInclude Include="src\internal\tcp_server.h" />
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="src\server_session.cpp" />
     <ClCompile Include="src\session.cpp" />
     <ClCompile Include="src\single_tcp_server.cpp" />
+    <ClCompile Include="src\tcp_client.cpp" />
     <ClCompile Include="src\tcp_server.cpp" />
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

File gb_net/gb_net.vcxproj.filters

     <ClInclude Include="src\internal\server_session.h">
       <Filter>Source Files\internal</Filter>
     </ClInclude>
+    <ClInclude Include="include\gb_net\tcp_client.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="src\internal\tcp_client.h">
+      <Filter>Source Files\internal</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="src\networking.cpp">
     <ClCompile Include="src\server_session.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="src\tcp_client.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

File gb_net/include/gb_net/networking.h

 	 * \return An object representing the connection to the signal. Be sure to \c disconnect() this
 	 * when finished with it.
 	 */
-	boost::signals::connection connectListening(
+	boost::signals::connection onListening(
+		boost::function<void (const boost::asio::ip::tcp::endpoint &endpoint)> callback);
+
+	//! Register to receive notifications when a connection is established.
+	/**
+	 * This is used by both clients and servers. For servers it will be called each time a client
+	 * connects.
+	 * \param endpoint The remote endpoint of the connection.
+	 */
+	boost::signals::connection onConnected(
 		boost::function<void (const boost::asio::ip::tcp::endpoint &endpoint)> callback);
 
 	//! Register to receive notifications when there is some network error.
 	 * \return An object representing the connection to the signal. Be sure to \c disconnect() this
 	 * when finished with it.
 	 */
-	boost::signals::connection connectNetworkError(
+	boost::signals::connection onNetworkError(
 		boost::function<void (ErrorType type, const boost::system::error_code &ec)> callback);
 
 protected:

File gb_net/include/gb_net/tcp_server.h

 	//! Constructor; starts the TCP server on the given port.
 	/**
 	 * This will bind both IPv4 and IPv6 where available. To be notified of any errors in starting
-	 * the server register with the Networking::connectNetworkError() signal before calling \c run()
-	 * or \c poll() on the I/O service.
+	 * the server register with the Networking::onNetworkError() signal before calling \c run() or
+	 * \c poll() on the I/O service.
 	 * \param ioService The I/O service to use for networking communications.
 	 * \param port The TCP port number to listen on.
 	 */

File gb_net/include/gb_net/types.h

 
 //! Types of networking errors that may occur.
 /**
- * These values are passed to the callback provided to Networking::connectNetworkError().
+ * These values are passed to the callback provided to Networking::onNetworkError().
  */
 enum ErrorType
 {
 	GBNET_ERR_RESOLVE,  //!< The client or server failed to resolve a host:port specifier
-	GBNET_ERR_ACCEPT    //!< The server failed to accept new client connections
+	GBNET_ERR_ACCEPT,   //!< The server failed to accept new client connections
+	GBNET_ERR_CONNECT   //!< The client failed to connect to the server
 };
 
 }

File gb_net/src/internal/networking.h

 	~NetworkingImpl();
 
 	//! Server listening signal.
-	/** See Networking::connectListening(). */
+	/** See Networking::onListening(). */
 	boost::signal<void (const boost::asio::ip::tcp::endpoint &endpoint)> onListening;
 
+	//! Connection established signal.
+	/** See Networking::onConnected(). */
+	boost::signal<void (const boost::asio::ip::tcp::endpoint &endpoint)> onConnected;
+
 	//! Network error signal.
-	/** See Networking::connectNetworkError(). */
+	/** See Networking::onNetworkError(). */
 	boost::signal<void (ErrorType type, const boost::system::error_code &ec)> onNetworkError;
 
 private:

File gb_net/src/internal/tcp_client.h

+/*  Copyright � 2011 Chris Spencer <spencercw@gmail.com>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef GBNET_INTERNAL_TCP_CLIENT_H
+#define GBNET_INTERNAL_TCP_CLIENT_H
+
+#include <gb_net/tcp_client.h>
+
+#include "session.h"
+
+namespace gbnet {
+namespace internal {
+
+//! Implementation class for TcpClient.
+class TcpClientImpl
+{
+public:
+	//! Constructor; connects to the given remote server.
+	/**
+	 * \param net The associated NetworkingImpl instance.
+	 * \param host The hostname or IP address (may be IPv4 or IPv6 where the operating system
+	 * supports it) of the server to connect to.
+	 * \param port The TCP port the server is listening on.
+	 */
+	TcpClientImpl(NetworkingImpl &net, const std::string &host, uint16_t port);
+	
+	//! Destructor.
+	~TcpClientImpl();
+
+private:
+	NetworkingImpl &net_;
+	Session session_;
+	boost::asio::ip::tcp::resolver resolver_;
+	
+	void handleResolve(boost::asio::ip::tcp::resolver::iterator endpointIt,
+		const boost::system::error_code &ec);
+	void handleConnect(boost::asio::ip::tcp::resolver::iterator endpointIt,
+		const boost::system::error_code &ec);
+
+	// Disabled operations
+	TcpClientImpl(const TcpClientImpl &);
+	TcpClientImpl & operator=(const TcpClientImpl &);
+};
+
+}
+}
+
+#endif

File gb_net/src/networking.cpp

     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include <gb_net/networking.h>
-
 #include "internal/networking.h"
 
 namespace bs = boost::system;
 	delete impl_;
 }
 
-sig::connection Networking::connectListening(
+sig::connection Networking::onListening(
 	boost::function<void (const tcp::endpoint &endpoint)> callback)
 {
 	return impl_->onListening.connect(callback);
 }
 
-sig::connection Networking::connectNetworkError(
+sig::connection Networking::onConnected(
+	boost::function<void (const tcp::endpoint &endpoint)> callback)
+{
+	return impl_->onConnected.connect(callback);
+}
+
+sig::connection Networking::onNetworkError(
 	boost::function<void (ErrorType type, const bs::error_code &ec)> callback)
 {
 	return impl_->onNetworkError.connect(callback);

File gb_net/src/tcp_client.cpp

+/*  Copyright � 2011 Chris Spencer <spencercw@gmail.com>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <gb_net/tcp_client.h>
+#include "internal/tcp_client.h"
+
+#include <sstream>
+
+#include <boost/asio/placeholders.hpp>
+#include <boost/asio/read.hpp>
+#include <boost/bind.hpp>
+
+#include "internal/networking.h"
+
+namespace bs = boost::system;
+namespace io = boost::asio;
+using boost::asio::ip::tcp;
+using std::ostringstream;
+using std::string;
+
+namespace gbnet {
+
+TcpClient::TcpClient(io::io_service &ioService, const string &host, uint16_t port):
+Networking(ioService),
+impl_(new internal::TcpClientImpl(*Networking::impl_, host, port))
+{
+}
+
+TcpClient::~TcpClient()
+{
+	delete impl_;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+namespace internal {
+
+TcpClientImpl::TcpClientImpl(NetworkingImpl &net, const string &host, uint16_t port):
+net_(net),
+session_(net_),
+resolver_(net_.ioService_)
+{
+	// Resolve the host:port pair to an endpoint
+	ostringstream oss;
+	oss << static_cast<unsigned>(port);
+	tcp::resolver::query query(host, oss.str());
+	resolver_.async_resolve(query, boost::bind(&TcpClientImpl::handleResolve, this,
+		io::placeholders::iterator, io::placeholders::error));
+}
+
+TcpClientImpl::~TcpClientImpl()
+{
+}
+
+void TcpClientImpl::handleResolve(tcp::resolver::iterator endpointIt, const bs::error_code &ec)
+{
+	if (ec)
+	{
+		net_.onNetworkError(GBNET_ERR_RESOLVE, ec);
+		return;
+	}
+
+	// Try the endpoints until we get a connection
+	tcp::endpoint endpoint = *endpointIt;
+	session_.socket().async_connect(endpoint, boost::bind(&TcpClientImpl::handleConnect, this,
+		++endpointIt, io::placeholders::error));
+}
+
+void TcpClientImpl::handleConnect(tcp::resolver::iterator endpointIt, const bs::error_code &ec)
+{
+	if (ec)
+	{
+		session_.socket().close();
+		if (endpointIt == tcp::resolver::iterator())
+		{
+			// No more endpoints to try; connection failed
+			net_.onNetworkError(GBNET_ERR_CONNECT, ec);
+			return;
+		}
+
+		// Try the next endpoint
+		tcp::endpoint endpoint = *endpointIt;
+		session_.socket().async_connect(endpoint, boost::bind(&TcpClientImpl::handleConnect, this,
+			++endpointIt, io::placeholders::error));
+	}
+
+	net_.onConnected(session_.socket().remote_endpoint());
+
+	// Don't delay packet sending
+	session_.socket().set_option(tcp::no_delay(true));
+}
+
+}
+}

File gb_net/src/tcp_server.cpp

     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include <gb_net/tcp_server.h>
+#include "internal/tcp_server.h"
 
 #include <sstream>
 
 
 #include "internal/networking.h"
 #include "internal/single_tcp_server.h"
-#include "internal/tcp_server.h"
 
 namespace bs = boost::system;
 namespace io = boost::asio;