TCP

include: co/tcp.h.

#tcp::Connection

tcp::Connection 类是对 TCP 连接的简单封装,用于实现 TCP server,客户端不需要用这个类。当服务端启用 SSL 时,tcp::Connection 会用 SSL 传输数据。

#Connection::Connection

Connection(int sock);
Connection(void* ssl);
Connection(Connection&& c);
  • 构造函数,Connectiontcp::Server 创建,用户不需要手动创建。
  • 第 1 个版本构造一般的 TCP 连接,第 2 个版本构造使用 SSL 传输数据的 TCP 连接,第 3 个是移动构造函数。
  • 从 v2.0.2 开始,用户不能继承 Connection 类。

#Connection::~Connection

Connection::~Connection();
  • 析构函数,调用 close() 关闭连接。

#Connection::close

int close(int ms = 0);
  • 关闭连接,参数 ms > 0 时,延迟一段时间再关闭连接。
  • 从 v2.0.1 开始,此方法可以在协程或非协程中调用。

#Connection::recv

int recv(void* buf, int n, int ms=-1);
  • 接收数据,与 co::recv 类似。
  • 此方法必须在协程中调用。
  • 此方法成功时返回值 >0,超时或发生错误时返回值 <0,对端关闭连接时返回 0。

#Connection::recvn

int recvn(void* buf, int n, int ms=-1);
  • 接收指定长度的数据,与 co::recvn 类似。
  • 此方法成功时返回 n,超时或发生错误时返回值 <0,对端关闭连接时返回 0。

#Connection::reset

int reset(int ms = 0)
  • 重置 TCP 连接,与 close() 不同,它不会进入 TIME_WAIT 状态。参数 ms > 0 时,延迟一段时间再重置连接。
  • 此方法必须在 I/O 线程(一般是进行 I/O 操作的协程)中调用。

#Connection::send

int send(const void* buf, int n, int ms=-1);
  • 发送数据,与 co::send() 类似。
  • 此方法成功时返回 n,超时或发生错误时返回值 <=0。

#Connection::socket

int socket() const;
  • 返回内部的 socket 描述符,连接已关闭时返回 -1。

#Connection::strerror

const char* strerror() const;
  • Connection 中的方法报错时,可以调用此方法查看错误信息。

#tcp::Server

tcp::Server 是基于协程的 TCP 服务端,它的特性如下:

  • 支持 IPv4 与 IPv6。
  • 支持 SSL (需要 openssl)。
  • 采用一个连接一个协程的模型。

#Server::Server

Server();
  • 构造函数,初始化。

#Server::conn_num

uint32 conn_num() const;
  • 返回当前的客户端连接数。

#Server::on_connection

Server& on_connection(std::function<void(Connection)>&& f);
Server& on_connection(const std::function<void(Connection)>& f);

template<typename T>
Server& on_connection(void (T::*f)(Connection), T* o);
  • 设置处理客户端连接的回调函数。

  • 第 1, 2 个版本中,参数 f 是 void f(Connection) 类型的函数,或 std::function<void(Connection)> 类型的函数对象。

  • 第 3 个版本中,参数 f 是类中的方法,参数 o 是 T 类型的指针。

  • 从 v2.0.2 开始,f 的参数改为 tcp::Connection 对象,而非指针,用户不需要手动 delete。

  • 服务端接收到新的客户端连接时,会新建一个协程,并在协程中调用此方法设置的回调函数,处理新连接上的数据。

  • 示例

void f(tcp::Connection conn);

tcp::Server s;
s.on_connection(f);

void f(tcp::Connection conn) {
    while (true) {
        conn.recv(...);
        process(...);
        conn.send(...);
    }
    
    conn.close();
}

#Server::on_exit

Server& on_exit(std::function<void()>&& cb);
  • 设置一个 callback,它将在 server 退出时被调用。

#Server::start

void start(const char* ip, int port, const char* key=0, const char* ca=0);
  • 启动 TCP server,此方法不会阻塞当前线程。

  • 参数 ip 是服务器 ip,可以是 IPv4 或 IPv6 地址,参数 port 是服务器端口。

  • 参数 key 是存放 SSL private key 的 PEM 文件路径,参数 ca 是存放 SSL 证书的 PEM 文件路径,默认 key 和 ca 是 NULL,不启用 SSL。

  • 从 v3.0 开始,server 启动后就不再依赖于 tcp::Server 对象。

  • 示例

void f(tcp::Connection conn);
tcp::Server().on_connection(f).start("0.0.0.0", 7788);

#Server::exit

void exit();
  • v2.0.2 新增。
  • 退出 TCP server,关闭 listening socket,不再接收新的连接。
  • 此方法不会关闭之前已经建立的连接
  • 若需要在 server 退出后,关闭之前建立的连接,可以参考 test/tcp2.cc 或 co 中 http::Serverrpc::Server 的实现。

#tcp::Client

