虽然虽然是无连接的,然是公网服务器仍然可以给发送者进行回包,甚至可以绕过NAT成为很多NAT内网穿透的基础,也成为打洞
服务端 udp_server.cpp
// g++ udp_server.cpp udp_component.cpp -o udp_server.exe --std=c++11
#include <iostream>
#include "udp_component.h"
using namespace std;
using namespace tubekit::ipc;
int main(int argc, char **argv)
{
;
udp_component udpuint64_t recv_count = 0;
.tick_callback = [&udp](bool &to_stop)
udp{
// std::cout<<"on_tick"<<std::endl;
};
.message_callback = [&udp, &recv_count](const char *buffer, ssize_t len, sockaddr_in &addr)
udp{
++recv_count;
std::cout << std::string(buffer, len) << " recv_count " << recv_count << std::endl;
// pingpong
.udp_component_client("", 0,
udp,
buffer,
len(sockaddr *)&addr,
sizeof(addr));
};
.close_callback = [&udp]()
udp{
// close callback
};
.udp_component_server("0.0.0.0", 20025);
udp
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 tubekit::ipc;
int main(int argc, char **argv)
{
;
udp_component udpuint64_t pingpong_count = 0;
.tick_callback = [&udp](bool &to_stop)
udp{
// std::cout<<"on_tick"<<std::endl;
};
.message_callback = [&udp, &pingpong_count](const char *buffer, ssize_t len, sockaddr_in &addr)
udp{
// 这里接受包不一定来自172.29.94.203 而是像172.29.94.203:20025发数据时 使用了一个端口 IP:A 只要有外界向IP:A发消息 如果接收到了则这里就会接收到
++;
pingpong_countstd::cout << std::string(buffer, len) << " pingpong_count " << pingpong_count << std::endl;
// pingpong
.udp_component_client("", 0,
udp,
buffer,
len(sockaddr *)&addr,
sizeof(addr));
};
.close_callback = [&udp]()
udp{
// close callback
};
.udp_component_client("172.29.94.203", 20025, "pingpong", 8);
udpreturn 0;
}
头文件 udp_component.h
#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 tubekit
{
namespace ipc
{
class udp_component
{
public:
~udp_component();
std::string udp_component_get_ip(struct sockaddr_in &addr);
int udp_component_get_port(struct sockaddr_in &addr);
int udp_component_server(const std::string &IP,
const int PORT);
int udp_component_client(const std::string &IP,
const int PORT,
const char *buffer,
ssize_t len,
struct sockaddr *addr = nullptr,
socklen_t addr_len = 0);
private:
int event_loop();
int init_sock();
void to_close();
private:
int m_socket_fd{0};
int m_epoll_fd{0};
public:
std::function<void(bool &to_stop)> tick_callback{nullptr};
std::function<void(const char *buffer, ssize_t len, struct sockaddr_in &)> message_callback{nullptr};
std::function<void()> close_callback{nullptr};
};
}
}
源码 udp_component.cpp
#include "udp_component.h"
namespace tubekit
{
namespace ipc
{
static int udp_component_setnonblocking(int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1)
{
("error getting fd flags");
perrorreturn -1;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
{
("error setting non-bloking mode");
perrorreturn -1;
}
return 0;
}
::~udp_component()
udp_component{
();
to_close}
void udp_component::to_close()
{
if (m_socket_fd > 0)
{
(m_socket_fd);
close}
if (m_epoll_fd > 0)
{
(m_epoll_fd);
close}
if (close_callback)
{
();
close_callback}
}
int udp_component::init_sock()
{
int &server_socket_fd = m_socket_fd;
if (server_socket_fd <= 0)
{
= ::socket(AF_INET, SOCK_DGRAM, 0);
server_socket_fd if (server_socket_fd == -1)
{
("error creating socket");
perrorreturn -1;
}
}
return 0;
}
std::string udp_component::udp_component_get_ip(struct sockaddr_in &addr)
{
char ip[INET_ADDRSTRLEN];
(AF_INET, &(addr.sin_addr), ip, INET_ADDRSTRLEN);
inet_ntopreturn std::string(ip, INET_ADDRSTRLEN);
}
int udp_component::udp_component_get_port(struct sockaddr_in &addr)
{
unsigned short port = ntohs(addr.sin_port);
return port;
}
int udp_component::udp_component_server(const std::string &IP,
const int PORT)
{
const int int_port = PORT;
// create udp socket
int &server_socket_fd = m_socket_fd;
if (server_socket_fd <= 0)
{
if (0 != init_sock())
{
("Error creating socket");
perrorreturn -1;
}
}
// setting server addr
struct sockaddr_in server_addr;
(&server_addr, sizeof(server_addr));
bzero
.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(IP.c_str());
server_addr.sin_port = htons(int_port);
server_addr
// server bind addr
if (-1 == bind(server_socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)))
{
("error binding socket");
perror();
to_closereturn -1;
}
// port reuse
int reuse = 1;
if (-1 == setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)))
{
("Error setting socket options");
perror();
to_closereturn -1;
}
return event_loop();
}
int udp_component::udp_component_client(const std::string &IP,
const int PORT,
const char *buffer,
ssize_t len,
struct sockaddr *addr /*=nullptr*/,
socklen_t addr_len /*=0*/)
{
int &client_socket = m_socket_fd;
if (client_socket <= 0)
{
if (0 != init_sock())
{
("Error creating socket");
perrorreturn -1;
}
}
;
sockaddr_in server_addrstd::memset(&server_addr, 0, sizeof(server_addr));
// server addr
if (IP != "" && PORT != 0)
{
.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(IP.c_str());
server_addr.sin_port = htons(PORT);
server_addr}
else if (addr && addr_len > 0)
{
}
else
{
("IP PORT and addr addr_len");
perrorreturn -1;
}
ssize_t bytesSent = 0;
if (addr)
{
= sendto(client_socket, buffer, len, 0,
bytesSent , addr_len);
addr}
else
{
= sendto(client_socket, buffer, len, 0,
bytesSent (sockaddr *)&server_addr, sizeof(server_addr));
}
if (bytesSent != len)
{
("Error sending message bytesSent != len");
perror();
to_closereturn -1;
}
if (addr == nullptr)
{
return event_loop();
}
return 0;
}
int udp_component::event_loop()
{
int &server_socket_fd = m_socket_fd;
if (server_socket_fd <= 0)
{
("eventloop err server_socket_fd <= 0");
perrorreturn -1;
}
// set nonblocking
if (udp_component_setnonblocking(server_socket_fd) == -1)
{
();
to_close("udp_component_setnonblocking err");
perrorreturn -1;
}
// create epoll instance
m_epoll_fd = epoll_create(1);
int &epoll_fd = m_epoll_fd;
if (epoll_fd == -1)
{
("error creating epoll");
perror();
to_closereturn -1;
}
// add server fd to epoll's listen list
;
epoll_event event(&event, sizeof(event));
bzero.data.fd = server_socket_fd;
event.events = EPOLLIN;
eventif (-1 == epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket_fd, &event))
{
("error adding server socket to epoll");
perror();
to_closereturn -1;
}
// event loop
constexpr int MAX_EVENTS_NUM = 1;
;
sockaddr_in client_addrsocklen_t addr_len = sizeof(client_addr);
[MAX_EVENTS_NUM];
epoll_event events(&events[0], sizeof(events));
bzeroint events_num = 0;
(&client_addr, sizeof(client_addr));
bzeroconstexpr int buffer_size = 65507;
char buffer[buffer_size];
(buffer, sizeof(buffer));
bzero
do
{
= epoll_wait(epoll_fd, events, MAX_EVENTS_NUM, 10); // 10ms
events_num
// tick
{
if (tick_callback)
{
bool to_stop = false;
(to_stop);
tick_callbackif (to_stop)
{
();
to_closereturn 0;
}
}
}
if (0 == events_num)
{
continue;
}
else if (0 > events_num)
{
("0 > events_num");
perror();
to_closereturn -1;
}
for (int i = 0; i < events_num; ++i)
{
if (events[i].data.fd == server_socket_fd)
{
// have new message
ssize_t bytes = recvfrom(server_socket_fd, buffer, buffer_size, 0, (struct sockaddr *)&client_addr, (socklen_t *)&addr_len);
// process recved data
if (bytes > 0)
{
if (message_callback)
{
(buffer, bytes, client_addr);
message_callback}
}
}
}
} while (true);
return 0;
}
}
}
假设两台主机分别位于两个局域网中,这两台主机不能直接通过TCP建立连接(TCP连接需要固定的ip和端口,通常路由器或者本机防火墙是不会将这个暴露在外的)。这时可以通过 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;
, clientAddress;
sockaddr_in serverAddress, clientAddress2;
sockaddr_in clientAddress1int flagAddress1 = 0, flagAddress2 = 0;
char message[BUF_SIZE];
int str_len;
socklen_t clientAddressLen;
= socket(AF_INET, SOCK_DGRAM, 0);
serverSocket (&serverAddress, 0, sizeof(serverAddress));
memset(&clientAddress, 0, sizeof(serverAddress));
memset(&clientAddress1, 0, sizeof(serverAddress));
memset(&clientAddress2, 0, sizeof(serverAddress));
memset.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddress.sin_port = htons(8888);
serverAddress(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
bindwhile (1)
{
= sizeof(sockaddr);
clientAddressLen std::cout << "等待接受来自客户端的UDP数据报....." << std::endl;
//接收消息
= recvfrom(serverSocket, message, BUF_SIZE, 0, (sockaddr*)&clientAddress, &clientAddressLen);
str_len //打印客户端信息
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) {
(&clientAddress1, &clientAddress, sizeof(clientAddress));
memcpy= 1;
flagAddress1 }
else if (flagAddress2 == 0) {
(&clientAddress2, &clientAddress, sizeof(clientAddress));
memcpy= 1;
flagAddress2 }
if (flagAddress1 == 1 && flagAddress2 == 1) { //UDP双方准备就绪 将地址分别发给对方
(serverSocket, (void*)&clientAddress1, sizeof(clientAddress1), 0, (struct sockaddr*)&clientAddress2, sizeof(clientAddress2));
sendto(serverSocket, (void*)&clientAddress2, sizeof(clientAddress2), 0, (struct sockaddr*)&clientAddress1, sizeof(clientAddress1));
sendtobreak; //该服务器可以关闭了
}
}
(serverSocket);
closereturn 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 wsaDataint err;
= MAKEWORD(1, 1);
wVersion = WSAStartup(wVersion, &wsaData);
err if (err != 0)
{
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
();
WSACleanupreturn -1;
}
// 创建套接字
= socket(AF_INET, SOCK_DGRAM, 0);
SOCKET sockCli , addTarget;
sockaddr_in addrSrv.sin_addr.s_addr = inet_addr("192.168.147.128");
addrSrv.sin_port = htons(8888);
addrSrv.sin_family = AF_INET;
addrSrv
int len = sizeof(sockaddr);
char buff[100] = "hello i am client!";
//发送数据 获取目标主机UDP地址到addTarget
<< sendto(sockCli, buff, strlen(buff), 0, (sockaddr*)&addrSrv, sizeof(sockaddr)) << endl;
cout << "等待服务器反馈!" << endl;
cout (sockCli, (char*)&addTarget, sizeof(addTarget), 0, (sockaddr*)&addrSrv, &len);
recvfrom<<"目标主机ip:" << addTarget.sin_addr.s_addr << endl;
cout << "目标主机端口:" << addTarget.sin_port << endl;
cout (sockCli);
closesocket("pause");
systemreturn 0;
}
客户端与公网服务器可以进行UDP双方收发数据,而KCP是建立在UDP上的可靠的应用层协议,可靠的UDP,UDP版本的TCP。
可以防止被攻击,被攻击时多搞点软路由,新软路由节点用新IP就行了,为什么用KCP而不是KCP,因为TCP是有状态的,玩家断线后不知道哪些内容已经被游戏服接收了,哪些需要重发。
(客户端从CDN拉去软路由列表 即路由的UDP IP 和 端口)
CDN|
<---kcp-->router<---kcp-->gate<---ipc--->gameworld_1
client<---kcp-->router<---kcp-->gate<---ipc--->gameworld_2
client<---kcp-->router<---kcp-->gate<---ipc--->gameworld_3 client
首先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, 1, 10, 2, 1);
kcp_set_nodelay(kcp, 128, 128);
kcp_set_wndsize
// 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.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
server_addr
// 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_buf.data(), protocol_data.size());
kcp_input
while (true) {
(kcp, kcp_ticks(1));
kcp_update
// Send data
int sent_len = kcp_send(kcp, output_buf.data(), BUFFER_SIZE);
if (sent_len > 0) {
(sockfd, output_buf.data(), sent_len, 0,
sendto(sockaddr*)&server_addr, sizeof(server_addr));
}
// Receive data
;
sockaddr_in client_addrsocklen_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);
kcp_release(sockfd);
close
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;
(1);
exit}
// 设置 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;
(1);
exit}
// 绑定 socket 到指定端口
struct sockaddr_inaddr;
(&addr, 0, sizeof(addr));
memset
.sin_family = AF_INET;
addr
.sin_port = htons(port);
addr
.sin_addr.s_addr = INADDR_ANY;
addr
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
std::cerr << "Failed to bind socket." << std::endl;
(1);
exit}
std::cout << "Socket created and bound to port " << port << std::endl;
}
int main()
{
// 创建两个套接字,使用相同的端口
(5001);
createSocket
(5001);
createSocket
return 0;
}