TCP服务入门篇:回显、聊天、代理三种经典服务实现详解

TCP回显服务

examples/tcp_echo_server.c

/*
 * tcp echo server
 *
 * @build   make examples
 * @server  bin/tcp_echo_server 1234
 * @client  bin/nc 127.0.0.1 1234
 *          nc     127.0.0.1 1234
 *          telnet 127.0.0.1 1234
 */

#include "hloop.h"
#include "hsocket.h"

// hloop_create_tcp_server -> on_accept -> hio_read -> on_recv -> hio_write

static void on_close(hio_t* io) {
    printf("on_close fd=%d error=%d\n", hio_fd(io), hio_error(io));
}

static void on_recv(hio_t* io, void* buf, int readbytes) {
    printf("on_recv fd=%d readbytes=%d\n", hio_fd(io), readbytes);
    char localaddrstr[SOCKADDR_STRLEN] = {0};
    char peeraddrstr[SOCKADDR_STRLEN] = {0};
    printf("[%s] <=> [%s]\n",
            SOCKADDR_STR(hio_localaddr(io), localaddrstr),
            SOCKADDR_STR(hio_peeraddr(io), peeraddrstr));
    printf("< %.*s", readbytes, (char*)buf);
    // 回显: 将读到的数据原封不动的发给对方
    printf("> %.*s", readbytes, (char*)buf);
    hio_write(io, buf, readbytes);
}

static void on_accept(hio_t* io) {
    printf("on_accept connfd=%d\n", hio_fd(io));
    char localaddrstr[SOCKADDR_STRLEN] = {0};
    char peeraddrstr[SOCKADDR_STRLEN] = {0};
    printf("accept connfd=%d [%s] <= [%s]\n", hio_fd(io),
            SOCKADDR_STR(hio_localaddr(io), localaddrstr),
            SOCKADDR_STR(hio_peeraddr(io), peeraddrstr));

    // 设置回调
    hio_setcb_close(io, on_close);
    hio_setcb_read(io, on_recv);
    // 监听读事件
    hio_read(io);
}

int main(int argc, char** argv) {
    if (argc < 2) {
        printf("Usage: %s port\n", argv[0]);
        return -10;
    }
    int port = atoi(argv[1]);

    // 创建事件循环
    hloop_t* loop = hloop_new(0);

    // 创建TCP服务,设置accept回调
    hio_t* listenio = hloop_create_tcp_server(loop, "0.0.0.0", port, on_accept);
    if (listenio == NULL) {
        return -20;
    }
    printf("listenfd=%d\n", hio_fd(listenio));

    // 运行事件循环
    hloop_run(loop);

    // 释放事件循环
    hloop_free(&loop);
    return 0;
}

编译步骤:

git clone https://github.com/ithewei/libhv.git
cd libhv
./configure
make

注:windows平台编译可使用cmake

git clone https://github.com/ithewei/libhv.git
cd libhv
mkdir win64
cd win64
cmake .. -G "Visual Studio 15 2017 Win64"
cmake --build .

测试:
tcp_echo_server
注:windows平台可使用telnet代替nc作为测试客户端
tcp_echo_server

TCP聊天服务

examples/tcp_chat_server.c

/*
* tcp chat server
*
* @build   make examples
* @server  bin/tcp_chat_server 1234
* @clients bin/nc 127.0.0.1 1234
*          nc     127.0.0.1 1234
*          telnet 127.0.0.1 1234
*/

#include "hloop.h"
#include "hsocket.h"
#include "hbase.h"
#include "list.h"

// hloop_create_tcp_server
// on_accept => join
// on_recv => broadcast
// on_close => leave

typedef struct chatroom_s {
   // 事件循环结构体指针
   hloop_t*            loop;
   // 监听IO结构体指针
   hio_t*              listenio;
   // 房间号
   int                 roomid;
   // 使用链表存储连接,插入与删除复杂度皆为O(1)
   struct list_head    conns;
} chatroom_t;

typedef struct connection_s {
   // 连接IO结构体指针
   hio_t*              connio;
   // 套接字地址
   char                addr[SOCKADDR_STRLEN];
   // 链表结点
   struct list_node    node;
} connection_t;

