虽然虽然是无连接的,然是公网服务器仍然可以给发送者进行回包,甚至可以绕过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 udp;
uint64_t recv_count = 0;
udp.tick_callback = [&udp](bool &to_stop)
{
// std::cout<<"on_tick"<<std::endl;
};
udp.message_callback = [&udp, &recv_count](const char *buffer, ssize_t len, sockaddr_in &addr)
{
++recv_count;
std::cout << std::string(buffer, len) << " recv_count " << recv_count << std::endl;
// pingpong
udp.udp_component_client("", 0,
buffer,
len,
(sockaddr *)&addr,
sizeof(addr));
};
udp.close_callback = [&udp]()
{
// close callback
};
udp.udp_component_server("0.0.0.0", 20025);
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 udp;
uint64_t pingpong_count = 0;
udp.tick_callback = [&udp](bool &to_stop)
{
// std::cout<<"on_tick"<<std::endl;
};
udp.message_callback = [&udp, &pingpong_count](const char *buffer, ssize_t len, sockaddr_in &addr)
{
// 这里接受包不一定来自172.29.94.203 而是像172.29.94.203:20025发数据时 使用了一个端口 IP:A 只要有外界向IP:A发消息 如果接收到了则这里就会接收到
pingpong_count++;
std::cout << std::string(buffer, len) << " pingpong_count " << pingpong_count << std::endl;
// pingpong
udp.udp_component_client("", 0,
buffer,
len,
(sockaddr *)&addr,
sizeof(addr));
};
udp.close_callback = [&udp]()
{
// close callback
};
udp.udp_component_client("172.29.94.203", 20025, "pingpong", 8);
return 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)
{
perror("error getting fd flags");
return -1;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
{
perror("error setting non-bloking mode");
return -1;
}
return 0;
}
udp_component::~udp_component()
{
to_close();
}
void udp_component::to_close()
{
if (m_socket_fd > 0)
{
close(m_socket_fd);
}
if (m_epoll_fd > 0)
{
close(m_epoll_fd);
}
if (close_callback)
{
close_callback();
}
}
int udp_component::init_sock()
{
int &server_socket_fd = m_socket_fd;
if (server_socket_fd <= 0)
{
server_socket_fd = ::socket(AF_INET, SOCK_DGRAM, 0);
if (server_socket_fd == -1)
{
perror("error creating socket");
return -1;
}
}
return 0;
}
std::string udp_component::udp_component_get_ip(struct sockaddr_in &addr)
{
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(addr.sin_addr), ip, INET_ADDRSTRLEN);
return 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())
{
perror("Error creating socket");
return -1;
}
}
// setting server addr
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(IP.c_str());
server_addr.sin_port = htons(int_port);
// server bind addr
if (-1 == bind(server_socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)))
{
perror("error binding socket");
to_close();
return -1;
}
// port reuse
int reuse = 1;
if (-1 == setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)))
{
perror("Error setting socket options");
to_close();
return -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())
{
perror("Error creating socket");
return -1;
}
}
sockaddr_in server_addr;
std::memset(&server_addr, 0, sizeof(server_addr));
// server addr
if (IP != "" && PORT != 0)
{
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(IP.c_str());
server_addr.sin_port = htons(PORT);
}
else if (addr && addr_len > 0)
{
}
else
{
perror("IP PORT and addr addr_len");
return -1;
}
ssize_t bytesSent = 0;
if (addr)
{
bytesSent = sendto(client_socket, buffer, len, 0,
addr, addr_len);
}
else
{
bytesSent = sendto(client_socket, buffer, len, 0,
(sockaddr *)&server_addr, sizeof(server_addr));
}
if (bytesSent != len)
{
perror("Error sending message bytesSent != len");
to_close();
return -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)
{
perror("eventloop err server_socket_fd <= 0");
return -1;
}
// set nonblocking
if (udp_component_setnonblocking(server_socket_fd) == -1)
{
to_close();
perror("udp_component_setnonblocking err");
return -1;
}
// create epoll instance
m_epoll_fd = epoll_create(1);
int &epoll_fd = m_epoll_fd;
if (epoll_fd == -1)
{
perror("error creating epoll");
to_close();
return -1;
}
// add server fd to epoll's listen list
epoll_event event;
bzero(&event, sizeof(event));
event.data.fd = server_socket_fd;
event.events = EPOLLIN;
if (-1 == epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket_fd, &event))
{
perror("error adding server socket to epoll");
to_close();
return -1;
}
// event loop
constexpr int MAX_EVENTS_NUM = 1;
sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
epoll_event events[MAX_EVENTS_NUM];
bzero(&events[0], sizeof(events));
int events_num = 0;
bzero(&client_addr, sizeof(client_addr));
constexpr int buffer_size = 65507;
char buffer[buffer_size];
bzero(buffer, sizeof(buffer));
do
{
events_num = epoll_wait(epoll_fd, events, MAX_EVENTS_NUM, 10); // 10ms
// tick
{
if (tick_callback)
{
bool to_stop = false;
tick_callback(to_stop);
if (to_stop)
{
to_close();
return 0;
}
}
}
if (0 == events_num)
{
continue;
}
else if (0 > events_num)
{
perror("0 > events_num");
to_close();
return -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)
{
message_callback(buffer, bytes, client_addr);
}
}
}
}
} 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;
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;
}