虽然虽然是无连接的,然是公网服务器仍然可以给发送者进行回包,甚至可以绕过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.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假设两台主机分别位于两个局域网中,这两台主机不能直接通过TCP建立连接(TCP连接需要固定的ip和端口,通常路由器或者本机防火墙是不会将这个暴露在外的)。这时可以通过 UDP穿透来实现两台主机的跨局域网通信(当然前提是需要一台公网服务器)。
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 已向服务器发送身份");服务器布设在远程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;
}客户端与公网服务器可以进行UDP双方收发数据,而KCP是建立在UDP上的可靠的应用层协议,可靠的UDP,UDP版本的TCP。
可以防止被攻击,被攻击时多搞点软路由,新软路由节点用新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 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;
}