static chatroom_t s_chatroom;

static void join(chatroom_t* room, connection_t* conn);
static void leave(chatroom_t* room, connection_t* conn);
static void broadcast(chatroom_t* room, const char* msg, int msglen);

// 加入聊天室
void join(chatroom_t* room, connection_t* conn) {
   // 添加到链表
   list_add(&conn->node, &room->conns);

   char msg[256] = {0};
   int msglen = 0;

   // 向新来的成员发送当前聊天室成员列表
   struct list_node* node;
   connection_t* cur;
   msglen = snprintf(msg, sizeof(msg), "room[%06d] clients:\r\n", room->roomid);
   hio_write(conn->connio, msg, msglen);
   list_for_each (node, &room->conns) {
       cur = list_entry(node, connection_t, node);
       msglen = snprintf(msg, sizeof(msg), "[%s]\r\n", cur->addr);
       hio_write(conn->connio, msg, msglen);
   }
   hio_write(conn->connio, "\r\n", 2);

   // 广播有新成员加入聊天室
   msglen = snprintf(msg, sizeof(msg), "client[%s] join room[%06d]\r\n", conn->addr, room->roomid);
   broadcast(room, msg, msglen);
}

// 离开聊天室
void leave(chatroom_t* room, connection_t* conn) {
   // 从链表里删除
   list_del(&conn->node);

   // 广播有成员离开聊天室
   char msg[256] = {0};
   int msglen = snprintf(msg, sizeof(msg), "client[%s] leave room[%d]\r\n", conn->addr, room->roomid);
   broadcast(room, msg, msglen);
}

// 广播消息
void broadcast(chatroom_t* room, const char* msg, int msglen) {
   printf("> %.*s", msglen, msg);
   struct list_node* node;
   connection_t* conn;
   // 循环向聊天室的每个连接发送消息
   list_for_each (node, &room->conns) {
       conn = list_entry(node, connection_t, node);
       hio_write(conn->connio, msg, msglen);
   }
}

static void on_close(hio_t* io) {
   printf("on_close fd=%d error=%d\n", hio_fd(io), hio_error(io));

   connection_t* conn = (connection_t*)hevent_userdata(io);
   if (conn) {
       hevent_set_userdata(io, NULL);

       // 断连离开聊天室
       leave(&s_chatroom, conn);
       HV_FREE(conn);
   }
}

static void on_recv(hio_t* io, void* buf, int readbytes) {
   printf("on_recv fd=%d readbytes=%d\n", hio_fd(io), readbytes);
   char localaddrstr[SOCKADDR_STRLEN] = {0};
   char peeraddrstr[SOCKADDR_STRLEN] = {0};
   printf("[%s] <=> [%s]\n",
           SOCKADDR_STR(hio_localaddr(io), localaddrstr),
           SOCKADDR_STR(hio_peeraddr(io), peeraddrstr));
   printf("< %.*s", readbytes, (char*)buf);

   // 广播消息
   connection_t* conn = (connection_t*)hevent_userdata(io);
   assert(conn != NULL);
   char msg[256] = {0};
   int msglen = snprintf(msg, sizeof(msg), "client[%s] say: %.*s", conn->addr, readbytes, (char*)buf);
   broadcast(&s_chatroom, msg, msglen);
}

static void on_accept(hio_t* io) {
   printf("on_accept connfd=%d\n", hio_fd(io));
   char localaddrstr[SOCKADDR_STRLEN] = {0};
   char peeraddrstr[SOCKADDR_STRLEN] = {0};
   printf("accept connfd=%d [%s] <= [%s]\n", hio_fd(io),
           SOCKADDR_STR(hio_localaddr(io), localaddrstr),
           SOCKADDR_STR(hio_peeraddr(io), peeraddrstr));

   // 设置回调
   hio_setcb_close(io, on_close);
   hio_setcb_read(io, on_recv);
   // 监听读事件
   hio_read(io);

   // 新的连接加入聊天室
   connection_t* conn = NULL;
   HV_ALLOC_SIZEOF(conn);
   conn->connio = io;
   strcpy(conn->addr, peeraddrstr);
   hevent_set_userdata(io, conn);
   join(&s_chatroom, conn);
}

