easy-asocket 0.1.2
A user-friendly asynchronous socket wrapper with a simple communication protocol, currently available for Python and C++.
Requires Python: >=3.11
Keywords: image, annotation, labeling, gui

# easy-asocket

A user-friendly asynchronous socket wrapper with a simple communication protocol, currently available for Python and C++.

## Version

**Current Version: 0.1.2**

## Changelog

### [0.1.2] - Current

- Add support for Boost 1.87 and later.

## Features

- **Asynchronous I/O**: Built on Python's `asyncio` for high-performance network communication
- **Simple Protocol**: Lightweight application-layer protocol with CRC16 checksum validation
- **Request-Response Pattern**: Built-in support for request-response communication with automatic message ID management
- **Multi-Client Server**: Server can handle multiple concurrent client connections
- **Cross-Language Support**: Available for both Python and C++

## Installation

```bash
pip install easy-asocket
```

## Quick Start

### Server Example

```python
import asyncio
from easy_asocket import SocketServer, SimpleProtocol

def on_request(pkg: SimpleProtocol, client_id: int):
    """Callback function to handle received requests"""
    print(f'Received data from client {client_id}:')
    print(f'  Message ID: {pkg.get_msgid()}')
    print(f'  CMD: {pkg.get_cmd()}')
    print(f'  Data: {pkg.get_data()}')
    
    # Create and send response
    resp = SimpleProtocol()
    resp.set_msgid(pkg.get_msgid())  # Use same message ID
    resp.set_cmd(pkg.get_cmd())      # Use same command
    resp.set_data(b'Response data')  # Set response data
    
    # Send response asynchronously
    asyncio.create_task(server.response(resp, client_id))

async def main():
    server = SocketServer(22334)
    server.set_request_callback(on_request)
    await server.start()

if __name__ == "__main__":
    asyncio.run(main())
```

### Client Example

```python
import asyncio
import easy_asocket as eas

def on_request(pkg: eas.SimpleProtocol):
    """Callback function to handle received requests from server"""
    print(f"Received request:")
    print(f"  Message ID: {pkg.get_msgid()}")
    print(f"  CMD: {pkg.get_cmd()}")
    print(f"  Data: {pkg.get_data()}")

async def main():
    client = eas.SocketClient("127.0.0.1", 22334)
    
    # Set callback to handle received requests
    client.set_request_callback(on_request)
    
    # Connect to server
    await client.connect()
    
    # Send a request and wait for response
    pkg = eas.SimpleProtocol()
    pkg.set_cmd(0x0001)
    pkg.set_data(b'Hello, Server!')
    
    success, response = await client.request(pkg, expect_response=True, timeout=5.0)
    if success:
        print(f"Response received: {response.get_data()}")
    
    # Keep connection alive
    await asyncio.sleep(3600)  # Keep running for 1 hour

if __name__ == "__main__":
    asyncio.run(main())
```

## API Reference

### SocketServer

Server class for accepting multiple client connections.

#### Methods

- `__init__(port: int)`: Initialize server on specified port
- `set_request_callback(callback: Callable[[SimpleProtocol, int], None])`: Set callback function to handle incoming requests. The callback receives the protocol package and client ID.
- `async start()`: Start the server and begin accepting connections
- `async stop()`: Stop the server and close all connections
- `get_clients() -> dict[int, ClientSession]`: Get a snapshot of all connected clients
- `async response(pkg: SimpleProtocol, client_id: int)`: Send a response to a specific client
- `async request(pkg: SimpleProtocol, client_id: int, expect_response: bool = False, timeout: float = 5.0)`: Send a request to a specific client

### SocketClient

Client class for connecting to a server.

#### Methods

- `__init__(host: str, port: int)`: Initialize client with server address
- `set_request_callback(callback: Callable[[SimpleProtocol], None])`: Set callback function to handle incoming requests from server
- `async connect()`: Connect to the server
- `async disconnect()`: Disconnect from the server
- `async request(pkg: SimpleProtocol, expect_response: bool = False, timeout: float = 5.0) -> Tuple[bool, SimpleProtocol]`: Send a request. Returns `(success, response)` tuple.
- `async response(pkg: SimpleProtocol)`: Send a response