tcp::Client 是基于协程的 TCP 客户端,它有如下特性:

  • 支持 IPv4 与 IPv6。
  • 支持 SSL (需要安装 openssl)。
  • 一个客户端对象,对应一个连接。
  • 它必须在协程中使用。
  • 它不是协程安全的,同一时刻,不能有多个协程对它进行操作。

#Client::Client

Client(const char* ip, int port, bool use_ssl=false);
Client(const Client& c);
  • 构造函数。参数 ip 是服务端 ip,可以是域名、IPv4 或 IPv6 地址;参数 port 是服务端口;参数 use_ssl 表示是否启用 SSL 传输,默认为 false。
  • 第 2 个版本是拷贝构造函数,仅拷贝 ip, port, use_ssl。
  • tcp::Client 构建时,并没有建立连接。
  • 一般建议在调用 recv, send 前,判断连接是否建立,没有的话就调用 connect() 方法建立连接,这种方式可以实现自动重连。

#Client::~Client

Client::~Client();

#Client::close

void close();
  • 关闭连接,与 disconnect() 相同。

#Client::connect

bool connect(int ms);
  • 建立连接,参数 ms 是超时时间,单位为毫秒。
  • 此方法必须在协程中调用。
  • 此方法成功时返回 true,否则返回 false。失败时,用户可以调用 strerror() 方法查看错误信息。

#Client::connected

bool connected() const;
  • 判断是否已经建立连接。

#Client::disconnect

void disconnect();
  • 从 v2.0.1 开始,可以在协程或非协程中调用。
  • 多次调用此方法是安全的,析构函数中会自动调用此方法。

#Client::recv

int recv(void* buf, int n, int ms=-1);
  • 接收数据,与 co::recv() 类似。
  • 此方法必须在协程中调用。
  • 此方法成功时返回值 >0,超时或发生错误时返回值 <0,对端关闭连接时返回 0。

#Client::recvn

int recvn(void* buf, int n, int ms=-1);
  • 接收指定长度的数据,与 co::recvn() 类似。
  • 此方法必须在协程中调用。
  • 此方法成功时返回 n,超时或发生错误时返回值 <0,对端关闭连接时返回 0。

#Client::send

int send(const void* buf, int n, int ms=-1);
  • 发送数据,与 co::send() 类似。
  • 此方法必须在协程中调用。
  • 此方法成功时返回 n,超时或发生错误时返回值 <=0。

#Client::socket

int socket() const;
  • 返回内部的 socket 描述符。
  • 未建立连接或连接已经关闭时,返回值是 -1。

#Client::strerror

const char* strerror() const;
  • tcp::Client 中的方法报错时,可以调用此方法查看错误信息。

#TCP 服务端代码示例

void on_connection(tcp::Connection conn) {
    char buf[8] = { 0 };

    while (true) {
        int r = conn.recv(buf, 8);
        if (r == 0) {         /* client close the connection */
            conn.close();
            break;
        } else if (r < 0) { /* error */
            conn.reset(3000);
            break;
        } else {
            LOG << "server recv " << fastring(buf, r);
            LOG << "server send pong";
            r = conn.send("pong", 4);
            if (r <= 0) {
                LOG << "server send error: " << conn.strerror();
                conn.reset(3000);
                break;
            }
        }
    }
}

tcp::Server s;
s.on_connection(on_connection);
s.start("0.0.0.0", 7788);                                    // no ssl
s.start("0.0.0.0", 7788, "privkey.pem", "certificate.pem");  // use ssl
  • 上面的例子实现了一个简单的 ping-pong server,收到客户端发送的 ping 时,回复一个 pong。

#TCP 客户端代码示例

bool use_ssl = false;
std::unique_ptr<tcp::Client> proto;

co::pool pool(
    []() {return (void*) new tcp::Client(*proto); },
    [](void* p) {delete (tcp::Client*) p;}
);

void client_fun() {
    co::pool_guard<tcp::Client> c(pool);
    
    if (!c->connect(3000)) {
        LOG << "connect failed: "<< c->strerror();
        return;
    }

    char buf[8] = {0 };

    while (true) {
        LOG << "client send ping";
        int r = c->send("ping", 4);
        if (r <= 0) {
            LOG << "client send error: "<< c->strerror();
            break;
        }

        r = c->recv(buf, 8);
        if (r < 0) {
            LOG << "client recv error: "<< c->strerror();
            break;
        } else if (r == 0) {
            LOG << "server close the connection";
            break;
        } else {
            LOG << "client recv "<< fastring(buf, r) <<'\n';
            co::sleep(3000);
        }
    }
}

proto.reset(new tcp::Client("127.0.0.1", 7788, use_ssl));
for (int i = 0; i <8; ++i) {
    go(client_fun);
}
  • 上面的例子中,我们用 co::pool 缓存客户端连接,不同协程可以共用 pool 中的连接。