-
Notifications
You must be signed in to change notification settings - Fork 0
feat 3hw: tcp client + server #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| cmake_minimum_required(VERSION 3.17) | ||
|
|
||
| add_subdirectory(hw_3_tcp) | ||
| add_subdirectory(hw_1_process) | ||
| add_subdirectory(hw_2_logger) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| cmake_minimum_required(VERSION 3.17) | ||
| project(hw_3_tcp) | ||
|
|
||
| set(CMAKE_CXX_STANDARD 17) | ||
|
|
||
| add_library(hw_3_tcp STATIC src/server.cpp src/connection.cpp) | ||
| target_include_directories(hw_3_tcp PUBLIC include) | ||
|
|
||
| target_link_libraries(hw_3_tcp hw_1_descriptor) | ||
| target_link_libraries(hw_3_tcp hw_2_logger) | ||
|
|
||
| add_executable(test_client_hw_3 tests/test_client.cpp) | ||
| target_link_libraries(test_client_hw_3 hw_3_tcp) | ||
|
|
||
| add_executable(test_server_hw_3 tests/test_server.cpp) | ||
| target_link_libraries(test_server_hw_3 hw_3_tcp) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| #pragma once | ||
|
|
||
| #include "descriptor.hpp" | ||
| #include <iostream> | ||
| #include <optional> | ||
|
|
||
| namespace tcp { | ||
|
|
||
| class TcpError : public std::runtime_error { | ||
| using std::runtime_error::runtime_error; | ||
| }; | ||
|
|
||
| class ConnectionError : public TcpError { | ||
| using TcpError::TcpError; | ||
| }; | ||
|
|
||
| class Connection { | ||
| public: | ||
| Connection() = default; | ||
| Connection(desc::Descriptor &&client_socket, std::string addr, uint16_t port); | ||
|
|
||
| Connection(Connection &other) = delete; | ||
| Connection(Connection &&other) noexcept; | ||
|
|
||
| Connection &operator=(Connection &other) = delete; | ||
| Connection &operator=(Connection &&other) noexcept; | ||
|
|
||
| virtual ~Connection() = default; | ||
|
|
||
| void Close(); | ||
|
|
||
| size_t Read(char *, size_t); | ||
| void ReadExact(char *, size_t); | ||
|
|
||
| size_t Write(const char *, size_t); | ||
| void WriteExact(const char *, size_t); | ||
|
|
||
| void SetTimeout(size_t sec, size_t ms); | ||
| void SetTimeout(const timeval &timeout); | ||
|
|
||
| [[nodiscard]] bool IsOpen() const; | ||
| [[nodiscard]] int GetSocket() const; | ||
| [[nodiscard]] std::string GetAddr() const; | ||
| [[nodiscard]] std::string GetPort() const; | ||
|
|
||
| protected: | ||
| desc::Descriptor socket_; | ||
| std::optional<timeval> timeout_; | ||
| std::string addr_; | ||
| uint16_t port_; | ||
|
|
||
| void SetTimeoutInternal(const timeval &timeout); | ||
| void LogErrorAndThrow(); | ||
| }; | ||
|
|
||
| class ClientConnection : public Connection { | ||
| public: | ||
| ClientConnection(const std::string &addr, int port); | ||
| void Connect(const std::string &addr, int port); | ||
| }; | ||
|
|
||
| } // namespace tcp | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| #pragma once | ||
|
|
||
| #include "descriptor.hpp" | ||
| #include "tcp.hpp" | ||
| #include <iostream> | ||
| #include <optional> | ||
|
|
||
| namespace tcp { | ||
|
|
||
| class ServerError : public TcpError { | ||
| using TcpError::TcpError; | ||
| }; | ||
|
|
||
| class ServerAcceptError : public ServerError { | ||
| using ServerError::ServerError; | ||
| }; | ||
|
|
||
| class Server { | ||
| public: | ||
| Server() = default; | ||
| Server(const std::string &addr, int port, size_t max_connections); | ||
|
|
||
| Server(Server &other) = delete; | ||
| Server(Server &&other) noexcept; | ||
|
|
||
| Server &operator=(Server &other) = delete; | ||
| Server &operator=(Server &&other) noexcept; | ||
|
|
||
| void Open(const std::string &addr, int port, size_t max_connections); | ||
| void Close(); | ||
| Connection Accept(); | ||
|
|
||
| void SetTimeout(size_t sec, size_t ms); | ||
|
|
||
| private: | ||
| desc::Descriptor server_socket_; | ||
| std::optional<timeval> timeout_; | ||
|
|
||
| void LogErrorAndThrow(); | ||
| void SetTimeoutInternal(const timeval &timeout); | ||
| }; | ||
|
|
||
| } // namespace tcp |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,209 @@ | ||
| #include "tcp.hpp" | ||
|
|
||
| #include "logger.hpp" | ||
| #include <arpa/inet.h> | ||
| #include <cassert> | ||
| #include <cstring> | ||
| #include <netinet/in.h> | ||
| #include <sys/socket.h> | ||
| #include <unistd.h> | ||
| #include <utility> | ||
|
|
||
| namespace tcp { | ||
|
|
||
| Connection::Connection(Connection &&other) noexcept | ||
| : socket_(std::move(other.socket_)) { | ||
|
|
||
| std::swap(addr_, other.addr_); | ||
| std::swap(port_, other.port_); | ||
| std::swap(timeout_, other.timeout_); | ||
| } | ||
|
|
||
| Connection &Connection::operator=(Connection &&other) noexcept { | ||
| if (this != &other) { | ||
| socket_ = std::move(other.socket_); | ||
|
|
||
| std::swap(addr_, other.addr_); | ||
| std::swap(port_, other.port_); | ||
| std::swap(timeout_, other.timeout_); | ||
| } | ||
| return *this; | ||
| } | ||
|
|
||
| Connection::Connection(desc::Descriptor &&client_socket, std::string addr, | ||
| uint16_t port) | ||
| : socket_(std::move(client_socket)), addr_(std::move(addr)), port_(port) {} | ||
|
|
||
| void Connection::Close() { | ||
| log::INFO("close connection on fd = " + std::to_string(*socket_)); | ||
| socket_.close(); | ||
| timeout_ = std::nullopt; | ||
| } | ||
|
|
||
| size_t Connection::Read(char *data, size_t len) { | ||
| if (!socket_.isValid()) { | ||
| log::ERROR("try to read from invalid descriptor"); | ||
| throw ConnectionError("can't read: invalid descriptor"); | ||
| } | ||
| ssize_t bytes_read = ::read(*socket_, data, len); | ||
|
|
||
| if (bytes_read < 0) { | ||
| LogErrorAndThrow(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Лучше кинуть исключение. |
||
| } | ||
|
|
||
| if (bytes_read == 0) { | ||
| log::INFO("other side close connection on fd= " + std::to_string(*socket_)); | ||
| socket_.close(); | ||
| } | ||
|
|
||
| log::DEBUG("from socket " + std::to_string(*socket_) + | ||
| ", read bytes = " + std::to_string(bytes_read)); | ||
| return static_cast<size_t>(bytes_read); | ||
| } | ||
|
|
||
| void Connection::ReadExact(char *data, size_t len) { | ||
| size_t rest = len; | ||
| size_t position = 0; | ||
| size_t bytes_read = 0; | ||
| while ((bytes_read = Read(static_cast<char *>(data) + position, rest)) > 0) { | ||
| if (bytes_read >= rest) { | ||
| return; | ||
| } | ||
| rest -= bytes_read; | ||
| position += bytes_read; | ||
| } | ||
| if (bytes_read == 0) { | ||
| log::WARN("channel was closed while reading, rest_bytes= " + | ||
| std::to_string(rest)); | ||
| throw ConnectionError("channel was closed while reading"); | ||
| } | ||
| } | ||
|
|
||
| size_t Connection::Write(const char *data, size_t len) { | ||
| if (!socket_.isValid()) { | ||
| log::ERROR("can't write message: invalid descriptor"); | ||
| throw ConnectionError("can't write message: invalid descriptor"); | ||
| } | ||
| ssize_t bytes_wrote = ::write(*socket_, data, len); | ||
|
|
||
| if (bytes_wrote < 0) { | ||
| LogErrorAndThrow(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Кинуть исключение |
||
| } | ||
| if (bytes_wrote == 0) { | ||
| log::INFO("other side close connection on fd= " + std::to_string(*socket_)); | ||
| socket_.close(); | ||
| } | ||
| log::DEBUG("to socket " + std::to_string(*socket_) + | ||
| ", wrote bytes = " + std::to_string(bytes_wrote)); | ||
| return static_cast<size_t>(bytes_wrote); | ||
| } | ||
|
|
||
| void Connection::WriteExact(const char *data, size_t rest) { | ||
| size_t position = 0; | ||
| size_t bytes_write = 0; | ||
| while ((bytes_write = | ||
| Write(static_cast<const char *>(data) + position, rest)) > 0) { | ||
| if (bytes_write >= rest) { | ||
| return; | ||
| } | ||
| rest -= bytes_write; | ||
| position += bytes_write; | ||
| } | ||
| if (bytes_write == 0) { | ||
| log::WARN("channel was closed while writing, rest_bytes= " + | ||
| std::to_string(rest)); | ||
| throw ConnectionError("channel was closed while writing"); | ||
| } | ||
| } | ||
|
|
||
| void Connection::SetTimeout(size_t sec, size_t ms) { | ||
| timeval timeout{}; | ||
| timeout.tv_sec = sec; | ||
| timeout.tv_usec = ms; | ||
|
|
||
| SetTimeout(timeout); | ||
| } | ||
|
|
||
| void Connection::SetTimeout(const timeval &timeout) { | ||
| if (!socket_.isValid()) { | ||
| timeout_ = timeout; | ||
| return; | ||
| } | ||
| SetTimeoutInternal(timeout); | ||
| } | ||
|
|
||
| void Connection::SetTimeoutInternal(const timeval &timeout) { | ||
|
|
||
| assert(socket_.isValid()); | ||
|
|
||
| log::DEBUG("try to set timeout s= " + std::to_string(timeout.tv_sec) + | ||
| ", ms= " + std::to_string(timeout.tv_usec)); | ||
|
|
||
| if (::setsockopt(*socket_, SOL_SOCKET, SO_SNDTIMEO, &timeout, | ||
| sizeof(timeout)) < 0) { | ||
| log::WARN("can't set out timeout to socket: " + std::to_string(*socket_)); | ||
| } | ||
| if (::setsockopt(*socket_, SOL_SOCKET, SO_RCVTIMEO, &timeout, | ||
| sizeof(timeout)) < 0) { | ||
| log::WARN("can't set in timeout to socket: " + std::to_string(*socket_)); | ||
| } | ||
| } | ||
|
|
||
| bool Connection::IsOpen() const { return socket_.isValid(); } | ||
|
|
||
| void Connection::LogErrorAndThrow() { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Название функции не отображает её сути.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Почему? |
||
|
|
||
| socket_.close(); | ||
| log::ERROR(std::strerror(errno)); | ||
| throw ConnectionError(std::strerror(errno)); | ||
| } | ||
|
|
||
| int Connection::GetSocket() const { return *socket_; } | ||
|
|
||
| std::string Connection::GetAddr() const { return addr_; } | ||
|
|
||
| std::string Connection::GetPort() const { return std::to_string(port_); } | ||
|
|
||
| ClientConnection::ClientConnection(const std::string &addr, int port) { | ||
|
Kapitonov marked this conversation as resolved.
|
||
| addr_ = addr; | ||
| port_ = port; | ||
| Connect(addr, port); | ||
| } | ||
|
|
||
| void ClientConnection::Connect(const std::string &addr, int port) { | ||
|
|
||
| if (socket_.isValid()) { | ||
| log::DEBUG("try to create new connection on p=" + std::to_string(port) + | ||
| ", addr= " + addr + "; close old one on "); | ||
| Close(); | ||
| } else { | ||
| log::DEBUG("try to create new connection on p=" + std::to_string(port) + | ||
| ", addr= " + addr); | ||
| } | ||
|
|
||
| try { | ||
| socket_ = desc::Descriptor(::socket(AF_INET, SOCK_STREAM, 0)); | ||
| } catch (const desc::DescriptorError &err) { | ||
| LogErrorAndThrow(); | ||
| } | ||
|
|
||
| if (timeout_) { | ||
| SetTimeoutInternal(*timeout_); | ||
| } | ||
|
|
||
| sockaddr_in sock_addr{}; | ||
| sock_addr.sin_family = AF_INET; | ||
| sock_addr.sin_port = ::htons(port); | ||
| int result = ::inet_aton(addr.c_str(), &sock_addr.sin_addr); | ||
| if (result <= 0) { | ||
| LogErrorAndThrow(); | ||
| } | ||
|
|
||
| result = ::connect(*socket_, reinterpret_cast<sockaddr *>(&sock_addr), | ||
| sizeof(sock_addr)); | ||
| if (result != 0) { | ||
| LogErrorAndThrow(); | ||
| } | ||
| } | ||
|
|
||
| } // namespace tcp | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
В других функциях вы пишете названия переменных.