### SimpleProtocol

Protocol class for encoding and decoding messages.

#### Methods

- `set_msgid(msg_id: int)`: Set message ID
- `get_msgid() -> int`: Get message ID
- `set_cmd(cmd: int)`: Set command code
- `get_cmd() -> int`: Get command code
- `set_direction(direction: int)`: Set direction (DIRECTION_REQUEST or DIRECTION_RESPONSE)
- `get_direction() -> int`: Get direction
- `set_data(data: bytes)`: Set data payload
- `get_data() -> bytes`: Get data payload (reference)
- `get_data_copy() -> bytes`: Get data payload (copy)
- `encode() -> bytes`: Encode protocol data into byte array
- `decode(data: bytes) -> bool`: Decode byte array into protocol data. Returns `True` if successful.

#### Constants

- `DIRECTION_REQUEST = 0x00`: Request direction
- `DIRECTION_RESPONSE = 0x01`: Response direction

## Protocol Format

The SimpleProtocol uses the following structure:

```
[Header(2)][Direction(1)][MessageID(2)][Length(4)][CMD(2)][Data(N)][CRC16(2)][Footer(2)]
```

- **Header**: `0xAA 0x55`
- **Direction**: `0x00` (request) or `0x01` (response)
- **MessageID**: 16-bit unsigned integer
- **Length**: 32-bit unsigned integer (data length)
- **CMD**: 16-bit unsigned integer (command code)
- **Data**: Variable length byte array
- **CRC16**: 16-bit CRC checksum
- **Footer**: `0x55 0xAA`

## C++ Support

The library also provides C++ headers and implementation files with the same API as Python. To get the C++ include directory path:

```bash
easy-asocket-cppdir
```

This will print the path to the C++ headers directory, which you can use in your CMake configuration.

### C++ Server Example

```cpp
#include "simple_protocol.hpp"
#include "simple_protocol_impl.hpp"
#include "easy_asocket.hpp"
#include "easy_asocket_impl.hpp"
#include <boost/asio.hpp>
#include <iostream>
#include <thread>

int main() {
    auto io_ctx = EasyASocket::get_global_io_ctx();
    EasyASocket::SocketServer server(22334, io_ctx);
    
    // Set callback to handle incoming requests
    server.set_request_callback([](EasyASocket::SimpleProtocol &pkg, size_t client_id) {
        std::cout << "Received request from client " << client_id << std::endl;
        std::cout << "  Message ID: " << pkg.get_msgid() << std::endl;
        std::cout << "  CMD: " << pkg.get_cmd() << std::endl;
        
        // Create and send response
        EasyASocket::SimpleProtocol resp;
        resp.set_msgid(pkg.get_msgid());
        resp.set_cmd(pkg.get_cmd());
        std::vector<uint8_t> resp_data{'R', 'e', 's', 'p', 'o', 'n', 's', 'e'};
        resp.set_data(resp_data);
        
        server.response(resp, client_id);
    });
    
    server.start();
    EasyASocket::run_ctx_backend();
    
    // Keep running
    std::this_thread::sleep_for(std::chrono::hours(1));
    return 0;
}
```

### C++ Client Example

```cpp
#include "simple_protocol.hpp"
#include "simple_protocol_impl.hpp"
#include "easy_asocket.hpp"
#include "easy_asocket_impl.hpp"
#include <boost/asio.hpp>
#include <iostream>
#include <thread>

int main() {
    auto io_ctx = EasyASocket::get_global_io_ctx();
    EasyASocket::SocketClient client("127.0.0.1", 22334, io_ctx);
    
    // Set callback to handle incoming requests
    client.set_request_callback([](EasyASocket::SimpleProtocol &pkg) {
        std::cout << "Received request from server" << std::endl;
        std::cout << "  Message ID: " << pkg.get_msgid() << std::endl;
        std::cout << "  CMD: " << pkg.get_cmd() << std::endl;
    });
    
    // Wait for connection
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    
    // Send a request and wait for response
    EasyASocket::SimpleProtocol pkg;
    pkg.set_cmd(0x0001);
    std::vector<uint8_t> data{'H', 'e', 'l', 'l', 'o'};
    pkg.set_data(data);
    
    auto [success, response] = client.request(pkg, true, 5000);
    if (success) {
        std::cout << "Response received" << std::endl;
    }
    
    // Keep running
    std::this_thread::sleep_for(std::chrono::hours(1));
    return 0;
}
```

