UDP打洞与KCP应用

UDP

虽然虽然是无连接的,然是公网服务器仍然可以给发送者进行回包,甚至可以绕过NAT成为很多NAT内网穿透的基础,也成为打洞

实现服务端与客户端

服务端 udp_server.cpp

// g++ udp_server.cpp udp_component.cpp -o udp_server.exe --std=c++11

#include <iostream>
#include <string>
#include <cstring>      // For memset and other memory functions
#include <arpa/inet.h>  // For inet_ntop and network utility functions
#include <netinet/in.h> // For sockaddr_in and sockaddr_in6
#include "udp_component.h"

using namespace std;
using namespace avant::ipc;

int main(int argc, char **argv)
{
    udp_component udp;
    uint64_t recv_count = 0;

    udp.tick_callback = [](bool &to_stop)
    {
        // tick
    };

    udp.message_callback =
        [&udp, &recv_count](const char *buffer,
                            ssize_t len,
                            const sockaddr_storage &addr, // 传入客户端地址信息
                            socklen_t addr_len)
    {
        ++recv_count;

        // --- 核心修改部分:直接解析 sockaddr_storage ---
        char ipbuf[INET6_ADDRSTRLEN]; // 最大的地址字符串长度
        int client_port = -1;

        // 检查地址族并进行类型转换
        if (addr.ss_family == AF_INET)
        {
            const struct sockaddr_in *a = (const struct sockaddr_in *)&addr;
            // IPv4 地址转换:使用 inet_ntop
            if (inet_ntop(AF_INET, &(a->sin_addr), ipbuf, INET_ADDRSTRLEN) == nullptr)
            {
                std::strncpy(ipbuf, "Unknown IPv4", sizeof(ipbuf));
            }
            // 端口转换:从网络字节序转为主机字节序
            client_port = ntohs(a->sin_port);
        }
        else if (addr.ss_family == AF_INET6)
        {
            const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)&addr;
            // IPv6 地址转换:使用 inet_ntop
            if (inet_ntop(AF_INET6, &(a6->sin6_addr), ipbuf, INET6_ADDRSTRLEN) == nullptr)
            {
                std::strncpy(ipbuf, "Unknown IPv6", sizeof(ipbuf));
            }
            // 端口转换:从网络字节序转为主机字节序
            client_port = ntohs(a6->sin6_port);
        }
        else
        {
            // 其他地址族
            std::strncpy(ipbuf, "Unsupported Family", sizeof(ipbuf));
        }

        std::string client_ip(ipbuf);
        // ----------------------------------------------------

        std::cout << "** Received Message **" << std::endl;
        std::cout << "Data: " << std::string(buffer, len) << std::endl;
        std::cout << "Count: " << recv_count << std::endl;
        // 输出解析结果
        std::cout << "Client: " << client_ip << ":" << client_port << std::endl;
        std::cout << "Family: " << (addr.ss_family == AF_INET ? "IPv4" : (addr.ss_family == AF_INET6 ? "IPv6" : "Other")) << std::endl;
        std::cout << "---" << std::endl;

        // echo 回去
        // 传入 addr 参数,避免进入 event_loop()
        udp.udp_component_client(
            "", 0,
            buffer,
            len,
            (sockaddr *)&addr,
            addr_len);
    };

    udp.close_callback = []()
    {
        std::cout << "udp closed" << std::endl;
    };

    std::cout << "udp listening on :: 20027" << std::endl;
    udp.udp_component_server("::", 20027);

    return 0;
}

客户端 udp_client.cpp

// g++ udp_client.cpp udp_component.cpp -o udp_client.exe --std=c++11

#include <iostream>
#include "udp_component.h"
using namespace std;
using namespace avant::ipc;

