Skip to content

Commit

Permalink
feat/client (#2)
Browse files Browse the repository at this point in the history
* Prototype testing

* (Broken) client

* Disconnection rework

* Uses fd_set for multiplexing

* Separate handling functions from the run function

* Implement fd_set for client
  • Loading branch information
jalsol authored May 19, 2023
1 parent d632906 commit 325526d
Show file tree
Hide file tree
Showing 11 changed files with 399 additions and 50 deletions.
19 changes: 16 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.22)
project(jaltext_server VERSION 0.1.0 LANGUAGES CXX)
project(jaltext VERSION 0.1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Expand All @@ -9,8 +9,8 @@ set(CMAKE_BUILD_TYPE Debug)

include_directories(src)

add_executable(${PROJECT_NAME}
src/main.cpp
add_executable(${PROJECT_NAME}_server
src/main_server.cpp

src/server/server.hpp src/server/server.cpp

Expand All @@ -19,5 +19,18 @@ add_executable(${PROJECT_NAME}
src/types/aliases.hpp
src/types/addr_info.hpp src/types/addr_info.cpp
src/types/sockaddr.hpp src/types/sockaddr.cpp
src/types/fd_set.hpp src/types/fd_set.cpp
)

add_executable(${PROJECT_NAME}_client
src/main_client.cpp

src/client/client.hpp src/client/client.cpp

src/jalsock/jalsock.hpp src/jalsock/jalsock.cpp

src/types/aliases.hpp
src/types/addr_info.hpp src/types/addr_info.cpp
src/types/sockaddr.hpp src/types/sockaddr.cpp
src/types/fd_set.hpp src/types/fd_set.cpp
)
125 changes: 125 additions & 0 deletions src/client/client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#include "client.hpp"

#include <unistd.h>

#include <cstring>
#include <iostream>
#include <string_view>
#include <utility>

#include "jalsock/jalsock.hpp"
#include "types/addr_info.hpp"

TCPClient::TCPClient(const std::string_view host, const std::string_view port)
: m_host{host}, m_port{port} {}

TCPClient::~TCPClient() {
if (m_server_fd != -1) {
jalsock::send(m_server_fd, "/quit", 0);
close();
}
}

TCPClient& TCPClient::setHost(const std::string_view host) {
m_host = host;
return *this;
}

TCPClient& TCPClient::setPort(const std::string_view port) {
m_port = port;
return *this;
}

void TCPClient::init() {
AddrInfo hints;
hints.setFamily(AIFamily::Unspec).setSocket(AISockType::Stream);

const auto& [error, addresses] =
jalsock::getAddressInfo(m_host, m_port, hints);

if (error != ErrAI::Success) {
std::cerr << "Can't get address info: "
<< gai_strerror(static_cast<int>(error)) << std::endl;
throw std::runtime_error{gai_strerror(static_cast<int>(error))};
}

for (const auto& address : addresses) {
m_server_fd = jalsock::socket(address.family(), address.sockType(),
address.protocol());

if (m_server_fd == -1) {
std::cerr << "Can't create socket: " << std::strerror(errno)
<< std::endl;
continue;
}

if (jalsock::connect(m_server_fd, address) == -1) {
jalsock::close(m_server_fd);
std::cerr << "Can't connect socket: " << std::strerror(errno)
<< std::endl;
continue;
}

break;
}

if (m_server_fd == -1) {
throw std::runtime_error{"Can't connect socket"};
}
}

void TCPClient::run() {
init();

std::cerr << "Connected to " << m_host << ":" << m_port << std::endl;

FileDescSet _;

while (true) {
m_fd_set.zero();
m_fd_set.set(m_server_fd);
m_fd_set.set(STDIN_FILENO);

if (jalsock::select(m_server_fd + 1, m_fd_set, _, _, nullptr) == -1) {
std::cerr << "Can't select: " << std::strerror(errno) << std::endl;
break;
}

if (m_fd_set.isSet(m_server_fd)) {
const auto& [len, message] = jalsock::recv(m_server_fd, 0);

if (len == -1) {
std::cerr << "Can't receive message: " << std::strerror(errno)
<< std::endl;
break;
} else if (len == 0) {
std::cerr << "Server closed connection" << std::endl;
break;
}

std::cout << message << std::endl;
}

if (m_fd_set.isSet(STDIN_FILENO)) {
std::string message;
std::getline(std::cin, message);

if (jalsock::send(m_server_fd, message, 0) == -1) {
std::cerr << "Can't send message: " << std::strerror(errno)
<< std::endl;
break;
}

if (message == "/quit") {
break;
}
}
}

close();
}

void TCPClient::close() {
jalsock::close(m_server_fd);
m_server_fd = -1;
}
33 changes: 33 additions & 0 deletions src/client/client.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include <string_view>

#include "types/aliases.hpp"
#include "types/fd_set.hpp"

class TCPClient {
public:
TCPClient(const std::string_view host, const std::string_view port);

TCPClient(const TCPClient&) = delete;
TCPClient(TCPClient&&) = delete;
TCPClient& operator=(const TCPClient&) = delete;
TCPClient& operator=(TCPClient&&) = delete;

~TCPClient();

TCPClient& setHost(const std::string_view host);
TCPClient& setPort(const std::string_view port);

void run();

private:
std::string_view m_host;
std::string_view m_port;

FileDesc m_server_fd{-1};
FileDescSet m_fd_set{};

void init();
void close();
};
28 changes: 28 additions & 0 deletions src/jalsock/jalsock.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
#include "jalsock.hpp"

#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>

#include <algorithm>
#include <string>

namespace jalsock {

void* getInAddr(const SockAddr& address) {
Expand All @@ -16,6 +21,10 @@ int bind(FileDesc fd, const AddrInfo& address) {
return ::bind(fd, address.address(), address.addressLen());
}

int connect(FileDesc fd, const AddrInfo& address) {
return ::connect(fd, address.address(), address.addressLen());
}

int listen(FileDesc fd, int backlog) { return ::listen(fd, backlog); }

int accept(FileDesc fd, SockAddr& address) {
Expand All @@ -31,6 +40,12 @@ int send(FileDesc fd, const std::string_view view, int flags) {
return ::send(fd, view.data(), view.size(), flags);
}

std::pair<int, std::string> recv(FileDesc fd, int flags) {
static char buffer[1024];
int len = ::recv(fd, buffer, sizeof(buffer), flags);
return {len, std::string(buffer, std::max(len, 0))};
}

FileDesc socket(AIFamily domain, AISockType type, AIProtocol protocol) {
return ::socket(static_cast<int>(domain), static_cast<int>(type),
static_cast<int>(protocol));
Expand Down Expand Up @@ -60,4 +75,17 @@ int setSockOpt(FileDesc fd, int level, SockOpt optname, const void* optval,
return ::setsockopt(fd, level, static_cast<int>(optname), optval, optlen);
}

std::string_view networkToPresentation(const SockAddr& address) {
static char ipstr[INET6_ADDRSTRLEN];
inet_ntop(static_cast<int>(address.family()), getInAddr(address), ipstr,
sizeof(ipstr));
return ipstr;
}

int select(int nfds, FileDescSet& readfds, FileDescSet& writefds,
FileDescSet& exceptfds, timeval* timeout) {
return ::select(nfds, &readfds.data(), &writefds.data(), &exceptfds.data(),
timeout);
}

} // namespace jalsock
11 changes: 11 additions & 0 deletions src/jalsock/jalsock.hpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
#pragma once

#include <sys/select.h>
#include <sys/socket.h>

#include <string_view>
#include <vector>

#include "types/addr_info.hpp"
#include "types/aliases.hpp"
#include "types/fd_set.hpp"
#include "types/sockaddr.hpp"

namespace jalsock {

int bind(FileDesc fd, const AddrInfo& address);

int connect(FileDesc fd, const AddrInfo& address);

int listen(FileDesc fd, int backlog);

int accept(FileDesc fd, SockAddr& addr);
Expand All @@ -23,6 +27,8 @@ int fork();

int send(FileDesc fd, const std::string_view view, int flags);

std::pair<int, std::string> recv(FileDesc fd, int flags);

FileDesc socket(AIFamily domain, AISockType type, AIProtocol protocol);

std::pair<ErrAI, std::vector<AddrInfo>> getAddressInfo(
Expand All @@ -34,4 +40,9 @@ int setSockOpt(FileDesc fd, int level, SockOpt optname, const void* optval,

void* getInAddr(const SockAddr& address);

std::string_view networkToPresentation(const SockAddr& address);

int select(int nfds, FileDescSet& readfds, FileDescSet& writefds,
FileDescSet& exceptfds, timeval* timeout);

} // namespace jalsock
18 changes: 18 additions & 0 deletions src/main_client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <iostream>

#include "client/client.hpp"

int main(int argc, char** argv) {
if (argc != 3) {
std::cerr << "Usage: ./jaltext_client <ip> <port>" << std::endl;
return 1;
}

try {
TCPClient client{argv[1], argv[2]};
client.run();
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return 1;
}
}
File renamed without changes.
Loading

0 comments on commit 325526d

Please sign in to comment.