### Dependencies

The C++ implementation requires **Boost.Asio** library. Make sure you have Boost installed on your system before using the C++ headers.

#### Installing Boost

**Ubuntu/Debian:**
```bash
sudo apt-get install libboost-all-dev
```

**macOS (using Homebrew):**
```bash
brew install boost
```

**Windows (using vcpkg):**
```bash
vcpkg install boost-asio
```

#### CMake Configuration

When using CMake, you can find Boost and link against it:

```cmake
find_package(Boost REQUIRED COMPONENTS system)
target_link_libraries(your_target PRIVATE Boost::system)
```

### C++ API Reference

The C++ API is designed to match the Python API as closely as possible. All classes and methods are in the `EasyASocket` namespace.

#### SocketServer

- `SocketServer(uint16_t port, std::shared_ptr<asio::io_context> io_ctx = nullptr)`: Initialize server on specified port
- `void start()`: Start the server and begin accepting connections
- `void stop()`: Stop the server and close all connections
- `void set_request_callback(std::function<void(SimpleProtocol &p, size_t client_id)> callback)`: Set callback function to handle incoming requests
- `std::unordered_map<size_t, std::shared_ptr<ClientSession>> get_clients() const`: Get a snapshot of all connected clients
- `void response(SimpleProtocol &p, size_t client_id)`: Send a response to a specific client
- `std::tuple<bool, SimpleProtocol> request(SimpleProtocol &p, size_t client_id, bool expect_response = false, uint32_t timeout = 5000)`: Send a request to a specific client

#### SocketClient

- `SocketClient(std::string host, uint16_t port, std::shared_ptr<asio::io_context> io_ctx = nullptr)`: Initialize client with server address (automatically connects)
- `void set_request_callback(std::function<void(SimpleProtocol &p)> callback)`: Set callback function to handle incoming requests from server
- `std::tuple<bool, SimpleProtocol> request(SimpleProtocol &p, bool expect_response = false, uint32_t timeout = 5000)`: Send a request. Returns `(success, response)` tuple.
- `void response(SimpleProtocol &p)`: Send a response
- `bool is_connected() const`: Check if the client is connected

#### SimpleProtocol

- `void set_msgid(uint16_t msg_id)`: Set message ID
- `uint16_t get_msgid()`: Get message ID
- `void set_cmd(uint16_t cmd)`: Set command code
- `uint16_t get_cmd()`: Get command code
- `void set_direction(uint8_t direction)`: Set direction (DIRECTION_REQUEST or DIRECTION_RESPONSE)
- `uint8_t get_direction()`: Get direction
- `void set_data(std::vector<uint8_t> data)`: Set data payload
- `void set_data(char *data, uint32_t length)`: Set data payload from C-style array
- `std::vector<uint8_t> &get_data()`: Get data payload (reference)
- `std::vector<uint8_t> get_data_copy()`: Get data payload (copy)
- `std::vector<uint8_t> encode()`: Encode protocol data into byte array
- `static std::tuple<bool, SimpleProtocol> decode(const std::vector<uint8_t> &bytearray)`: Decode byte array into protocol data

#### Global Functions

- `std::shared_ptr<asio::io_context> get_global_io_ctx()`: Get or create global io_context
- `void run_ctx_backend()`: Run the io_context in a background thread
- `void stop_ctx()`: Stop the io_context backend thread
- `void set_terminal_verbose(bool enabled)`: Enable/disable verbose terminal output
- `bool get_terminal_verbose()`: Get verbose terminal output status

## License

MIT License

## Author

Red Elephant (redelephant@foxmail.com)