int main(int argc, char **argv)
{
    udp_component udp;
    uint64_t pingpong_count = 0;

    udp.tick_callback = [](bool &to_stop)
    {
        // tick
    };

    udp.message_callback =
        [&udp, &pingpong_count](const char *buffer,
                                ssize_t len,
                                const sockaddr_storage &addr, // 必须 const sockaddr_storage&
                                socklen_t addr_len)           // 必须 socklen_t
    {
        pingpong_count++;

        std::cout << std::string(buffer, len)
                  << " pingpong_count " << pingpong_count
                  << std::endl;

        // --- 核心修改部分:直接解析 sockaddr_storage ---
        char ipbuf[INET6_ADDRSTRLEN]; // 最大的地址字符串长度
        int client_port = -1;

        // 检查地址族并进行类型转换
        if (addr.ss_family == AF_INET)
        {
            const struct sockaddr_in *a = (const struct sockaddr_in *)&addr;
            // IPv4 地址转换:使用 inet_ntop
            if (inet_ntop(AF_INET, &(a->sin_addr), ipbuf, INET_ADDRSTRLEN) == nullptr)
            {
                std::strncpy(ipbuf, "Unknown IPv4", sizeof(ipbuf));
            }
            // 端口转换:从网络字节序转为主机字节序
            client_port = ntohs(a->sin_port);
        }
        else if (addr.ss_family == AF_INET6)
        {
            const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)&addr;
            // IPv6 地址转换:使用 inet_ntop
            if (inet_ntop(AF_INET6, &(a6->sin6_addr), ipbuf, INET6_ADDRSTRLEN) == nullptr)
            {
                std::strncpy(ipbuf, "Unknown IPv6", sizeof(ipbuf));
            }
            // 端口转换:从网络字节序转为主机字节序
            client_port = ntohs(a6->sin6_port);
        }
        else
        {
            // 其他地址族
            std::strncpy(ipbuf, "Unsupported Family", sizeof(ipbuf));
        }

        std::string client_ip(ipbuf);
        // ----------------------------------------------------

        std::cout << "** Received Message **" << std::endl;
        std::cout << "Data: " << std::string(buffer, len) << std::endl;
        // 输出解析结果
        std::cout << "Client: " << client_ip << ":" << client_port << std::endl;
        std::cout << "Family: " << (addr.ss_family == AF_INET ? "IPv4" : (addr.ss_family == AF_INET6 ? "IPv6" : "Other")) << std::endl;
        std::cout << "---" << std::endl;

        // echo 回去
        // 传入 addr 参数,避免进入 event_loop()
        udp.udp_component_client(
            "", 0,
            buffer,
            len,
            (sockaddr *)&addr,
            addr_len);
    };

    udp.close_callback = []()
    {
        // close
    };

    // IPv6 地址示例
    udp.udp_component_client(
        "fe80::14c8:ffff:fe00:34", 20027,
        "pingpong", 8, nullptr, 0);
    udp.event_loop();

    return 0;
}

udp_component

头文件 udp_component.h

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#include <string>
#include <sys/epoll.h>
#include <functional>

namespace avant
{
    namespace ipc
    {
        class udp_component
        {
        public:
            ~udp_component();

            // 使用 sockaddr_storage 来确保 IPv6/IPv4 地址都能安全存储
            std::string udp_component_get_ip(const struct sockaddr_storage &addr);
            int udp_component_get_port(const struct sockaddr_storage &addr);

            // 如果 IP 为空字符串 "" 表示绑定到 any (:: or 0.0.0.0 取决于 socket 类型)
            int udp_component_server(const std::string &IP,
                                     const int PORT);

            // client:如果 addr != nullptr 则向指定地址发送并返回,否则向 IP:PORT 发送并在发送完后进入 event_loop(),前者用于服务器向客户端反包 后者用于客户端向服务器发送消息
            int udp_component_client(
                const std::string &TARGET_IP,
                const int TARGET_PORT,
                const char *buffer,
                ssize_t len,
                struct sockaddr *addr = nullptr,
                socklen_t addr_len = 0);

            static bool is_ipv6(const std::string &ip);

            int event_loop();

        private:
            int init_sock(const std::string &ip);
            void to_close();

        private:
            // 用 -1 表示无效 fd
            int m_socket_fd{-1};
            int m_epoll_fd{-1};

        public:
            // tick_callback: 可在 event loop 中周期性调用来判断是否退出
            std::function<void(bool &to_stop)> tick_callback{nullptr};

            // message callback: 安全传入 sockaddr_storage(拷贝/引用都安全)
            std::function<void(const char *buffer, ssize_t len, const struct sockaddr_storage &addr, socklen_t addr_len)> message_callback{nullptr};

