From 99e2e923d0c77b02f3fb4ff648ea916954868606 Mon Sep 17 00:00:00 2001 From: Yuqian Yang Date: Fri, 28 Feb 2025 23:13:39 +0800 Subject: chore(store): move everything to store. --- .../life/computer-network-experiment/.gitignore | 5 + .../.vscode/launch.json | 16 + .../life/computer-network-experiment/Base.hpp | 59 ++++ .../computer-network-experiment/CMakeLists.txt | 23 ++ .../life/computer-network-experiment/Common.cpp | 142 +++++++++ .../life/computer-network-experiment/Common.h | 54 ++++ .../works/life/computer-network-experiment/IO.cpp | 81 +++++ store/works/life/computer-network-experiment/IO.h | 68 ++++ .../life/computer-network-experiment/PreConfig.hpp | 6 + .../computer-network-experiment/ReadWriteLock.cpp | 97 ++++++ .../computer-network-experiment/ReadWriteLock.h | 45 +++ .../computer-network-experiment/StringUtil.cpp | 355 +++++++++++++++++++++ .../computer-network-experiment/StringUtil.hpp | 158 +++++++++ .../works/life/computer-network-experiment/TODO.md | 6 + .../life/computer-network-experiment/client.cpp | 103 ++++++ .../life/computer-network-experiment/server.cpp | 261 +++++++++++++++ 16 files changed, 1479 insertions(+) create mode 100644 store/works/life/computer-network-experiment/.gitignore create mode 100644 store/works/life/computer-network-experiment/.vscode/launch.json create mode 100644 store/works/life/computer-network-experiment/Base.hpp create mode 100644 store/works/life/computer-network-experiment/CMakeLists.txt create mode 100644 store/works/life/computer-network-experiment/Common.cpp create mode 100644 store/works/life/computer-network-experiment/Common.h create mode 100644 store/works/life/computer-network-experiment/IO.cpp create mode 100644 store/works/life/computer-network-experiment/IO.h create mode 100644 store/works/life/computer-network-experiment/PreConfig.hpp create mode 100644 store/works/life/computer-network-experiment/ReadWriteLock.cpp create mode 100644 store/works/life/computer-network-experiment/ReadWriteLock.h create mode 100644 store/works/life/computer-network-experiment/StringUtil.cpp create mode 100644 store/works/life/computer-network-experiment/StringUtil.hpp create mode 100644 store/works/life/computer-network-experiment/TODO.md create mode 100644 store/works/life/computer-network-experiment/client.cpp create mode 100644 store/works/life/computer-network-experiment/server.cpp (limited to 'store/works/life/computer-network-experiment') 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 +#include +#include + +#define CRU_UNUSED(entity) static_cast(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 +inline void hash_combine(std::size_t& s, const T& v) { + std::hash 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 + +#ifdef WIN32 +#include +#include +#else +#include +#include +#include +#include +#endif + +#include + +[[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 +#include +#include +#include + +#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 + +#include +#include +#include +#include + +folly::MPMCQueue output_queue(100); + +namespace { +folly::CancellationSource cancellation_source; +std::thread io_thread; +} + +void PrintOutput(const Output &output) { + std::basic_ostream *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 +#include +#include +#include + +#include +#include + +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_queue; + +inline void SendOutput(Output output) { + output_queue.blockingWrite(std::move(output)); +} + +inline void SendOutput(String output) { SendOutput(Output{std::move(output)}); } + +template void SendOutput(StringView format, Args &&...args) { + output_queue.blockingWrite(fmt::format(format, std::forward(args)...)); +} + +template +void SendOutput(OutputType type, StringView format, Args &&...args) { + output_queue.blockingWrite( + Output{fmt::format(format, std::forward(args)...), type}); +} + +template +void SendOutput(OutputColor color, StringView format, Args &&...args) { + output_queue.blockingWrite( + Output{fmt::format(format, std::forward(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 +#include + +namespace cru { +ReadWriteLock::ReadWriteLock() { +#ifdef WIN32 + lock_ = std::make_unique(); + 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 + +#ifdef WIN32 +#include +#else +#include +#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 lock_; +#else + std::unique_ptr 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 + +namespace cru { +namespace { +template +inline std::enable_if_t, ReturnType> +ExtractBits(UInt n) { + return static_cast(n & ((1u << number_of_bit) - 1)); +} +} // namespace + +CodePoint Utf8NextCodePoint(std::string_view str, Index current, + Index *next_position) { + CodePoint result; + + if (current >= static_cast(str.length())) { + result = k_invalid_code_point; + } else { + const auto cu0 = static_cast(str[current++]); + + auto read_next_folowing_code = [&str, ¤t]() -> CodePoint { + if (current == static_cast(str.length())) + throw TextEncodeException( + "Unexpected end when read continuing byte of multi-byte code " + "point."); + + const auto u = static_cast(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(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(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(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(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(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(str.length())) { + result = k_invalid_code_point; + } else { + const auto cu0 = str[current++]; + + if (!IsUtf16SurrogatePairCodeUnit(cu0)) { // 1-length code point + result = static_cast(cu0); + } else if (IsUtf16SurrogatePairLeading(cu0)) { // 2-length code point + if (current >= static_cast(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(cu0) << 10; + const auto s1 = ExtractBits(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(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(cu1) << 10; + const auto s1 = ExtractBits(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(code_point)); + } else if (code_point >= 0x0080 && code_point <= 0x07FF) { + std::uint32_t unsigned_code_point = code_point; + str.push_back(static_cast(ExtractBits( + (unsigned_code_point >> 6)) + + 0b11000000)); + write_continue_byte( + ExtractBits(unsigned_code_point)); + } else if (code_point >= 0x0800 && code_point <= 0xFFFF) { + std::uint32_t unsigned_code_point = code_point; + str.push_back(static_cast(ExtractBits( + (unsigned_code_point >> (6 * 2))) + + 0b11100000)); + write_continue_byte( + ExtractBits(unsigned_code_point >> 6)); + write_continue_byte( + ExtractBits(unsigned_code_point)); + } else if (code_point >= 0x10000 && code_point <= 0x10FFFF) { + std::uint32_t unsigned_code_point = code_point; + str.push_back(static_cast(ExtractBits( + (unsigned_code_point >> (6 * 3))) + + 0b11110000)); + write_continue_byte(ExtractBits( + unsigned_code_point >> (6 * 2))); + write_continue_byte( + ExtractBits(unsigned_code_point >> 6)); + write_continue_byte( + ExtractBits(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(code_point)); + } else if (code_point >= 0x10000 && code_point <= 0x10FFFF) { + std::uint32_t u = code_point - 0x10000; + str.push_back(static_cast( + ExtractBits(u >> 10) + 0xD800u)); + str.push_back(static_cast( + ExtractBits(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(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(s.size())) + return false; + if (position == 0) + return true; + if (position == static_cast(s.size())) + return true; + return !IsUtf16SurrogatePairTrailing(s[position]); +} + +gsl::index Utf16BackwardUntil(std::u16string_view str, gsl::index position, + const std::function &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 &predicate) { + if (position >= static_cast(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(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 +#include +#include + +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 +using NextCodePointFunctionType = CodePoint (*)(StringType, Index, Index*); + +template 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(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; + +using Utf16CodePointIterator = + CodePointIterator; + +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& 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& 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 +#include + +#ifdef WIN32 +#include +#include +#else +#include +#include +#include +#endif + +const auto connect_address = "127.0.0.1"; // control connect address +const u_short port = 1234; // control connect port + +namespace { +folly::ProducerConsumerQueue 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 +#include + +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#include +#else +#include +#include +#include +#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 send_queue{100}; + folly::CancellationSource cancellation_source; +}; + +namespace { +cru::ReadWriteLock connections_lock; +std::vector> connections; + +void RemoveConnection(int id) { + connections_lock.WriteLock(); + connections.erase( + std::remove_if(connections.begin(), connections.end(), + [id](const std::unique_ptr &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 &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(&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(&client_address), +#ifdef WIN32 + reinterpret_cast +#endif + (&sin_size)); + + if (client_socket == -1) { + PrintErrorMessageAndExit(CRUT("Failed to accecpt.")); + } + + connections_lock.WriteLock(); + connections.push_back(std::make_unique()); + const std::unique_ptr &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(); + } +} -- cgit v1.2.3