int main(int argc, char** argv) {
   if (argc < 2) {
       printf("Usage: %s port\n", argv[0]);
       return -10;
   }
   int port = atoi(argv[1]);

   // 创建事件循环
   hloop_t* loop = hloop_new(0);

   // 创建TCP服务,设置accept回调
   hio_t* listenio = hloop_create_tcp_server(loop, "0.0.0.0", port, on_accept);
   if (listenio == NULL) {
       return -20;
   }
   printf("listenfd=%d\n", hio_fd(listenio));

   // 初始化聊天室结构体
   s_chatroom.loop = loop;
   s_chatroom.listenio = listenio;
   // 随机生成一个房间号
   srand(time(NULL));
   s_chatroom.roomid = rand() % 1000000;
   list_init(&s_chatroom.conns);

   // 运行事件循环
   hloop_run(loop);

   // 释放事件循环
   hloop_free(&loop);
   return 0;
}

测试:
tcp_chat_server

TCP代理服务

examples/tcp_proxy_server.c

/*
* tcp proxy server
*
* @build:        make clean && make examples WITH_OPENSSL=yes
* @http_server:  bin/httpd -s restart -d
* @proxy_server: bin/tcp_proxy_server 8888 127.0.0.1:8080
*                bin/tcp_proxy_server 8888 127.0.0.1:8443
*                bin/tcp_proxy_server 8888 www.baidu.com
*                bin/tcp_proxy_server 8888 www.baidu.com:443
* @client:       bin/curl -v 127.0.0.1:8888
*                bin/nc 127.0.0.1 8888
*                > GET / HTTP/1.1
*                > Connection: close
*                > [Enter]
*                > GET / HTTP/1.1
*                > Connection: keep-alive
*                > [Enter]
*/

#include "hloop.h"
#include "hsocket.h"

static char proxy_host[64] = "127.0.0.1";
static int  proxy_port = 80;
static int  proxy_ssl = 0;

// hloop_create_tcp_server -> on_accept -> hio_setup_tcp_upstream

static void on_accept(hio_t* io) {
   /*
   printf("on_accept connfd=%d\n", hio_fd(io));
   char localaddrstr[SOCKADDR_STRLEN] = {0};
   char peeraddrstr[SOCKADDR_STRLEN] = {0};
   printf("accept connfd=%d [%s] <= [%s]\n", hio_fd(io),
           SOCKADDR_STR(hio_localaddr(io), localaddrstr),
           SOCKADDR_STR(hio_peeraddr(io), peeraddrstr));
   */

   if (proxy_port % 1000 == 443) proxy_ssl = 1;
   // 建立TCP转发
   hio_setup_tcp_upstream(io, proxy_host, proxy_port, proxy_ssl);
}

int main(int argc, char** argv) {
   if (argc < 3) {
       printf("Usage: %s port proxy_host:proxy_port\n", argv[0]);
       return -10;
   }
   int port = atoi(argv[1]);
   char* pos = strchr(argv[2], ':');
   if (pos) {
       int len = pos - argv[2];
       if (len > 0) {
           memcpy(proxy_host, argv[2], len);
           proxy_host[len] = '\0';
       }
       proxy_port = atoi(pos + 1);
   } else {
       strncpy(proxy_host, argv[2], sizeof(proxy_host));
   }
   if (proxy_port == 0) proxy_port = 80;
   printf("proxy: [%s:%d]\n", proxy_host, proxy_port);

   // 创建事件循环
   hloop_t* loop = hloop_new(0);

   // 创建TCP服务,设置accept回调
   hio_t* listenio = hloop_create_tcp_server(loop, "0.0.0.0", port, on_accept);
   if (listenio == NULL) {
       return -20;
   }
   printf("listenfd=%d\n", hio_fd(listenio));

   // 运行事件循环
   hloop_run(loop);

   // 释放事件循环
   hloop_free(&loop);
   return 0;
}

测试:
tcp_proxy_server

ithewei CSDN认证博客专家 c/c++ Qt libhv
编程之路,其路漫漫,吾将上下而求索
https://github.com/ithewei
https://hewei.blog.csdn.net
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 代码科技 设计师:Amelia_0503 返回首页