            std::function<void()> close_callback{nullptr};
        };
    }
}

源码 udp_component.cpp

#include "udp_component.h"
#include <memory>

namespace avant
{
    namespace ipc
    {
        static int udp_component_setnonblocking(int fd)
        {
            int flags = fcntl(fd, F_GETFL, 0);
            if (flags == -1)
            {
                perror("udp_component_setnonblocking: error getting fd flags");
                return -1;
            }
            if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
            {
                perror("udp_component_setnonblocking: error setting non-blocking mode");
                return -1;
            }
            return 0;
        }

        udp_component::~udp_component()
        {
            to_close();
        }

        void udp_component::to_close()
        {
            if (m_socket_fd != -1)
            {
                close(m_socket_fd);
                m_socket_fd = -1;
            }
            if (m_epoll_fd != -1)
            {
                close(m_epoll_fd);
                m_epoll_fd = -1;
            }
            if (close_callback)
            {
                close_callback();
            }
        }

        int udp_component::init_sock(const std::string &ip)
        {
            if (m_socket_fd != -1)
            {
                return 0; // 已初始化
            }

            bool want_ipv6 = false;
            if (ip.empty())
            {
                // 默认使用 IPv6 socket(dual-stack),以便支持 IPv4 + IPv6
                want_ipv6 = true;
            }
            else
            {
                want_ipv6 = is_ipv6(ip);
            }

            if (want_ipv6)
            {
                m_socket_fd = ::socket(AF_INET6, SOCK_DGRAM, 0);
                if (m_socket_fd == -1)
                {
                    perror("init_sock: error creating AF_INET6 socket");
                    return -1;
                }
                // 允许接收 IPv4-mapped 地址(dual-stack)
                int off = 0;
                if (setsockopt(m_socket_fd, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)) == -1)
                {
                    // 非致命:记录但不强制失败(有的平台可能不允许修改)
                    perror("init_sock: warning setsockopt(IPV6_V6ONLY) failed");
                }
            }
            else
            {
                m_socket_fd = ::socket(AF_INET, SOCK_DGRAM, 0);
                if (m_socket_fd == -1)
                {
                    perror("init_sock: error creating AF_INET socket");
                    return -1;
                }
            }

            return 0;
        }

        std::string udp_component::udp_component_get_ip(const struct sockaddr_storage &addr)
        {
            char ipbuf[INET6_ADDRSTRLEN + 1] = {0};
            if (addr.ss_family == AF_INET)
            {
                struct sockaddr_in *a = (struct sockaddr_in *)&addr;
                inet_ntop(AF_INET, &a->sin_addr, ipbuf, INET_ADDRSTRLEN);
                return std::string(ipbuf);
            }
            else if (addr.ss_family == AF_INET6)
            {
                struct sockaddr_in6 *a6 = (struct sockaddr_in6 *)&addr;
                inet_ntop(AF_INET6, &a6->sin6_addr, ipbuf, INET6_ADDRSTRLEN);
                return std::string(ipbuf);
            }
            return std::string();
        }

        int udp_component::udp_component_get_port(const struct sockaddr_storage &addr)
        {
            if (addr.ss_family == AF_INET)
            {
                struct sockaddr_in *a = (struct sockaddr_in *)&addr;
                return ntohs(a->sin_port);
            }
            else if (addr.ss_family == AF_INET6)
            {
                struct sockaddr_in6 *a6 = (struct sockaddr_in6 *)&addr;
                return ntohs(a6->sin6_port);
            }
            return -1;
        }

