aboutsummaryrefslogtreecommitdiff
path: root/store/works/life/computer-network-experiment
diff options
context:
space:
mode:
Diffstat (limited to 'store/works/life/computer-network-experiment')
-rw-r--r--store/works/life/computer-network-experiment/.gitignore5
-rw-r--r--store/works/life/computer-network-experiment/.vscode/launch.json16
-rw-r--r--store/works/life/computer-network-experiment/Base.hpp59
-rw-r--r--store/works/life/computer-network-experiment/CMakeLists.txt23
-rw-r--r--store/works/life/computer-network-experiment/Common.cpp142
-rw-r--r--store/works/life/computer-network-experiment/Common.h54
-rw-r--r--store/works/life/computer-network-experiment/IO.cpp81
-rw-r--r--store/works/life/computer-network-experiment/IO.h68
-rw-r--r--store/works/life/computer-network-experiment/PreConfig.hpp6
-rw-r--r--store/works/life/computer-network-experiment/ReadWriteLock.cpp97
-rw-r--r--store/works/life/computer-network-experiment/ReadWriteLock.h45
-rw-r--r--store/works/life/computer-network-experiment/StringUtil.cpp355
-rw-r--r--store/works/life/computer-network-experiment/StringUtil.hpp158
-rw-r--r--store/works/life/computer-network-experiment/TODO.md6
-rw-r--r--store/works/life/computer-network-experiment/client.cpp103
-rw-r--r--store/works/life/computer-network-experiment/server.cpp261
16 files changed, 1479 insertions, 0 deletions
diff --git a/store/works/life/computer-network-experiment/.gitignore b/store/works/life/computer-network-experiment/.gitignore
new file mode 100644
index 0000000..502724c
--- /dev/null
+++ b/store/works/life/computer-network-experiment/.gitignore
@@ -0,0 +1,5 @@
+*.exe
+*.pdb
+.cache
+build
+compile_commands.json \ No newline at end of file
diff --git a/store/works/life/computer-network-experiment/.vscode/launch.json b/store/works/life/computer-network-experiment/.vscode/launch.json
new file mode 100644
index 0000000..882a540
--- /dev/null
+++ b/store/works/life/computer-network-experiment/.vscode/launch.json
@@ -0,0 +1,16 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Launch server.",
+ "type": "gdb",
+ "request": "launch",
+ "target": "${workspaceRoot}/build/server",
+ "cwd": "${workspaceRoot}",
+ "valuesFormatting": "parseText"
+ }
+ ]
+} \ No newline at end of file
diff --git a/store/works/life/computer-network-experiment/Base.hpp b/store/works/life/computer-network-experiment/Base.hpp
new file mode 100644
index 0000000..b1ad55c
--- /dev/null
+++ b/store/works/life/computer-network-experiment/Base.hpp
@@ -0,0 +1,59 @@
+#pragma once
+#include "PreConfig.hpp"
+
+#include <exception>
+#include <stdexcept>
+#include <gsl/gsl>
+
+#define CRU_UNUSED(entity) static_cast<void>(entity);
+
+#define CRU__CONCAT(a, b) a##b
+#define CRU_MAKE_UNICODE_LITERAL(str) CRU__CONCAT(u, #str)
+
+#define CRU_DEFAULT_COPY(classname) \
+ classname(const classname&) = default; \
+ classname& operator=(const classname&) = default;
+
+#define CRU_DEFAULT_MOVE(classname) \
+ classname(classname&&) = default; \
+ classname& operator=(classname&&) = default;
+
+#define CRU_DELETE_COPY(classname) \
+ classname(const classname&) = delete; \
+ classname& operator=(const classname&) = delete;
+
+#define CRU_DELETE_MOVE(classname) \
+ classname(classname&&) = delete; \
+ classname& operator=(classname&&) = delete;
+
+namespace cru {
+class Object {
+ public:
+ Object() = default;
+ CRU_DEFAULT_COPY(Object)
+ CRU_DEFAULT_MOVE(Object)
+ virtual ~Object() = default;
+};
+
+struct Interface {
+ Interface() = default;
+ CRU_DELETE_COPY(Interface)
+ CRU_DELETE_MOVE(Interface)
+ virtual ~Interface() = default;
+};
+
+[[noreturn]] inline void UnreachableCode() { std::terminate(); }
+
+using Index = gsl::index;
+
+// https://www.boost.org/doc/libs/1_54_0/doc/html/hash/reference.html#boost.hash_combine
+template <class T>
+inline void hash_combine(std::size_t& s, const T& v) {
+ std::hash<T> h;
+ s ^= h(v) + 0x9e3779b9 + (s << 6) + (s >> 2);
+}
+
+#define CRU_DEFINE_CLASS_LOG_TAG(tag) \
+ private: \
+ constexpr static std::u16string_view log_tag = tag;
+} // namespace cru
diff --git a/store/works/life/computer-network-experiment/CMakeLists.txt b/store/works/life/computer-network-experiment/CMakeLists.txt
new file mode 100644
index 0000000..2ffd481
--- /dev/null
+++ b/store/works/life/computer-network-experiment/CMakeLists.txt
@@ -0,0 +1,23 @@
+cmake_minimum_required(VERSION 3.20)
+
+set(CMAKE_TOOLCHAIN_FILE $ENV{VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake
+ CACHE STRING "Vcpkg toolchain file")
+
+project(network-experiment)
+
+set(CMAKE_CXX_STANDARD 17)
+
+find_package(fmt CONFIG REQUIRED)
+find_package(Microsoft.GSL CONFIG REQUIRED)
+add_library(base STATIC Common.cpp StringUtil.cpp IO.cpp ReadWriteLock.cpp)
+target_link_libraries(base PUBLIC Microsoft.GSL::GSL fmt::fmt Folly::folly)
+if(WIN32)
+target_link_libraries(base PUBLIC Ws2_32)
+endif()
+
+add_executable(client client.cpp)
+target_link_libraries(client PRIVATE base)
+
+add_executable(server server.cpp)
+find_package(folly CONFIG REQUIRED)
+target_link_libraries(server PRIVATE base)
diff --git a/store/works/life/computer-network-experiment/Common.cpp b/store/works/life/computer-network-experiment/Common.cpp
new file mode 100644
index 0000000..1df4d56
--- /dev/null
+++ b/store/works/life/computer-network-experiment/Common.cpp
@@ -0,0 +1,142 @@
+#include "Common.h"
+
+#include "IO.h"
+#include <memory>
+
+#ifdef WIN32
+#include <Windows.h>
+#include <winsock.h>
+#else
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/unistd.h>
+#endif
+
+#include <string>
+
+[[noreturn]] void PrintErrorMessageAndExit(StringView message,
+ bool print_last_error) {
+
+ SendOutput(CRUT("{}\n"), message);
+
+ if (print_last_error) {
+#ifdef WIN32
+ auto error_code = WSAGetLastError();
+ SendOutput(OutputType::Error, CRUT("Error code is {}.\n"), error_code);
+ wchar_t buffer[500];
+ if (!FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_ARGUMENT_ARRAY |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr, error_code, 0, buffer, 500, nullptr)) {
+ SendOutput(OutputType::Error, CRUT("Failed to format error message.\n"));
+ } else {
+ SendOutput(OutputType::Error, CRUT("{}\n"), buffer);
+ }
+#else
+#endif
+ }
+
+ BeforeExit();
+
+ std::exit(1);
+}
+
+#ifdef WIN32
+namespace {
+void InitWSA() {
+ WSADATA wsa_data;
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsa_data)) { // initialize wsa
+ PrintErrorMessageAndExit(CRUT("Failed to initialize wsa."));
+ }
+}
+} // namespace
+
+#endif
+
+int CloseSocket(int socket) {
+#ifdef WIN32
+ return closesocket(socket);
+#else
+ return close(socket);
+#endif
+}
+
+void BeforeExit() {
+#ifdef WIN32
+ WSACleanup();
+#endif
+
+ SignalAndWaitForOutputThreadStop();
+}
+
+bool SafeSend(int socket, std::string_view buffer) {
+ const int total_byte_count = buffer.size();
+ int byte_count_sent = 0;
+ int retry_count = 0;
+
+ while (true) {
+ // Now we have sent all data.
+ if (byte_count_sent == total_byte_count)
+ return true;
+
+ auto byte_actually_sent = send(socket, buffer.data() + byte_count_sent,
+ buffer.size() - byte_count_sent, 0);
+
+ // send failed
+ if (byte_actually_sent == -1) {
+ return false;
+ }
+
+ byte_count_sent += byte_actually_sent;
+ }
+}
+
+bool SafeReadUntil(int socket, char c, std::string &data, std::string &rest) {
+ data = rest;
+
+ const int buffer_size = 100;
+ char buffer[buffer_size];
+
+ while (true) {
+ int received_number = recv(socket, buffer, buffer_size, 0);
+
+ if (received_number == -1) {
+ return false;
+ }
+
+ bool end = false;
+
+ for (int i = 0; i < received_number; i++) {
+ if (buffer[i] == c) {
+ data.append(buffer, i);
+ rest = std::string(buffer + i + 1, received_number - i - 1);
+ end = true;
+ break;
+ }
+ }
+
+ if (end)
+ return true;
+
+ data.append(buffer, received_number);
+ }
+}
+
+int main() {
+#ifdef WIN32
+ HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
+ DWORD mode;
+ GetConsoleMode(h, &mode);
+ mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+ SetConsoleMode(h, mode);
+
+ InitWSA();
+#endif
+
+ int c = Main();
+
+ BeforeExit();
+ return c;
+}
diff --git a/store/works/life/computer-network-experiment/Common.h b/store/works/life/computer-network-experiment/Common.h
new file mode 100644
index 0000000..1e6c277
--- /dev/null
+++ b/store/works/life/computer-network-experiment/Common.h
@@ -0,0 +1,54 @@
+#pragma once
+#include "StringUtil.hpp"
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <string_view>
+
+#ifdef WIN32
+using Char = wchar_t;
+using String = std::wstring;
+using StringView = std::wstring_view;
+using StringStream = std::wstringstream;
+inline auto &input_stream = std::wcin;
+inline auto &output_stream = std::wcout;
+inline auto &error_stream = std::wcerr;
+#define CRUT(string_literal) L##string_literal
+
+inline String ConvertCharString(std::string_view s) {
+ return cru::ToUtf16WString(s);
+}
+
+inline std::string ConvertCharStringBack(StringView s) {
+ return cru::ToUtf8(s);
+}
+#else
+using Char = char;
+using String = std::string;
+using StringView = std::string_view;
+using StringStream = std::stringstream;
+inline auto &input_stream = std::cin;
+inline auto &output_stream = std::cout;
+inline auto &error_stream = std::cerr;
+#define CRUT(string_literal) string_literal
+
+inline String ConvertCharString(std::string_view s) { return String(s); }
+inline std::string ConvertCharStringBack(StringView s) {
+ return std::string(s);
+}
+#endif
+
+int Main();
+
+[[noreturn]] void PrintErrorMessageAndExit(StringView message,
+ bool print_last_error = true);
+
+int CloseSocket(int socket);
+
+void BeforeExit();
+
+// Return false for error.
+bool SafeSend(int socket, std::string_view buffer);
+// Return false for error.
+bool SafeReadUntil(int socket, char c, std::string &data, std::string &rest);
diff --git a/store/works/life/computer-network-experiment/IO.cpp b/store/works/life/computer-network-experiment/IO.cpp
new file mode 100644
index 0000000..5d3fe12
--- /dev/null
+++ b/store/works/life/computer-network-experiment/IO.cpp
@@ -0,0 +1,81 @@
+#include "IO.h"
+
+#include <folly/CancellationToken.h>
+
+#include <mutex>
+#include <ostream>
+#include <thread>
+#include <type_traits>
+
+folly::MPMCQueue<Output> output_queue(100);
+
+namespace {
+folly::CancellationSource cancellation_source;
+std::thread io_thread;
+}
+
+void PrintOutput(const Output &output) {
+ std::basic_ostream<Char> *stream;
+
+ switch (output.type) {
+ case OutputType::Error:
+ stream = &error_stream;
+ break;
+ default:
+ stream = &output_stream;
+ break;
+ }
+
+ switch (output.color) {
+ case OutputColor::Normal:
+ (*stream) << output.message;
+ break;
+ case OutputColor::Green:
+ (*stream) << CRUT("\x1b[32m") << output.message << CRUT("\x1b[39m")
+ << std::flush;
+ break;
+ case OutputColor::Red:
+ (*stream) << CRUT("\x1b[31m") << output.message << CRUT("\x1b[39m")
+ << std::flush;
+ break;
+ case OutputColor::Yellow:
+ (*stream) << CRUT("\x1b[33m") << output.message << CRUT("\x1b[39m")
+ << std::flush;
+ break;
+ }
+}
+
+String ReadInputLine() {
+ String line;
+ std::getline(input_stream, line);
+ return line;
+}
+
+void IOThread() {
+ while (true) {
+ if (cancellation_source.isCancellationRequested()) {
+ while (true) {
+ Output output;
+ if (output_queue.readIfNotEmpty(output)) {
+ PrintOutput(output);
+ } else {
+ return;
+ }
+ }
+ }
+
+ Output output;
+ while (output_queue.readIfNotEmpty(output))
+ PrintOutput(output);
+
+ PrintOutput({CRUT("> ")});
+ OnInputLine(ReadInputLine());
+ }
+}
+
+void SignalAndWaitForOutputThreadStop() {
+ cancellation_source.requestCancellation();
+ io_thread.join();
+}
+
+void StartIOThread() { io_thread = std::thread(IOThread); }
diff --git a/store/works/life/computer-network-experiment/IO.h b/store/works/life/computer-network-experiment/IO.h
new file mode 100644
index 0000000..1658b78
--- /dev/null
+++ b/store/works/life/computer-network-experiment/IO.h
@@ -0,0 +1,68 @@
+#pragma once
+#include "Common.h"
+#include "StringUtil.hpp"
+
+#include <fmt/format.h>
+#include <folly/CancellationToken.h>
+#include <folly/MPMCPipeline.h>
+#include <folly/MPMCQueue.h>
+
+#include <iostream>
+#include <thread>
+
+enum class OutputType { Normal, Error };
+enum class OutputColor { Normal, Green, Red, Yellow };
+
+struct Output {
+ Output() = default;
+ Output(String message, OutputType type = OutputType::Normal)
+ : message(std::move(message)), type(type),
+ color(type == OutputType::Error ? OutputColor::Red
+ : OutputColor::Normal) {}
+
+ Output(String message, OutputColor color)
+ : message(std::move(message)), type(OutputType::Normal), color(color) {}
+
+ Output(String message, OutputType type, OutputColor color)
+ : message(std::move(message)), type(type), color(color) {}
+
+ CRU_DEFAULT_COPY(Output)
+ CRU_DEFAULT_MOVE(Output)
+ ~Output() = default;
+
+ String message;
+ OutputType type;
+ OutputColor color;
+};
+
+extern folly::MPMCQueue<Output> output_queue;
+
+inline void SendOutput(Output output) {
+ output_queue.blockingWrite(std::move(output));
+}
+
+inline void SendOutput(String output) { SendOutput(Output{std::move(output)}); }
+
+template <typename... Args> void SendOutput(StringView format, Args &&...args) {
+ output_queue.blockingWrite(fmt::format(format, std::forward<Args>(args)...));
+}
+
+template <typename... Args>
+void SendOutput(OutputType type, StringView format, Args &&...args) {
+ output_queue.blockingWrite(
+ Output{fmt::format(format, std::forward<Args>(args)...), type});
+}
+
+template <typename... Args>
+void SendOutput(OutputColor color, StringView format, Args &&...args) {
+ output_queue.blockingWrite(
+ Output{fmt::format(format, std::forward<Args>(args)...), color});
+}
+
+void SignalAndWaitForOutputThreadStop();
+
+void OnInputLine(StringView line);
+
+void StartIOThread();
+
+String ReadInputLine(); \ No newline at end of file
diff --git a/store/works/life/computer-network-experiment/PreConfig.hpp b/store/works/life/computer-network-experiment/PreConfig.hpp
new file mode 100644
index 0000000..d78292c
--- /dev/null
+++ b/store/works/life/computer-network-experiment/PreConfig.hpp
@@ -0,0 +1,6 @@
+#pragma once
+
+#ifdef _MSC_VER
+// disable the unnecessary warning about multi-inheritance
+#pragma warning(disable : 4250)
+#endif
diff --git a/store/works/life/computer-network-experiment/ReadWriteLock.cpp b/store/works/life/computer-network-experiment/ReadWriteLock.cpp
new file mode 100644
index 0000000..46d2857
--- /dev/null
+++ b/store/works/life/computer-network-experiment/ReadWriteLock.cpp
@@ -0,0 +1,97 @@
+#include "ReadWriteLock.h"
+
+#include <cassert>
+#include <memory>
+
+namespace cru {
+ReadWriteLock::ReadWriteLock() {
+#ifdef WIN32
+ lock_ = std::make_unique<SRWLOCK>();
+ InitializeSRWLock(lock_.get());
+#else
+ lock_.reset(new pthread_rwlock_t(PTHREAD_RWLOCK_INITIALIZER));
+ pthread_rwlock_init(lock_.get(), nullptr);
+#endif
+}
+
+ReadWriteLock::ReadWriteLock(ReadWriteLock &&other)
+ : lock_(std::move(other.lock_)) {}
+
+ReadWriteLock &ReadWriteLock::operator=(ReadWriteLock &&other) {
+ if (this != &other) {
+ Destroy();
+ lock_ = std::move(other.lock_);
+ }
+ return *this;
+}
+
+ReadWriteLock::~ReadWriteLock() { Destroy(); }
+
+void ReadWriteLock::ReadLock() {
+ assert(lock_);
+#ifdef WIN32
+ AcquireSRWLockShared(lock_.get());
+#else
+ pthread_rwlock_rdlock(lock_.get());
+#endif
+}
+
+void ReadWriteLock::WriteLock() {
+ assert(lock_);
+#ifdef WIN32
+ AcquireSRWLockExclusive(lock_.get());
+#else
+ pthread_rwlock_wrlock(lock_.get());
+#endif
+}
+
+bool ReadWriteLock::ReadTryLock() {
+ assert(lock_);
+#ifdef WIN32
+ return TryAcquireSRWLockShared(lock_.get()) != 0;
+#else
+ return pthread_rwlock_tryrdlock(lock_.get()) == 0;
+#endif
+}
+
+bool ReadWriteLock::WriteTryLock() {
+ assert(lock_);
+#ifdef WIN32
+ return TryAcquireSRWLockExclusive(lock_.get()) != 0;
+#else
+ return pthread_rwlock_trywrlock(lock_.get()) == 0;
+#endif
+}
+
+void ReadWriteLock::ReadUnlock() {
+ assert(lock_);
+#ifdef WIN32
+ ReleaseSRWLockShared(lock_.get());
+#else
+ pthread_rwlock_unlock(lock_.get());
+#endif
+}
+
+void ReadWriteLock::WriteUnlock() {
+ assert(lock_);
+#ifdef WIN32
+ ReleaseSRWLockExclusive(lock_.get());
+#else
+ pthread_rwlock_unlock(lock_.get());
+#endif
+}
+
+void ReadWriteLock::Destroy() {
+#ifndef WIN32
+ if (lock_ != nullptr)
+ pthread_rwlock_destroy(lock_.get());
+#endif
+}
+
+void swap(ReadWriteLock &left, ReadWriteLock &right) {
+ auto temp = std::move(left.lock_);
+ left.lock_ = std::move(right.lock_);
+ right.lock_ = std::move(temp);
+}
+
+} // namespace cru \ No newline at end of file
diff --git a/store/works/life/computer-network-experiment/ReadWriteLock.h b/store/works/life/computer-network-experiment/ReadWriteLock.h
new file mode 100644
index 0000000..ee40ac0
--- /dev/null
+++ b/store/works/life/computer-network-experiment/ReadWriteLock.h
@@ -0,0 +1,45 @@
+#pragma once
+#include "Common.h"
+
+#include <memory>
+
+#ifdef WIN32
+#include <Windows.h>
+#else
+#include <pthread.h>
+#endif
+
+namespace cru {
+class ReadWriteLock {
+ friend void swap(ReadWriteLock &left, ReadWriteLock &right);
+public:
+ ReadWriteLock();
+
+ ReadWriteLock(ReadWriteLock &&other);
+ ReadWriteLock &operator=(ReadWriteLock &&other);
+
+ ~ReadWriteLock();
+
+public:
+ void ReadLock();
+ void WriteLock();
+
+ bool ReadTryLock();
+ bool WriteTryLock();
+
+ void ReadUnlock();
+ void WriteUnlock();
+
+private:
+ void Destroy();
+
+private:
+#ifdef WIN32
+ std::unique_ptr<SRWLOCK> lock_;
+#else
+ std::unique_ptr<pthread_rwlock_t> lock_;
+#endif
+};
+
+void swap(ReadWriteLock &left, ReadWriteLock &right);
+} // namespace cru
diff --git a/store/works/life/computer-network-experiment/StringUtil.cpp b/store/works/life/computer-network-experiment/StringUtil.cpp
new file mode 100644
index 0000000..6bf906d
--- /dev/null
+++ b/store/works/life/computer-network-experiment/StringUtil.cpp
@@ -0,0 +1,355 @@
+#include "StringUtil.hpp"
+#include "Base.hpp"
+#include <string>
+
+namespace cru {
+namespace {
+template <typename UInt, int number_of_bit, typename ReturnType>
+inline std::enable_if_t<std::is_unsigned_v<UInt>, ReturnType>
+ExtractBits(UInt n) {
+ return static_cast<ReturnType>(n & ((1u << number_of_bit) - 1));
+}
+} // namespace
+
+CodePoint Utf8NextCodePoint(std::string_view str, Index current,
+ Index *next_position) {
+ CodePoint result;
+
+ if (current >= static_cast<Index>(str.length())) {
+ result = k_invalid_code_point;
+ } else {
+ const auto cu0 = static_cast<std::uint8_t>(str[current++]);
+
+ auto read_next_folowing_code = [&str, &current]() -> CodePoint {
+ if (current == static_cast<Index>(str.length()))
+ throw TextEncodeException(
+ "Unexpected end when read continuing byte of multi-byte code "
+ "point.");
+
+ const auto u = static_cast<std::uint8_t>(str[current]);
+ if (!(u & (1u << 7)) || (u & (1u << 6))) {
+ throw TextEncodeException(
+ "Unexpected bad-format (not 0b10xxxxxx) continuing byte of "
+ "multi-byte code point.");
+ }
+
+ return ExtractBits<std::uint8_t, 6, CodePoint>(str[current++]);
+ };
+
+ if ((1u << 7) & cu0) {
+ if ((1u << 6) & cu0) { // 2~4-length code point
+ if ((1u << 5) & cu0) { // 3~4-length code point
+ if ((1u << 4) & cu0) { // 4-length code point
+ if (cu0 & (1u << 3)) {
+ throw TextEncodeException(
+ "Unexpected bad-format begin byte (not 0b11110xxx) of 4-byte"
+ "code point.");
+ }
+
+ const CodePoint s0 = ExtractBits<std::uint8_t, 3, CodePoint>(cu0)
+ << (6 * 3);
+ const CodePoint s1 = read_next_folowing_code() << (6 * 2);
+ const CodePoint s2 = read_next_folowing_code() << 6;
+ const CodePoint s3 = read_next_folowing_code();
+ result = s0 + s1 + s2 + s3;
+ } else { // 3-length code point
+ const CodePoint s0 = ExtractBits<std::uint8_t, 4, CodePoint>(cu0)
+ << (6 * 2);
+ const CodePoint s1 = read_next_folowing_code() << 6;
+ const CodePoint s2 = read_next_folowing_code();
+ result = s0 + s1 + s2;
+ }
+ } else { // 2-length code point
+ const CodePoint s0 = ExtractBits<std::uint8_t, 5, CodePoint>(cu0)
+ << 6;
+ const CodePoint s1 = read_next_folowing_code();
+ result = s0 + s1;
+ }
+ } else {
+ throw TextEncodeException(
+ "Unexpected bad-format (0b10xxxxxx) begin byte of a code point.");
+ }
+ } else {
+ result = static_cast<CodePoint>(cu0);
+ }
+ }
+
+ if (next_position != nullptr)
+ *next_position = current;
+ return result;
+}
+
+CodePoint Utf16NextCodePoint(std::u16string_view str, Index current,
+ Index *next_position) {
+ CodePoint result;
+
+ if (current >= static_cast<Index>(str.length())) {
+ result = k_invalid_code_point;
+ } else {
+ const auto cu0 = str[current++];
+
+ if (!IsUtf16SurrogatePairCodeUnit(cu0)) { // 1-length code point
+ result = static_cast<CodePoint>(cu0);
+ } else if (IsUtf16SurrogatePairLeading(cu0)) { // 2-length code point
+ if (current >= static_cast<Index>(str.length())) {
+ throw TextEncodeException(
+ "Unexpected end when reading second code unit of surrogate pair.");
+ }
+ const auto cu1 = str[current++];
+
+ if (!IsUtf16SurrogatePairTrailing(cu1)) {
+ throw TextEncodeException(
+ "Unexpected bad-range second code unit of surrogate pair.");
+ }
+
+ const auto s0 = ExtractBits<std::uint16_t, 10, CodePoint>(cu0) << 10;
+ const auto s1 = ExtractBits<std::uint16_t, 10, CodePoint>(cu1);
+
+ result = s0 + s1 + 0x10000;
+
+ } else {
+ throw TextEncodeException(
+ "Unexpected bad-range first code unit of surrogate pair.");
+ }
+ }
+
+ if (next_position != nullptr)
+ *next_position = current;
+ return result;
+}
+
+CodePoint Utf16PreviousCodePoint(std::u16string_view str, Index current,
+ Index *previous_position) {
+ CodePoint result;
+ if (current <= 0) {
+ result = k_invalid_code_point;
+ } else {
+ const auto cu0 = str[--current];
+
+ if (!IsUtf16SurrogatePairCodeUnit(cu0)) { // 1-length code point
+ result = static_cast<CodePoint>(cu0);
+ } else if (IsUtf16SurrogatePairTrailing(cu0)) { // 2-length code point
+ if (current <= 0) {
+ throw TextEncodeException(
+ "Unexpected end when reading first code unit of surrogate pair.");
+ }
+ const auto cu1 = str[--current];
+
+ if (!IsUtf16SurrogatePairLeading(cu1)) {
+ throw TextEncodeException(
+ "Unexpected bad-range first code unit of surrogate pair.");
+ }
+
+ const auto s0 = ExtractBits<std::uint16_t, 10, CodePoint>(cu1) << 10;
+ const auto s1 = ExtractBits<std::uint16_t, 10, CodePoint>(cu0);
+
+ result = s0 + s1 + 0x10000;
+
+ } else {
+ throw TextEncodeException(
+ "Unexpected bad-range second code unit of surrogate pair.");
+ }
+ }
+
+ if (previous_position != nullptr)
+ *previous_position = current;
+ return result;
+}
+
+void Utf8EncodeCodePointAppend(CodePoint code_point, std::string &str) {
+ auto write_continue_byte = [&str](std::uint8_t byte6) {
+ str.push_back((1u << 7) + (((1u << 6) - 1) & byte6));
+ };
+
+ if (code_point >= 0 && code_point <= 0x007F) {
+ str.push_back(static_cast<char>(code_point));
+ } else if (code_point >= 0x0080 && code_point <= 0x07FF) {
+ std::uint32_t unsigned_code_point = code_point;
+ str.push_back(static_cast<char>(ExtractBits<std::uint32_t, 5, std::uint8_t>(
+ (unsigned_code_point >> 6)) +
+ 0b11000000));
+ write_continue_byte(
+ ExtractBits<std::uint32_t, 6, std::uint8_t>(unsigned_code_point));
+ } else if (code_point >= 0x0800 && code_point <= 0xFFFF) {
+ std::uint32_t unsigned_code_point = code_point;
+ str.push_back(static_cast<char>(ExtractBits<std::uint32_t, 4, std::uint8_t>(
+ (unsigned_code_point >> (6 * 2))) +
+ 0b11100000));
+ write_continue_byte(
+ ExtractBits<std::uint32_t, 6, std::uint8_t>(unsigned_code_point >> 6));
+ write_continue_byte(
+ ExtractBits<std::uint32_t, 6, std::uint8_t>(unsigned_code_point));
+ } else if (code_point >= 0x10000 && code_point <= 0x10FFFF) {
+ std::uint32_t unsigned_code_point = code_point;
+ str.push_back(static_cast<char>(ExtractBits<std::uint32_t, 3, std::uint8_t>(
+ (unsigned_code_point >> (6 * 3))) +
+ 0b11110000));
+ write_continue_byte(ExtractBits<std::uint32_t, 6, std::uint8_t>(
+ unsigned_code_point >> (6 * 2)));
+ write_continue_byte(
+ ExtractBits<std::uint32_t, 6, std::uint8_t>(unsigned_code_point >> 6));
+ write_continue_byte(
+ ExtractBits<std::uint32_t, 6, std::uint8_t>(unsigned_code_point));
+ } else {
+ throw TextEncodeException("Code point out of range.");
+ }
+}
+
+void Utf16EncodeCodePointAppend(CodePoint code_point, std::u16string &str) {
+ if ((code_point >= 0 && code_point <= 0xD7FF) ||
+ (code_point >= 0xE000 && code_point <= 0xFFFF)) {
+ str.push_back(static_cast<char16_t>(code_point));
+ } else if (code_point >= 0x10000 && code_point <= 0x10FFFF) {
+ std::uint32_t u = code_point - 0x10000;
+ str.push_back(static_cast<char16_t>(
+ ExtractBits<std::uint32_t, 10, std::uint32_t>(u >> 10) + 0xD800u));
+ str.push_back(static_cast<char16_t>(
+ ExtractBits<std::uint32_t, 10, std::uint32_t>(u) + 0xDC00u));
+ } else {
+ throw TextEncodeException("Code point out of range.");
+ }
+}
+
+std::string ToUtf8(std::u16string_view s) {
+ std::string result;
+ for (CodePoint cp : Utf16CodePointIterator{s}) {
+ Utf8EncodeCodePointAppend(cp, result);
+ }
+ return result;
+}
+
+std::u16string ToUtf16(std::string_view s) {
+ std::u16string result;
+ for (CodePoint cp : Utf8CodePointIterator{s}) {
+ Utf16EncodeCodePointAppend(cp, result);
+ }
+ return result;
+}
+
+#ifdef WIN32
+std::string ToUtf8(std::wstring_view s) {
+ std::u16string_view string{reinterpret_cast<const char16_t *>(s.data()),
+ s.size()};
+ std::string result;
+ for (CodePoint cp : Utf16CodePointIterator{string}) {
+ Utf8EncodeCodePointAppend(cp, result);
+ }
+ return result;
+}
+std::wstring ToUtf16WString(std::string_view s) {
+ std::u16string result;
+ for (CodePoint cp : Utf8CodePointIterator{s}) {
+ Utf16EncodeCodePointAppend(cp, result);
+ }
+
+ std::wstring r(result.cbegin(), result.cend());
+ return r;
+}
+#endif
+
+bool Utf16IsValidInsertPosition(std::u16string_view s, gsl::index position) {
+ if (position < 0)
+ return false;
+ if (position > static_cast<gsl::index>(s.size()))
+ return false;
+ if (position == 0)
+ return true;
+ if (position == static_cast<gsl::index>(s.size()))
+ return true;
+ return !IsUtf16SurrogatePairTrailing(s[position]);
+}
+
+gsl::index Utf16BackwardUntil(std::u16string_view str, gsl::index position,
+ const std::function<bool(CodePoint)> &predicate) {
+ if (position <= 0)
+ return position;
+ while (true) {
+ gsl::index p = position;
+ auto c = Utf16PreviousCodePoint(str, p, &position);
+ if (predicate(c))
+ return p;
+ if (c == k_invalid_code_point)
+ return p;
+ }
+ UnreachableCode();
+}
+
+gsl::index Utf16ForwardUntil(std::u16string_view str, gsl::index position,
+ const std::function<bool(CodePoint)> &predicate) {
+ if (position >= static_cast<gsl::index>(str.size()))
+ return position;
+ while (true) {
+ gsl::index p = position;
+ auto c = Utf16NextCodePoint(str, p, &position);
+ if (predicate(c))
+ return p;
+ if (c == k_invalid_code_point)
+ return p;
+ }
+ UnreachableCode();
+}
+
+inline bool IsSpace(CodePoint c) { return c == 0x20 || c == 0xA; }
+
+gsl::index Utf16PreviousWord(std::u16string_view str, gsl::index position,
+ bool *is_space) {
+ if (position <= 0)
+ return position;
+ auto c = Utf16PreviousCodePoint(str, position, nullptr);
+ if (IsSpace(c)) { // TODO: Currently only test against 0x20(space).
+ if (is_space)
+ *is_space = true;
+ return Utf16BackwardUntil(str, position,
+ [](CodePoint c) { return !IsSpace(c); });
+ } else {
+ if (is_space)
+ *is_space = false;
+ return Utf16BackwardUntil(str, position, IsSpace);
+ }
+}
+
+gsl::index Utf16NextWord(std::u16string_view str, gsl::index position,
+ bool *is_space) {
+ if (position >= static_cast<gsl::index>(str.size()))
+ return position;
+ auto c = Utf16NextCodePoint(str, position, nullptr);
+ if (IsSpace(c)) { // TODO: Currently only test against 0x20(space).
+ if (is_space)
+ *is_space = true;
+ return Utf16ForwardUntil(str, position,
+ [](CodePoint c) { return !IsSpace(c); });
+ } else {
+ if (is_space)
+ *is_space = false;
+ return Utf16ForwardUntil(str, position, IsSpace);
+ }
+}
+
+char16_t ToLower(char16_t c) {
+ if (c >= u'A' && c <= u'Z') {
+ return c - u'A' + u'a';
+ }
+ return c;
+}
+
+char16_t ToUpper(char16_t c) {
+ if (c >= u'a' && c <= u'z') {
+ return c - u'a' + u'A';
+ }
+ return c;
+}
+
+std::u16string ToLower(std::u16string_view s) {
+ std::u16string result;
+ for (auto c : s)
+ result.push_back(ToLower(c));
+ return result;
+}
+
+std::u16string ToUpper(std::u16string_view s) {
+ std::u16string result;
+ for (auto c : s)
+ result.push_back(ToUpper(c));
+ return result;
+}
+} // namespace cru
diff --git a/store/works/life/computer-network-experiment/StringUtil.hpp b/store/works/life/computer-network-experiment/StringUtil.hpp
new file mode 100644
index 0000000..b0ca675
--- /dev/null
+++ b/store/works/life/computer-network-experiment/StringUtil.hpp
@@ -0,0 +1,158 @@
+#pragma once
+#include "Base.hpp"
+
+#include <functional>
+#include <string>
+#include <string_view>
+
+namespace cru {
+using CodePoint = std::int32_t;
+constexpr CodePoint k_invalid_code_point = -1;
+
+class TextEncodeException : public std::runtime_error {
+ public:
+ using runtime_error::runtime_error;
+};
+
+inline bool IsUtf16SurrogatePairCodeUnit(char16_t c) {
+ return c >= 0xD800 && c <= 0xDFFF;
+}
+
+inline bool IsUtf16SurrogatePairLeading(char16_t c) {
+ return c >= 0xD800 && c <= 0xDBFF;
+}
+
+inline bool IsUtf16SurrogatePairTrailing(char16_t c) {
+ return c >= 0xDC00 && c <= 0xDFFF;
+}
+
+CodePoint Utf8NextCodePoint(std::string_view str, Index current,
+ Index* next_position);
+
+CodePoint Utf16NextCodePoint(std::u16string_view str, Index current,
+ Index* next_position);
+CodePoint Utf16PreviousCodePoint(std::u16string_view str, Index current,
+ Index* previous_position);
+
+template <typename StringType>
+using NextCodePointFunctionType = CodePoint (*)(StringType, Index, Index*);
+
+template <typename StringType,
+ NextCodePointFunctionType<StringType> NextCodePointFunction>
+class CodePointIterator {
+ public:
+ using difference_type = Index;
+ using value_type = CodePoint;
+ using pointer = void;
+ using reference = value_type;
+ using iterator_category = std::forward_iterator_tag;
+
+ public:
+ struct past_end_tag_t {};
+
+ explicit CodePointIterator(StringType string)
+ : string_(std::move(string)), position_(0) {}
+ explicit CodePointIterator(StringType string, past_end_tag_t)
+ : string_(std::move(string)), position_(string_.size()) {}
+
+ CRU_DEFAULT_COPY(CodePointIterator)
+ CRU_DEFAULT_MOVE(CodePointIterator)
+
+ ~CodePointIterator() = default;
+
+ public:
+ StringType GetString() const { return string_; }
+ Index GetPosition() const { return position_; }
+
+ bool IsPastEnd() const {
+ return position_ == static_cast<Index>(string_.size());
+ }
+
+ public:
+ CodePointIterator begin() const { return *this; }
+ CodePointIterator end() const {
+ return CodePointIterator{string_, past_end_tag_t{}};
+ }
+
+ public:
+ bool operator==(const CodePointIterator& other) const {
+ // You should compare iterator that iterate on the same string.
+ Expects(this->string_.data() == other.string_.data() &&
+ this->string_.size() == other.string_.size());
+ return this->position_ == other.position_;
+ }
+ bool operator!=(const CodePointIterator& other) const {
+ return !this->operator==(other);
+ }
+
+ CodePointIterator& operator++() {
+ Expects(!IsPastEnd());
+ Forward();
+ return *this;
+ }
+
+ CodePointIterator operator++(int) {
+ Expects(!IsPastEnd());
+ CodePointIterator old = *this;
+ Forward();
+ return old;
+ }
+
+ CodePoint operator*() const {
+ return NextCodePointFunction(string_, position_, &next_position_cache_);
+ }
+
+ private:
+ void Forward() {
+ if (next_position_cache_ > position_) {
+ position_ = next_position_cache_;
+ } else {
+ NextCodePointFunction(string_, position_, &position_);
+ }
+ }
+
+ private:
+ StringType string_;
+ Index position_;
+ mutable Index next_position_cache_;
+};
+
+using Utf8CodePointIterator =
+ CodePointIterator<std::string_view, &Utf8NextCodePoint>;
+
+using Utf16CodePointIterator =
+ CodePointIterator<std::u16string_view, &Utf16NextCodePoint>;
+
+void Utf8EncodeCodePointAppend(CodePoint code_point, std::string& str);
+void Utf16EncodeCodePointAppend(CodePoint code_point, std::u16string& str);
+
+std::string ToUtf8(std::u16string_view s);
+std::u16string ToUtf16(std::string_view s);
+
+#ifdef WIN32
+std::string ToUtf8(std::wstring_view s);
+std::wstring ToUtf16WString(std::string_view s);
+#endif
+
+// If given s is not a valid utf16 string, return value is UD.
+bool Utf16IsValidInsertPosition(std::u16string_view s, gsl::index position);
+
+// Return position after the character making predicate returns true or 0 if no
+// character doing so.
+gsl::index Utf16BackwardUntil(std::u16string_view str, gsl::index position,
+ const std::function<bool(CodePoint)>& predicate);
+// Return position before the character making predicate returns true or
+// str.size() if no character doing so.
+gsl::index Utf16ForwardUntil(std::u16string_view str, gsl::index position,
+ const std::function<bool(CodePoint)>& predicate);
+
+gsl::index Utf16PreviousWord(std::u16string_view str, gsl::index position,
+ bool* is_space = nullptr);
+gsl::index Utf16NextWord(std::u16string_view str, gsl::index position,
+ bool* is_space = nullptr);
+
+char16_t ToLower(char16_t c);
+char16_t ToUpper(char16_t c);
+std::u16string ToLower(std::u16string_view s);
+std::u16string ToUpper(std::u16string_view s);
+} // namespace cru
diff --git a/store/works/life/computer-network-experiment/TODO.md b/store/works/life/computer-network-experiment/TODO.md
new file mode 100644
index 0000000..248a2d7
--- /dev/null
+++ b/store/works/life/computer-network-experiment/TODO.md
@@ -0,0 +1,6 @@
+1. Apply read-write lock to connections. ✅
+2. Remove dead connection from connection list. ✅
+3. Handle SIGINT gracefully.
+4. Add close method to protocol.
+5. Add help and exit command.
+6. Use multiprocess to show output.
diff --git a/store/works/life/computer-network-experiment/client.cpp b/store/works/life/computer-network-experiment/client.cpp
new file mode 100644
index 0000000..73ae52f
--- /dev/null
+++ b/store/works/life/computer-network-experiment/client.cpp
@@ -0,0 +1,103 @@
+/** Created by crupest.
+ * This is the client program.
+ */
+
+#include "Common.h"
+#include "IO.h"
+
+#include <folly/CancellationToken.h>
+#include <folly/ProducerConsumerQueue.h>
+
+#ifdef WIN32
+#include <Windows.h>
+#include <winsock.h>
+#else
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#endif
+
+const auto connect_address = "127.0.0.1"; // control connect address
+const u_short port = 1234; // control connect port
+
+namespace {
+folly::ProducerConsumerQueue<std::string> send_queue(100);
+folly::CancellationSource cancellation_source;
+} // namespace
+
+void PrintHelp() {
+ SendOutput(CRUT("Input anything to send to server. Or just enter to receive "
+ "lastest messages from server.\n"));
+}
+
+void OnInputLine(StringView line) {
+ if (line.empty()) {
+ return;
+ } else {
+ send_queue.write(ConvertCharStringBack(line) + '\n');
+ }
+}
+
+int Main() {
+ int client_socket;
+
+ if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+ PrintErrorMessageAndExit(CRUT("Failed to create socket!\n"));
+ }
+
+ sockaddr_in server_address;
+
+ server_address.sin_family = AF_INET;
+ server_address.sin_port = htons(port);
+ server_address.sin_addr.s_addr = inet_addr(connect_address);
+ memset(&(server_address.sin_zero), 0, sizeof(server_address.sin_zero));
+
+ if (connect(client_socket, (sockaddr *)&server_address, sizeof(sockaddr)) ==
+ -1) {
+ PrintErrorMessageAndExit(CRUT("Failed to connect!"));
+ }
+
+ output_stream << CRUT("Please input your name:\n> ");
+ String name = ReadInputLine();
+
+ PrintHelp();
+
+ StartIOThread();
+
+ name.push_back(CRUT('\n'));
+ auto name_data = ConvertCharStringBack(name);
+ SafeSend(client_socket, name_data);
+
+ std::thread receive_thread([client_socket] {
+ std::string rest;
+ while (true) {
+ if (cancellation_source.isCancellationRequested()) {
+ break;
+ }
+
+ std::string data;
+ if (!SafeReadUntil(client_socket, '\n', data, rest)) {
+ PrintErrorMessageAndExit(CRUT("Failed to receive message.\n"));
+ }
+
+ SendOutput(CRUT("Recived a message:\n{}\n"), ConvertCharString(data));
+ }
+ });
+ receive_thread.detach();
+
+ while (true) {
+ if (cancellation_source.isCancellationRequested()) {
+ break;
+ }
+
+ std::string s;
+ if (send_queue.read(s)) {
+ if (!SafeSend(client_socket, s)) {
+ PrintErrorMessageAndExit(CRUT("Failed to send message to server."));
+ }
+ }
+ }
+
+ CloseSocket(client_socket);
+ return 0;
+}
diff --git a/store/works/life/computer-network-experiment/server.cpp b/store/works/life/computer-network-experiment/server.cpp
new file mode 100644
index 0000000..065687c
--- /dev/null
+++ b/store/works/life/computer-network-experiment/server.cpp
@@ -0,0 +1,261 @@
+/** Created by crupest.
+ * This is the server program.
+ */
+
+#include "Common.h"
+#include "IO.h"
+#include "ReadWriteLock.h"
+
+#include <folly/CancellationToken.h>
+#include <folly/ProducerConsumerQueue.h>
+
+#include <algorithm>
+#include <memory>
+#include <optional>
+#include <stdint.h>
+#include <thread>
+
+#ifdef WIN32
+#include <Windows.h>
+#include <winsock.h>
+#else
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#endif
+
+const auto bind_address = "127.0.0.1"; // control bind address
+const u_short port = 1234; // control bind port
+
+namespace {
+void PrintHelp() {
+ SendOutput(CRUT(
+ "Input and run one of following command:\n\t> NOTHING -> Continue and "
+ "print new messages.\n\t> list -> List all connected client.\n\t> send "
+ "[i] [message] -> Send messages to client with number i.\n"));
+}
+} // namespace
+
+struct Connection {
+ int id;
+ std::thread thread;
+ std::thread receive_thread;
+ int socket;
+ sockaddr_in address;
+ String address_string;
+ String name;
+ folly::ProducerConsumerQueue<std::string> send_queue{100};
+ folly::CancellationSource cancellation_source;
+};
+
+namespace {
+cru::ReadWriteLock connections_lock;
+std::vector<std::unique_ptr<Connection>> connections;
+
+void RemoveConnection(int id) {
+ connections_lock.WriteLock();
+ connections.erase(
+ std::remove_if(connections.begin(), connections.end(),
+ [id](const std::unique_ptr<Connection> &connection) {
+ return connection->id == id;
+ }),
+ connections.end());
+
+ connections_lock.WriteUnlock();
+}
+
+void PrintConnections() {
+ connections_lock.ReadLock();
+ if (connections.empty()) {
+ SendOutput(CRUT("Currently there is no connection.\n"));
+ }
+
+ String s;
+ for (const auto &connection : connections) {
+ s += fmt::format(CRUT("{}: {}({})\n"), connection->id, connection->name,
+ connection->address_string);
+ }
+ SendOutput(s);
+ connections_lock.ReadUnlock();
+}
+} // namespace
+
+void ResponseThreadProc(Connection *connection) {
+ auto host = ConvertCharString(inet_ntoa(connection->address.sin_addr));
+ auto port = htons(connection->address.sin_port);
+ connection->address_string = fmt::format(CRUT("{}:{}"), host, port);
+
+ std::string rest;
+ std::string name_data;
+ if (!SafeReadUntil(connection->socket, '\n', name_data, rest)) {
+ SendOutput(OutputType::Error, CRUT("Failed to read name of {}.\n"),
+ connection->address_string);
+ CloseSocket(connection->socket);
+ return;
+ }
+
+ connection->name = ConvertCharString(name_data);
+ SendOutput(OutputColor::Green, CRUT("Connected to {}, whose name is {}.\n"),
+ connection->address_string, connection->name);
+
+ connection->receive_thread = std::thread(
+ [](Connection *connection) {
+ std::string rest;
+ while (true) {
+ if (connection->cancellation_source.isCancellationRequested()) {
+ break;
+ }
+
+ std::string data;
+
+ if (!SafeReadUntil(connection->socket, '\n', data, rest)) {
+ SendOutput(OutputType::Error,
+ CRUT("Failed read data from socket of {}({}).\n"),
+ connection->name, connection->address_string);
+ connection->cancellation_source.requestCancellation();
+ return;
+ }
+
+ SendOutput(CRUT("{}({}) send a message:\n{}\n"), connection->name,
+ connection->address_string, ConvertCharString(data));
+ }
+ },
+ connection);
+ connection->receive_thread.detach();
+
+ while (true) {
+ if (connection->cancellation_source.isCancellationRequested()) {
+ break;
+ }
+
+ std::string s;
+ if (connection->send_queue.read(s)) {
+ if (!SafeSend(connection->socket, s)) {
+ SendOutput(OutputType::Error, CRUT("Failed send data to {}({}).\n"),
+ connection->name, connection->address_string);
+ connection->cancellation_source.requestCancellation();
+ break;
+ }
+ }
+ }
+
+ CloseSocket(connection->socket);
+
+ RemoveConnection(connection->id);
+}
+
+void OnInputLine(StringView line) {
+ StringStream ss{String(line)};
+
+ ss >> std::ws;
+ if (ss.eof())
+ return;
+
+ String command;
+ ss >> command;
+
+ if (command == CRUT("list")) {
+ if (!ss.eof()) {
+ SendOutput(OutputType::Error,
+ CRUT("List command can't have arguments!\n"));
+ PrintHelp();
+ } else {
+ PrintConnections();
+ }
+ return;
+ } else if (command == CRUT("send")) {
+ int id;
+ ss >> id;
+ if (!ss) {
+ SendOutput(OutputType::Error, CRUT("Send format error!\n"));
+ PrintHelp();
+ return;
+ }
+
+ String message;
+ getline(ss, message);
+
+ if (message.empty()) {
+ SendOutput(OutputType::Error, CRUT("Send message can't be empty.!\n"));
+ PrintHelp();
+ return;
+ }
+
+ auto i = std::find_if(
+ connections.begin(), connections.end(),
+ [id](const std::unique_ptr<Connection> &c) { return c->id == id; });
+
+ if (i == connections.end()) {
+ SendOutput(OutputType::Error, CRUT("No connection with such id.\n"));
+ return;
+ }
+
+ (*i)->send_queue.write(ConvertCharStringBack(message) + "\n");
+ return;
+ } else {
+ SendOutput(OutputType::Error, CRUT("Unkown command!\n"));
+ PrintHelp();
+ return;
+ }
+}
+
+int Main() {
+ int server_socket;
+
+ if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+ PrintErrorMessageAndExit(CRUT("Failed to create socket."));
+ }
+
+ sockaddr_in server_address;
+
+ server_address.sin_family = AF_INET;
+ server_address.sin_port = htons(port);
+ server_address.sin_addr.s_addr = inet_addr(bind_address);
+ memset(&(server_address.sin_zero), 0, sizeof(server_address.sin_zero));
+
+ if (bind(server_socket, reinterpret_cast<sockaddr *>(&server_address),
+ sizeof(sockaddr_in)) == -1) {
+ PrintErrorMessageAndExit(CRUT("Failed to bind."));
+ }
+
+ if (listen(server_socket, SOMAXCONN) == -1) {
+ PrintErrorMessageAndExit(CRUT("Failed to listen."));
+ }
+
+ SendOutput(OutputColor::Green,
+ CRUT("Now start to accept incoming connection.\n"));
+
+ PrintHelp();
+
+ StartIOThread();
+
+ int current_id = 1;
+
+ while (true) {
+ sockaddr_in client_address;
+ int client_socket;
+ unsigned sin_size = sizeof(sockaddr_in);
+ client_socket =
+ accept(server_socket, reinterpret_cast<sockaddr *>(&client_address),
+#ifdef WIN32
+ reinterpret_cast<int *>
+#endif
+ (&sin_size));
+
+ if (client_socket == -1) {
+ PrintErrorMessageAndExit(CRUT("Failed to accecpt."));
+ }
+
+ connections_lock.WriteLock();
+ connections.push_back(std::make_unique<Connection>());
+ const std::unique_ptr<Connection> &connection = connections.back();
+
+ connection->id = current_id++;
+ connection->socket = client_socket;
+ connection->address = client_address;
+ connection->thread =
+ std::thread(ResponseThreadProc, connections.back().get());
+ connection->thread.detach();
+ connections_lock.WriteUnlock();
+ }
+}