当前位置:   article > 正文

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

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

编译步骤:

git clone https://github.com/ithewei/libhv.git
cd libhv
./configure
make
  • 1
  • 2
  • 3
  • 4

注: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 .
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试:
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" // libhv没有暴露出list.h、heap.h、rbtree.h等c版本基础数据结构,如需要请自行从base/目录下拷贝

// 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179

测试:
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80

测试:
tcp_proxy_server

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/158718
推荐阅读
相关标签
  

闽ICP备14008679号