        int udp_component::udp_component_server(const std::string &IP,
                                                const int PORT)
        {
            const int int_port = PORT;

            // create udp socket (可能是 AF_INET6 dual-stack)
            if (m_socket_fd == -1)
            {
                if (0 != init_sock(IP))
                {
                    perror("udp_component_server: Error creating socket");
                    return -1;
                }
            }

            // 设置 socket 选项:在 bind 之前设置
            int reuse = 1;
            if (setsockopt(m_socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
            {
                perror("udp_component_server: warning setsockopt(SO_REUSEADDR) failed");
            }
#ifdef SO_REUSEPORT
            if (setsockopt(m_socket_fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) == -1)
            {
                // 非致命
                perror("udp_component_server: warning setsockopt(SO_REUSEPORT) failed");
            }
#endif

            // bind
            if (is_ipv6(IP) || IP.empty())
            {
                struct sockaddr_in6 server_addr6;
                memset(&server_addr6, 0, sizeof(server_addr6));
                server_addr6.sin6_family = AF_INET6;
                server_addr6.sin6_port = htons(int_port);
                if (!IP.empty())
                {
                    if (inet_pton(AF_INET6, IP.c_str(), &server_addr6.sin6_addr) != 1)
                    {
                        perror("udp_component_server: Invalid IPV6 address");
                        to_close();
                        return -1;
                    }
                }
                else
                {
                    server_addr6.sin6_addr = in6addr_any;
                }

                if (-1 == bind(m_socket_fd, (struct sockaddr *)&server_addr6, sizeof(server_addr6)))
                {
                    perror("udp_component_server: error binding AF_INET6 socket");
                    to_close();
                    return -1;
                }
            }
            else
            {
                struct sockaddr_in server_addr;
                memset(&server_addr, 0, sizeof(server_addr));
                server_addr.sin_family = AF_INET;
                server_addr.sin_port = htons(int_port);
                if (!IP.empty())
                {
                    if (inet_pton(AF_INET, IP.c_str(), &server_addr.sin_addr) != 1)
                    {
                        perror("udp_component_server: Invalid IPV4 address");
                        to_close();
                        return -1;
                    }
                }
                else
                {
                    server_addr.sin_addr.s_addr = INADDR_ANY;
                }

                if (-1 == bind(m_socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)))
                {
                    perror("udp_component_server: error binding AF_INET socket");
                    to_close();
                    return -1;
                }
            }

            return event_loop();
        }

        int udp_component::udp_component_client(
            const std::string &TARGET_IP,
            const int TARGET_PORT,
            const char *buffer,
            ssize_t len,
            struct sockaddr *addr /*=nullptr*/,
            socklen_t addr_len /*=0*/)
        {
            // ensure socket exists
            if (m_socket_fd == -1 && addr == nullptr)
            {
                if (0 != init_sock("")) // client udp socket
                {
                    perror("udp_component_client: Error creating client udp socket");
                    return -1;
                }
            }

            sockaddr_in parse_target_addr;
            memset(&parse_target_addr, 0, sizeof(parse_target_addr));
            sockaddr_in6 parse_target_addr6;
            memset(&parse_target_addr6, 0, sizeof(parse_target_addr6));

            // call by client
            if (TARGET_IP != "" && TARGET_PORT != 0)
            {
                if (is_ipv6(TARGET_IP))
                {
                    parse_target_addr6.sin6_family = AF_INET6;
                    if (inet_pton(AF_INET6, TARGET_IP.c_str(), &parse_target_addr6.sin6_addr) <= 0)
                    {
                        perror("udp_component_client: Invalid IPV6 address");
                        to_close();
                        return -1;
                    }
                    parse_target_addr6.sin6_port = htons(TARGET_PORT);
                }
                else
                {
                    parse_target_addr.sin_family = AF_INET;
                    if (inet_pton(AF_INET, TARGET_IP.c_str(), &parse_target_addr.sin_addr) != 1)
                    {
                        perror("udp_component_client: Invalid IPV4 address");
                        to_close();
                        return -1;
                    }
                    parse_target_addr.sin_port = htons(TARGET_PORT);
                }
            }
            else if (addr && addr_len > 0)
            {
                // 直接使用传入的 addr
            }
            else
            {
                perror("udp_component_client: IP PORT and addr addr_len cannot both be empty/zero");
                return -1;
            }

            ssize_t bytesSent = 0;

            if (addr) // 服务端调用向客户端反包
            {
                bytesSent = sendto(m_socket_fd, buffer, len, 0, addr, addr_len);
            }
            else
            {
                sockaddr *target_addr = (sockaddr *)&parse_target_addr;
                socklen_t target_len = sizeof(sockaddr_in);
                if (is_ipv6(TARGET_IP))
                {
                    target_addr = (sockaddr *)&parse_target_addr6;
                    target_len = sizeof(sockaddr_in6);
                }
                bytesSent = sendto(m_socket_fd, buffer, len, 0, target_addr, target_len);
            }

            if (bytesSent != len)
            {
                perror("udp_component_client: Error sending message (bytesSent != len)");
                // 不立即 close,返回错误以便上层处理
                return -1;
            }

            return 0;
        }

        int udp_component::event_loop()
        {
            if (m_socket_fd == -1)
            {
                perror("event_loop: err m_socket_fd == -1");
                return -1;
            }

            // set nonblocking
            if (udp_component_setnonblocking(m_socket_fd) == -1)
            {
                perror("event_loop: udp_component_setnonblocking err");
                to_close();
                return -1;
            }

            // create epoll instance if not created
            if (m_epoll_fd == -1)
            {
                m_epoll_fd = epoll_create1(0);
            }
            if (m_epoll_fd == -1)
            {
                perror("event_loop: error creating epoll");
                to_close();
                return -1;
            }

            // add server fd to epoll's listen list
            epoll_event event;
            memset(&event, 0, sizeof(event));
            event.data.fd = m_socket_fd;
            event.events = EPOLLIN | EPOLLET; // edge-triggered 更高效(注意:非阻塞)
            if (-1 == epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, m_socket_fd, &event))
            {
                perror("event_loop: error adding server socket to epoll");
                to_close();
                return -1;
            }

            constexpr int MAX_EVENTS_NUM = 8;
            epoll_event events[MAX_EVENTS_NUM];
            memset(events, 0, sizeof(events));

            constexpr int buffer_size = 65507;
            std::unique_ptr<char[]> buffer(new char[buffer_size]);

            // event loop
            while (true)
            {
                int timeout_ms = 1000; // 1s;避免 10ms 的 busy-loop
                int events_num = epoll_wait(m_epoll_fd, events, MAX_EVENTS_NUM, timeout_ms);
                if (events_num < 0)
                {
                    if (errno == EINTR)
                    {
                        continue;
                    }
                    perror("event_loop: epoll_wait error");
                    to_close();
                    return -1;
                }

                // tick
                if (tick_callback)
                {
                    bool to_stop = false;
                    tick_callback(to_stop);
                    if (to_stop)
                    {
                        to_close();
                        return 0;
                    }
                }

                if (events_num == 0)
                {
                    // timeout,无事件,继续循环(tick 已经执行)
                    continue;
                }

                for (int i = 0; i < events_num; ++i)
                {
                    if (events[i].data.fd == m_socket_fd)
                    {
                        // 可能有多个消息(edge-triggered),循环读尽
                        while (true)
                        {
                            struct sockaddr_storage client_addr;
                            socklen_t addr_len = sizeof(client_addr);
                            memset(&client_addr, 0, sizeof(client_addr));

                            ssize_t bytes = recvfrom(m_socket_fd, buffer.get(), buffer_size, 0,
                                                     (struct sockaddr *)&client_addr, &addr_len);
                            if (bytes < 0)
                            {
                                if (errno == EAGAIN || errno == EWOULDBLOCK)
                                {
                                    // 已读尽
                                    break;
                                }
                                else if (errno == EINTR)
                                {
                                    continue;
                                }
                                else
                                {
                                    perror("event_loop: recvfrom error");
                                    // 不立即关闭,跳出当前 socket 读循环
                                    break;
                                }
                            }
                            else if (bytes == 0)
                            {
                                // UDP 不会正常返回 0,忽略
                                continue;
                            }
                            else
                            {
                                // 调用回调(传入 const sockaddr_storage &)
                                if (message_callback)
                                {
                                    message_callback(buffer.get(), bytes, client_addr, addr_len);
                                }
                            }
                        } // end inner read loop
                    }
                } // end for events
            } // end while
            return 0;
        }

