diff --git a/Client/Client.vcxproj b/Client/Client.vcxproj new file mode 100644 index 0000000..8b59325 --- /dev/null +++ b/Client/Client.vcxproj @@ -0,0 +1,94 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {60BA9FBE-D2BA-4422-AF9A-652FF009EA46} + Win32Proj + Client + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(BOOST_ROOT); + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(BOOST_ROOT)\lib32-msvc-12.0; + + + false + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(BOOST_ROOT); + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(BOOST_ROOT)\lib32-msvc-12.0; + + + + + + Level3 + Disabled + WIN32;_WIN32_WINNT=0x0601;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;_WIN32_WINNT=0x0601;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Client/Client.vcxproj.filters b/Client/Client.vcxproj.filters new file mode 100644 index 0000000..d69994e --- /dev/null +++ b/Client/Client.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/Client/client.cpp b/Client/client.cpp new file mode 100644 index 0000000..e3fc23e --- /dev/null +++ b/Client/client.cpp @@ -0,0 +1,75 @@ +#include +#include + +#include +#include + +#include "client.h" + + +Client::Client(IoService& t_ioService, TcpResolverIterator t_endpointIterator, + std::string const& t_path) + : m_ioService(t_ioService), m_socket(t_ioService), + m_endpointIterator(t_endpointIterator), m_path(t_path) +{ + doConnect(); + openFile(m_path); +} + + +void Client::openFile(std::string const& t_path) +{ + m_sourceFile.open(t_path, std::ios_base::binary | std::ios_base::ate); + if (m_sourceFile.fail()) + throw std::fstream::failure("Failed while opening file " + t_path); + + m_sourceFile.seekg(0, m_sourceFile.end); + auto fileSize = m_sourceFile.tellg(); + m_sourceFile.seekg(0, m_sourceFile.beg); + + std::ostream requestStream(&m_request); + boost::filesystem::path p(t_path); + requestStream << p.filename().string() << "\n" << fileSize << "\n\n"; + BOOST_LOG_TRIVIAL(trace) << "Request size: " << m_request.size(); +} + + +void Client::doConnect() +{ + boost::asio::async_connect(m_socket, m_endpointIterator, + [this](boost::system::error_code ec, TcpResolverIterator) + { + if (!ec) { + writeBuffer(m_request); + } else { + std::cout << "Coudn't connect to host. Please run server " + "or check network connection.\n"; + BOOST_LOG_TRIVIAL(error) << "Error: " << ec.message(); + } + }); +} + + +void Client::doWriteFile(const boost::system::error_code& t_ec) +{ + if (!t_ec) { + if (m_sourceFile) { + m_sourceFile.read(m_buf.data(), m_buf.size()); + if (m_sourceFile.fail() && !m_sourceFile.eof()) { + auto msg = "Failed while reading file"; + BOOST_LOG_TRIVIAL(error) << msg; + throw std::fstream::failure(msg); + } + std::stringstream ss; + ss << "Send " << m_sourceFile.gcount() << " bytes, total: " + << m_sourceFile.tellg() << " bytes"; + BOOST_LOG_TRIVIAL(trace) << ss.str(); + std::cout << ss.str() << std::endl; + + auto buf = boost::asio::buffer(m_buf.data(), static_cast(m_sourceFile.gcount())); + writeBuffer(buf); + } + } else { + BOOST_LOG_TRIVIAL(error) << "Error: " << t_ec.message(); + } +} diff --git a/Client/client.h b/Client/client.h new file mode 100644 index 0000000..7f2ffb1 --- /dev/null +++ b/Client/client.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +#include + + +class Client +{ +public: + using IoService = boost::asio::io_service; + using TcpResolver = boost::asio::ip::tcp::resolver; + using TcpResolverIterator = TcpResolver::iterator; + using TcpSocket = boost::asio::ip::tcp::socket; + + Client(IoService& t_ioService, TcpResolverIterator t_endpointIterator, + std::string const& t_path); + +private: + void openFile(std::string const& t_path); + void doConnect(); + void doWriteFile(const boost::system::error_code& t_ec); + template + void writeBuffer(Buffer& t_buffer); + + + TcpResolver m_ioService; + TcpSocket m_socket; + TcpResolverIterator m_endpointIterator; + enum { MessageSize = 1024 }; + std::array m_buf; + boost::asio::streambuf m_request; + std::ifstream m_sourceFile; + std::string m_path; +}; + + +template +void Client::writeBuffer(Buffer& t_buffer) +{ + boost::asio::async_write(m_socket, + t_buffer, + [this](boost::system::error_code ec, size_t /*length*/) + { + doWriteFile(ec); + }); +} diff --git a/Client/main.cpp b/Client/main.cpp new file mode 100644 index 0000000..a8cf35c --- /dev/null +++ b/Client/main.cpp @@ -0,0 +1,37 @@ +#include + +#include + +#include "client.h" +#include "../Common/logger.h" + + +int main(int argc, char* argv[]) +{ + if (argc != 4) { + std::cerr << "Usage: client
\n"; + return 1; + } + + Logger::instance().setOptions("client_%3N.log", 1 * 1024 * 1024, 10 * 1024 * 1024); + + auto address = argv[1]; + auto port = argv[2]; + auto filePath = argv[3]; + + try { + boost::asio::io_service ioService; + + boost::asio::ip::tcp::resolver resolver(ioService); + auto endpointIterator = resolver.resolve({ address, port }); + Client client(ioService, endpointIterator, filePath); + + ioService.run(); + } catch (std::fstream::failure& e) { + std::cerr << e.what() << "\n"; + } catch (std::exception& e) { + std::cerr << "Exception: " << e.what() << "\n"; + } + + return 0; +} diff --git a/Common/logger.cpp b/Common/logger.cpp new file mode 100644 index 0000000..d87938d --- /dev/null +++ b/Common/logger.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "logger.h" + + +Logger& Logger::instance() +{ + static Logger logger; + return logger; +} + + +void Logger::setOptions(std::string const& t_fileName, unsigned t_rotationSize, + unsigned t_maxSize) +{ + boost::log::add_file_log( + boost::log::keywords::file_name = t_fileName, + boost::log::keywords::rotation_size = t_rotationSize, + boost::log::keywords::max_size = t_maxSize, + boost::log::keywords::time_based_rotation + = boost::log::sinks::file::rotation_at_time_point(0, 0, 0), + boost::log::keywords::format = "[%TimeStamp%]: %Message%", + boost::log::keywords::auto_flush = true + ); + + boost::log::add_common_attributes(); +} diff --git a/Common/logger.h b/Common/logger.h new file mode 100644 index 0000000..7e545af --- /dev/null +++ b/Common/logger.h @@ -0,0 +1,13 @@ +#pragma once + +#include + + +class Logger +{ + Logger() {} +public: + static Logger& instance(); + static void setOptions(std::string const& t_fileName, unsigned t_rotationSize, + unsigned t_maxSize); +}; \ No newline at end of file diff --git a/FileTransfer.sln b/FileTransfer.sln new file mode 100644 index 0000000..99e7446 --- /dev/null +++ b/FileTransfer.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40418.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Client", "Client\Client.vcxproj", "{60BA9FBE-D2BA-4422-AF9A-652FF009EA46}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Server", "Server\Server.vcxproj", "{49207E11-2770-4624-ACBC-CB9D1CD4A6A9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {60BA9FBE-D2BA-4422-AF9A-652FF009EA46}.Debug|Win32.ActiveCfg = Debug|Win32 + {60BA9FBE-D2BA-4422-AF9A-652FF009EA46}.Debug|Win32.Build.0 = Debug|Win32 + {60BA9FBE-D2BA-4422-AF9A-652FF009EA46}.Release|Win32.ActiveCfg = Release|Win32 + {60BA9FBE-D2BA-4422-AF9A-652FF009EA46}.Release|Win32.Build.0 = Release|Win32 + {49207E11-2770-4624-ACBC-CB9D1CD4A6A9}.Debug|Win32.ActiveCfg = Debug|Win32 + {49207E11-2770-4624-ACBC-CB9D1CD4A6A9}.Debug|Win32.Build.0 = Debug|Win32 + {49207E11-2770-4624-ACBC-CB9D1CD4A6A9}.Release|Win32.ActiveCfg = Release|Win32 + {49207E11-2770-4624-ACBC-CB9D1CD4A6A9}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md new file mode 100644 index 0000000..5a129b2 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# Requirements +- MS Visual Studio 2015 with update 5 +- boost 1.58.0 + +# Building for Windows +- Install boost.asio +- Add `BOOST_ROOT` with boost's directory path to environment variables +- Open solution and build + +# Running +- start server to accept files +``` +server +``` +- start client to transfer file +``` +client
+``` + diff --git a/Server/Server.vcxproj b/Server/Server.vcxproj new file mode 100644 index 0000000..ea64aa8 --- /dev/null +++ b/Server/Server.vcxproj @@ -0,0 +1,94 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {49207E11-2770-4624-ACBC-CB9D1CD4A6A9} + Win32Proj + Server + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(BOOST_ROOT); + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(BOOST_ROOT)\lib32-msvc-12.0; + + + false + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(BOOST_ROOT); + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(BOOST_ROOT)\lib32-msvc-12.0; + + + + + + Level3 + Disabled + WIN32;_WIN32_WINNT=0x0601;WIN32_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;_WIN32_WINNT=0x0601;WIN32NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Server/Server.vcxproj.filters b/Server/Server.vcxproj.filters new file mode 100644 index 0000000..9e2d290 --- /dev/null +++ b/Server/Server.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/Server/main.cpp b/Server/main.cpp new file mode 100644 index 0000000..7687bb1 --- /dev/null +++ b/Server/main.cpp @@ -0,0 +1,29 @@ +#include + +#include + +#include "server.h" +#include "../Common/logger.h" + + +int main(int argc, char* argv[]) +{ + if (argc != 3) { + std::cerr << "Usage: server \n"; + return 1; + } + + Logger::instance().setOptions("server_%3N.log", 1 * 1024 * 1024, 10 * 1024 * 1024); + + try { + boost::asio::io_service ioService; + + Server server(ioService, std::stoi(argv[1]), argv[2]); + + ioService.run(); + } catch (std::exception& e) { + std::cerr << "Exception: " << e.what() << "\n"; + } + + return 0; +} diff --git a/Server/server.cpp b/Server/server.cpp new file mode 100644 index 0000000..37fb58d --- /dev/null +++ b/Server/server.cpp @@ -0,0 +1,146 @@ +#include + +#include +#include +#include + +#include "server.h" + + +Session::Session(TcpSocket t_socket) + : m_socket(std::move(t_socket)) +{ +} + + +void Session::doRead() +{ + auto self = shared_from_this(); + async_read_until(m_socket, m_requestBuf_, "\n\n", + [this, self](boost::system::error_code ec, size_t bytes) + { + if (!ec) + processRead(bytes); + else + handleError(__FUNCTION__, ec); + }); +} + + +void Session::processRead(size_t t_bytesTransferred) +{ + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << "(" << t_bytesTransferred << ")" + << ", in_avail = " << m_requestBuf_.in_avail() << ", size = " + << m_requestBuf_.size() << ", max_size = " << m_requestBuf_.max_size() << "."; + + std::istream requestStream(&m_requestBuf_); + readData(requestStream); + + auto pos = m_fileName.find_last_of('\\'); + if (pos != std::string::npos) + m_fileName = m_fileName.substr(pos + 1); + + createFile(); + + // write extra bytes to file + do { + requestStream.read(m_buf.data(), m_buf.size()); + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << " write " << requestStream.gcount() << " bytes."; + m_outputFile.write(m_buf.data(), requestStream.gcount()); + } while (requestStream.gcount() > 0); + + auto self = shared_from_this(); + m_socket.async_read_some(boost::asio::buffer(m_buf.data(), m_buf.size()), + [this, self](boost::system::error_code ec, size_t bytes) + { + if (!ec) + doReadFileContent(bytes); + else + handleError(__FUNCTION__, ec); + }); +} + + +void Session::readData(std::istream &stream) +{ + stream >> m_fileName; + stream >> m_fileSize; + stream.read(m_buf.data(), 2); + + BOOST_LOG_TRIVIAL(trace) << m_fileName << " size is " << m_fileSize + << ", tellg = " << stream.tellg(); +} + + +void Session::createFile() +{ + m_outputFile.open(m_fileName, std::ios_base::binary); + if (!m_outputFile) { + BOOST_LOG_TRIVIAL(error) << __LINE__ << ": Failed to create: " << m_fileName; + return; + } +} + + +void Session::doReadFileContent(size_t t_bytesTransferred) +{ + if (t_bytesTransferred > 0) { + m_outputFile.write(m_buf.data(), static_cast(t_bytesTransferred)); + + BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << " recv " << m_outputFile.tellp() << " bytes"; + + if (m_outputFile.tellp() >= static_cast(m_fileSize)) { + std::cout << "Received file: " << m_fileName << std::endl; + return; + } + } + auto self = shared_from_this(); + m_socket.async_read_some(boost::asio::buffer(m_buf.data(), m_buf.size()), + [this, self](boost::system::error_code ec, size_t bytes) + { + doReadFileContent(bytes); + }); +} + + +void Session::handleError(std::string const& t_functionName, boost::system::error_code const& t_ec) +{ + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " in " << t_functionName << " due to " + << t_ec << " " << t_ec.message() << std::endl; +} + + +Server::Server(IoService& t_ioService, short t_port, std::string const& t_workDirectory) + : m_socket(t_ioService), + m_acceptor(t_ioService, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), t_port)), + m_workDirectory(t_workDirectory) +{ + std::cout << "Server started\n"; + + createWorkDirectory(); + + doAccept(); +} + + +void Server::doAccept() +{ + m_acceptor.async_accept(m_socket, + [this](boost::system::error_code ec) + { + if (!ec) + std::make_shared(std::move(m_socket))->start(); + + doAccept(); + }); +} + + +void Server::createWorkDirectory() +{ + using namespace boost::filesystem; + auto currentPath = path(m_workDirectory); + if (!exists(currentPath) && !create_directory(currentPath)) + BOOST_LOG_TRIVIAL(error) << "Coudn't create working directory: " << m_workDirectory; + current_path(currentPath); +} diff --git a/Server/server.h b/Server/server.h new file mode 100644 index 0000000..eaaf8d0 --- /dev/null +++ b/Server/server.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include +#include + + +class Session + : public std::enable_shared_from_this +{ +public: + using TcpSocket = boost::asio::ip::tcp::socket; + + Session(TcpSocket t_socket); + + void start() + { + doRead(); + } + +private: + void doRead(); + void processRead(size_t t_bytesTransferred); + void createFile(); + void readData(std::istream &stream); + void doReadFileContent(size_t t_bytesTransferred); + void handleError(std::string const& t_functionName, boost::system::error_code const& t_ec); + + + TcpSocket m_socket; + enum { MaxLength = 40960 }; + std::array m_buf; + boost::asio::streambuf m_requestBuf_; + std::ofstream m_outputFile; + size_t m_fileSize; + std::string m_fileName; +}; + + +class Server +{ +public: + using TcpSocket = boost::asio::ip::tcp::socket; + using TcpAcceptor = boost::asio::ip::tcp::acceptor; + using IoService = boost::asio::io_service; + + Server(IoService& t_ioService, short t_port, std::string const& t_workDirectory); + +private: + void doAccept(); + void createWorkDirectory(); + + TcpSocket m_socket; + TcpAcceptor m_acceptor; + + std::string m_workDirectory; +};