        bool udp_component::is_ipv6(const std::string &ip)
        {
            if (ip.empty())
            {
                return true; // 默认使用 IPv6 socket(dual-stack)
            }
            struct in6_addr addr6;
            if (inet_pton(AF_INET6, ip.c_str(), &addr6) == 1)
            {
                return true;
            }
            struct in_addr addr4;
            if (inet_pton(AF_INET, ip.c_str(), &addr4) == 1)
            {
                return false;
            }
            // 如果既不是合法 IPv4 也不是合法 IPv6,则保守判断为 IPv6(上层会在 bind/pton 报错)
            return true;
        }

    } // namespace ipc
} // namespace avant

UDP穿透

UDP穿透作用

假设两台主机分别位于两个局域网中,这两台主机不能直接通过TCP建立连接(TCP连接需要固定的ip和端口,通常路由器或者本机防火墙是不会将这个暴露在外的)。这时可以通过 UDP穿透来实现两台主机的跨局域网通信(当然前提是需要一台公网服务器)。

UDP穿透原理

  1. 公网服务器开启一个UDP监听套接字
  2. 主机1向公网服务器发送UDP报文,此时公网服务器就能知道主机1的ip和端口(此时的这个ip和端口是不会受到防火墙的限制的,因为是主机的主动行为),公网服务器存储该地址
  3. 同样的,主机2向公网服务器发送UDP报文,此时公网服务器就能知道主机2的ip和端口,公网服务器存储该地址
  4. 公网服务器收集到这两个地址以后,分别将对方的地址返还,公网服务器就可以关闭了
  5. 主机1收到主机2的地址,主机2收到主机1的地址,只要它们不关闭套接字,就可以向对方发送UDP(UDP只管收,并不关心谁发的,发的啥)

UDP Node.js穿透实例

server.js

// server.js
const dgram = require("dgram");
const server = dgram.createSocket("udp4");

let clients = {}; // { A: {address, port}, B: {address, port} }

server.on("message", (msg, rinfo) => {
    const text = msg.toString();

    if (text === "A") {
        clients.A = { address: rinfo.address, port: rinfo.port };
        console.log("A 来自:", clients.A);
    } else if (text === "B") {
        clients.B = { address: rinfo.address, port: rinfo.port };
        console.log("B 来自:", clients.B);
    }

    // 如果双方都有了,就互相告诉对方外网地址
    if (clients.A && clients.B) {
        server.send(
            JSON.stringify(clients.B),
            clients.A.port,
            clients.A.address
        );
        server.send(
            JSON.stringify(clients.A),
            clients.B.port,
            clients.B.address
        );
        console.log("已交换 A <-> B 地址");
    }
});

server.bind(20000, () => console.log("服务器运行在 20000"));

clientA.js

// clientA.js
const dgram = require("dgram");
const client = dgram.createSocket("udp4");

const SERVER_IP = "IP";
const SERVER_PORT = 20000;

client.on("message", (msg, rinfo) => {
    console.log("收到服务器消息:", msg.toString());

    const peer = JSON.parse(msg.toString()); // {address, port}

    console.log("B 的外网地址:", peer);

    // 开始打洞(向 B 连续发几个包)
    setInterval(() => {
        client.send("Hello B! 我是 A", peer.port, peer.address);
        client.send("Hello B! 我是 A", peer.port, peer.address);
        client.send("Hello B! 我是 A", peer.port, peer.address);
        client.send("Hello B! 我是 A", peer.port, peer.address);
    }, 10);
});

// 先告诉服务器:我是 A
client.send("A", SERVER_PORT, SERVER_IP);
console.log("A 已向服务器发送身份");

clientB.js

// clientB.js
const dgram = require("dgram");
const client = dgram.createSocket("udp4");

const SERVER_IP = "IP";
const SERVER_PORT = 20000;

client.on("message", (msg, rinfo) => {
    console.log("收到服务器消息:", msg.toString());

    const peer = JSON.parse(msg.toString());

    console.log("A 的外网地址:", peer);

    // 开始对 A 打洞
    setInterval(() => {
        client.send("Hello A! 我是 B", peer.port, peer.address);
        client.send("Hello A! 我是 B", peer.port, peer.address);
        client.send("Hello A! 我是 B", peer.port, peer.address);
        client.send("Hello A! 我是 B", peer.port, peer.address);
    }, 10);
});

client.send("B", SERVER_PORT, SERVER_IP);
console.log("B 已向服务器发送身份");

UDP穿透实例

服务器布设在远程linux服务器上 ip:47.113.150.167

局域网主机环境是windows

服务器代码:

#include <cstdio>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include<iostream>
#define BUF_SIZE 30
//using namespace std;
/*
* 服务器
*/
int main()
{
    int serverSocket;
    sockaddr_in serverAddress, clientAddress;
    sockaddr_in clientAddress1, clientAddress2;
    int flagAddress1 = 0, flagAddress2 = 0;
    char message[BUF_SIZE];
    int str_len;
    socklen_t clientAddressLen;
    serverSocket = socket(AF_INET, SOCK_DGRAM, 0);
    memset(&serverAddress, 0, sizeof(serverAddress));
    memset(&clientAddress, 0, sizeof(serverAddress));
    memset(&clientAddress1, 0, sizeof(serverAddress));
    memset(&clientAddress2, 0, sizeof(serverAddress));
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddress.sin_port = htons(8888);
    bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
    while (1)
    {
        clientAddressLen = sizeof(sockaddr);
        std::cout << "等待接受来自客户端的UDP数据报....." << std::endl;
        //接收消息
        str_len = recvfrom(serverSocket, message, BUF_SIZE, 0, (sockaddr*)&clientAddress, &clientAddressLen);
        //打印客户端信息
        std::cout << "客户端地址:" << clientAddress.sin_addr.s_addr << std::endl;
        std::cout << "客户端端口:" << clientAddress.sin_port << std::endl;
        std::cout << "UDP内容:" << message << std::endl;
        if (flagAddress1 == 0) {
            memcpy(&clientAddress1, &clientAddress, sizeof(clientAddress));
            flagAddress1 = 1;
        }
        else if (flagAddress2 == 0) {
            memcpy(&clientAddress2, &clientAddress, sizeof(clientAddress));
            flagAddress2 = 1;
        }

        if (flagAddress1 == 1 && flagAddress2 == 1) {   //UDP双方准备就绪   将地址分别发给对方
            sendto(serverSocket, (void*)&clientAddress1, sizeof(clientAddress1), 0, (struct sockaddr*)&clientAddress2, sizeof(clientAddress2));
            sendto(serverSocket, (void*)&clientAddress2, sizeof(clientAddress2), 0, (struct sockaddr*)&clientAddress1, sizeof(clientAddress1));
            break;   //该服务器可以关闭了
        }
       
    }
    close(serverSocket);
    return 0;
}

局域网主机端代码:

//局域网主机端代码
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
using namespace std;
int main()
{
    // 加载套接字库
    WORD wVersion;
    WSADATA wsaData;
    int err;
    wVersion = MAKEWORD(1, 1);
    err = WSAStartup(wVersion, &wsaData);
    if (err != 0)
    {
        return err;
    }
    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
        WSACleanup();
        return -1;
    }

    // 创建套接字
    SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);
    sockaddr_in addrSrv, addTarget;
    addrSrv.sin_addr.s_addr = inet_addr("192.168.147.128");
    addrSrv.sin_port = htons(8888);
    addrSrv.sin_family = AF_INET;

    int len = sizeof(sockaddr);
    char buff[100] = "hello i am client!";
    //发送数据   获取目标主机UDP地址到addTarget
    cout << sendto(sockCli, buff, strlen(buff), 0, (sockaddr*)&addrSrv, sizeof(sockaddr)) << endl;
    cout << "等待服务器反馈!" << endl;
    recvfrom(sockCli, (char*)&addTarget, sizeof(addTarget), 0, (sockaddr*)&addrSrv, &len);
    cout <<"目标主机ip:" << addTarget.sin_addr.s_addr << endl;
    cout << "目标主机端口:" << addTarget.sin_port << endl;
    closesocket(sockCli);
    system("pause");
    return 0;

}

详细了解NAT

不同NAT类型对打洞能不能成功的影响

游戏用KCP进行通信

客户端与公网服务器可以进行UDP双方收发数据,而KCP是建立在UDP上的可靠的应用层协议,可靠的UDP,UDP版本的TCP。

用KCP建立游戏服务器软路由

可以防止被攻击,被攻击时多搞点软路由,新软路由节点用新IP就行了,为什么用KCP而不是KCP,因为TCP是有状态的,玩家断线后不知道哪些内容已经被游戏服接收了,哪些需要重发。

CDN(客户端从CDN拉去软路由列表 即路由的UDP IP 和 端口)
|
client<---kcp-->router<---kcp-->gate<---ipc--->gameworld_1
client<---kcp-->router<---kcp-->gate<---ipc--->gameworld_2
client<---kcp-->router<---kcp-->gate<---ipc--->gameworld_3

首先client会向router回报转发规则到哪里,router会将客户端发的每个UDP数据包转发到gate去,gate确认后 router知道了才通知 client说收到了(我们换router时gate正向router发udp包告诉router确认,但是router挂了,这也没关系,当client通过新router转发上来后gate可以继续原来的kcp要发的数据)。 如果有人攻击router,我们准备换一批router,client重新连接到新router告诉router转发规则后,继续上次的kcp任务,进而client与gate就又打通了。 而且是无缝衔接。

#include <array>
#include <iostream>
#include <kcp/kcp.h>
#include <sys/socket.h>
#include <unistd.h>

constexpr size_t BUFFER_SIZE = 1024;
constexpr size_t SERVER_PORT = 8080;
constexpr char SERVER_ADDRESS[] = "127.0.0.1";

int main() {
    // Initialize KCP
    uint32_t conv = 0;
    kcp_t* kcp = kcp_create(conv);
    kcp_set_nodelay(kcp, 1, 10, 2, 1);
    kcp_set_wndsize(kcp, 128, 128);

    // Create a UDP socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        std::cerr << "Failed to create socket" << std::endl;
        return 1;
    }

    sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);

    // Send data using KCP
    std::array<char, BUFFER_SIZE> input_buf;
    std::array<char, BUFFER_SIZE> output_buf;

    // Fill input_buf with your custom protocol data
    // For example, let's use a simple string
    std::string protocol_data = "Hello, KCP Server!";
    std::copy(protocol_data.begin(), protocol_data.end(), input_buf.begin());

    kcp_input(kcp, input_buf.data(), protocol_data.size());

    while (true) {
        kcp_update(kcp, kcp_ticks(1));

        // Send data
        int sent_len = kcp_send(kcp, output_buf.data(), BUFFER_SIZE);
        if (sent_len > 0) {
            sendto(sockfd, output_buf.data(), sent_len, 0,
                   (sockaddr*)&server_addr, sizeof(server_addr));
        }

        // Receive data
        sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
        int recv_len = recvfrom(sockfd, input_buf.data(), input_buf.size(), 0,
                               (sockaddr*)&client_addr, &client_len);
        if (recv_len > 0) {
            // Process received data if needed
            std::cout << "Received: " << input_buf.data() << std::endl;
        }
    }

    // Release resources
    kcp_release(kcp);
    close(sockfd);

    return 0;
}

TCP打洞

TCP NAT,两端获得自己对方 二元组后,建立个新套接字 设置复用 bind复用原来已经映射到NAT的二元组,让后不断地调用connect目标地址为对方二元组,两端都同时不断connect ,connect 如果能返回新fd ,则就把连接搞到手了^_^。

#include

#include <unistd.h>

#include <sys/socket.h>

#include <netinet/in.h>

void createSocket(int port)
{

    // 创建一个 UDP socket

    int sock = socket(AF_INET, SOCK_DGRAM, 0);

    if (sock == -1)
    {

        std::cerr << "Failed to create socket." << std::endl;

        exit(1);
    }

    // 设置 SO_REUSEADDR 选项以启用端口复用

    int yes = 1;

    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
    {

        std::cerr << "Failed to set socket option." << std::endl;

        exit(1);
    }

    // 绑定 socket 到指定端口

    struct sockaddr_inaddr;

    memset(&addr, 0, sizeof(addr));

    addr.sin_family = AF_INET;

    addr.sin_port = htons(port);

    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {

        std::cerr << "Failed to bind socket." << std::endl;

        exit(1);
    }

    std::cout << "Socket created and bound to port " << port << std::endl;
}

int main()
{

    // 创建两个套接字,使用相同的端口

    createSocket(5001);

    createSocket(5001);

    return 0;
}