使用c++在windows上用udp和kcp发送数据
目前使用kcp和udp的方式做网络协议,平台windows,目前测试了,大数据包(这里考虑了音视频数据发送的问题)也没问题。
中间还咨询了ai问答,要注意的点:
即使两个进程,kcp的数值要一样,否则无法传输。
中间的数据一直在发送这是因为ikcp_recv的内存没有一帧数据大,(即一次接受的内存能放置一次完整的信息)而ikcp_input则不用关心,这里要保证的内存比mtu大即可。
设置的时候要注意,mtu要比wnd(滑动窗口打)
先看我的代码:
KSock.h
#pragma once
#include <iostream>
#include <string>
#include <chrono>
#include <functional>
#include <WinSock2.h>
#include <map>
#include "ikcp.h"
#include <vector>
#include <thread>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
using Buffer = std::vector<char>;
class KSock
{
public:
void Init(int kcpId,bool isReceiver,SOCKET s);
void Send(char* data, int len);
void Recv(std::vector<char>& data);
void Release();
void KCPLoop();
UINT32 NOW()
{
std::chrono::steady_clock::time_point ts = std::chrono::steady_clock::now();
return ts.time_since_epoch().count();
}
SOCKET sock;
SOCKADDR_IN sockAddr;
std::string ip{ "127.0.0.1" };
int port{6868};
ikcpcb* kcp;
volatile bool isRunning;
Buffer RecvBuffer;
Buffer SendBuffer;
bool isRecvSock;
};
KSock.cpp
#include "KSock.h"
#define _WINSOCK_DEPRECATED_NO_WARNINGS
void sendUdpData(SOCKET sock, const char* message,int len, const char* ipAddress, int port) {
sockaddr_in toAddr;
memset(&toAddr, 0, sizeof(toAddr));
toAddr.sin_family = AF_INET;
toAddr.sin_port = htons(port);
toAddr.sin_addr.s_addr = inet_addr(ipAddress);
int bytesSent = sendto(sock, message, len, 0, (sockaddr*)&toAddr, sizeof(toAddr));
/*if (bytesSent == SOCKET_ERROR) {
std::cerr << "sendto failed with error: " << WSAGetLastError() << std::endl;
closesocket(sock);
WSACleanup();
exit(1);
}*/
//std::cout << "Sent data len: " << len << std::endl;
}
int UOutPut(const char* buf, int len, ikcpcb* kcp, void* user)
{
KSock* kth = (KSock*)user;
//sockaddr_in sClient;
//sClient.sin_family = AF_INET;
//sClient.sin_port = htons(6868);
////此处或出现bug,解决办法详见下文
////inet_pton(AF_INET, "127.0.0.1", &sClient.sin_addr);
//sClient.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//SOCKET psock = socket(AF_INET, SOCK_DGRAM, 0);
//send(psock, buf, len, 0);
if (len > 0) {
int port = 0;
if (kth->isRecvSock) {
port = 3232;
}
else {
port = 6868;
}
std::cout << "udp:" << port << std::endl;
sendUdpData(kth->sock, buf, len, "127.0.0.1", port);
/*std::cout << "udp output" << buf << std::endl;
std::cout << "udp output size" << len << std::endl;*/
}
//std::cout << buf << std::endl;
return 0;
}
void WriteLog(const char* log, struct IKCPCB* kcp, void* user){
cout << log << endl;
}
void KSock::Init(int kcpId, bool isReceiver, SOCKET s)
{
WSAData wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
return;
sock = socket(PF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == sock) {
//OnError("UDP socket creation error.");
cout << "UDP Error Socket" << endl;
}
unsigned long ul = 1;
int ret = ioctlsocket(sock, FIONBIO, (unsigned long*)&ul); //设置成非阻塞模式
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
isRecvSock = isReceiver;
// 设置套接字选项(可选)
//int timeout = 1000; // 设置接收超时时间为 1000ms
//setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<char*>(&timeout), sizeof(timeout));
if (!isReceiver)
{
//sockAddr.sin_addr.s_addr = inet_addr(ip.c_str()); //htonl(INADDR_ANY);
//sockAddr.sin_port = htons(port);
/*if (connect(sock, (SOCKADDR*)&sockAddr, sizeof(sockAddr)) < 0)
{
printf("connect error\r\n");
}*/
sockAddr.sin_addr.s_addr = inet_addr(ip.c_str());
sockAddr.sin_port = htons(3232);
printf("发送方 端口3232 \r\n");
if (::bind(sock, (SOCKADDR*)&sockAddr, sizeof(sockAddr)) == -1)
{
printf("bind error");
}
}
else {
sockAddr.sin_addr.s_addr = inet_addr(ip.c_str());
sockAddr.sin_port = htons(6868);
if (::bind(sock, (SOCKADDR*)&sockAddr, sizeof(sockAddr)) == -1)
{
printf("bind error");
}
printf("接收方 端口6868 \r\n");
}
//kcp
//初始化kcp
//kcp = ikcp_create(0x11223344, (void*)this);
kcp = ikcp_create(kcpId, (void*)this);
kcp->logmask = IKCP_LOG_OUTPUT;
ikcp_setmtu(kcp, 1024);
kcp->output = UOutPut;
kcp->writelog = WriteLog;
ikcp_wndsize(kcp, 128, 128);
int mode = 0;
if (mode == 0) {
// 默认模式
ikcp_nodelay(kcp, 0, 10, 1, 0);
//ikcp_nodelay(kcp2, 0, 10, 0, 0);
}
else if (mode == 1) {
// 普通模式,关闭流控等
ikcp_nodelay(kcp, 0, 10, 0, 1);
//ikcp_nodelay(kcp2, 0, 10, 0, 1);
}
else {
// 启动快速模式
// 第二个参数 nodelay-启用以后若干常规加速将启动
// 第三个参数 interval为内部处理时钟,默认设置为 10ms
// 第四个参数 resend为快速重传指标,设置为2
// 第五个参数 为是否禁用常规流控,这里禁止
ikcp_nodelay(kcp, 2, 10, 2, 1);
//ikcp_nodelay(kcp2, 2, 10, 2, 1);
kcp->rx_minrto = 10;
kcp->fastresend = 1;
}
//ikcp_nodelay(kcp, 1, 10, 2, 1);
//开启线程
std::thread th(std::bind(&KSock::KCPLoop, this));
th.join();
}
void KSock::Send(char* data, int len)
{
//ikcp_send(kcp, data, len);
}
void KSock::Recv(std::vector<char>& data)
{
char buff[1024];
int nRecvLen = 1;
while (nRecvLen > 0)
{
memset(buff, 0, sizeof(char) * 1024);
nRecvLen = ikcp_recv(kcp, buff, 1024);
for (int i = 0; i < nRecvLen; i++)
{
data.push_back(buff[i]);
}
}
}
void KSock::Release()
{
ikcp_release(kcp);
closesocket(sock);
WSACleanup();
}
SOCKET S;
void KSock::KCPLoop()
{
isRunning = true;
IUINT32 current = NOW();
IUINT32 slap = current + 20;
IUINT32 index = 0;
IUINT32 next = 0;
IINT64 sumrtt = 0;
int count = 0;
int maxrtt = 0;
const int nSendBuffLen = 40240;
const int nRecvBuffLen = 40240;
char sendBuff[nSendBuffLen];
char recvBuff[nRecvBuffLen];
SOCKADDR sockAddr;
int fromlen;
int clntAdrSz = sizeof(sockAddr);
bool bSend = false;
float sec = 0;
while (isRunning)
{
ikcp_update(kcp, NOW());
sec += 10.1f;
//ikcp_send(kcp2, data, len);
//hr = ikcp_recv(kcp1, buffer, 10);
memset(sendBuff, 0, nSendBuffLen * sizeof(char));
memset(recvBuff, 0, nRecvBuffLen * sizeof(char));
//if (isRecvSock)
{
int nRecvLen = 1;
while (nRecvLen > 0)
{
//面向kcp
nRecvLen = recvfrom(sock, recvBuff, 1024, 0, &sockAddr, &clntAdrSz);
//recvLen = recv(sock, data, len, 0);
if (nRecvLen > 0)
{
std::cout << "recv len and put into kcp" << nRecvLen << std::endl;
ikcp_input(kcp, recvBuff, nRecvLen);
//ikcp_send(kcp, recvBuff, nRecvLen);
}
}
nRecvLen = 1;//重置
while (nRecvLen > 0)
{
//上层 面向用户
nRecvLen = ikcp_recv(kcp, recvBuff, nRecvBuffLen * sizeof(char));
//RecvBuffer
if (nRecvLen > 0) {
for (int i = 0; i < nRecvLen; i++)
RecvBuffer.push_back(recvBuff[i]);
cout << "KcpRecv:---" << nRecvLen << endl;
}
}
//ikcp_flush(kcp);
}
// else
if(!isRecvSock)
{
if (!bSend) {
cout << "---------------" << endl;
bSend = true;
int c = 0;
while (SendBuffer.size() < 30000) {
SendBuffer.push_back(c % 65+1);
c++;
}
SendBuffer.push_back('\r');
SendBuffer.push_back('\n');
SendBuffer.push_back('0');
if (SendBuffer.size() > 0)
{
//printf("Send Buff,%d\r\n", SendBuffer.size());
ikcp_send(kcp, SendBuffer.data(), SendBuffer.size());
//sendUdpData(sock, SendBuffer.data(), "127.0.0.1", 6868);
/*SendBuffer.clear();
SendBuffer.shrink_to_fit();*/
//ikcp_flush(kcp);
}
//ikcp_flush(kcp);
}
}
::Sleep(10);
}
}
主函数:
// KcpUdp.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <tchar.h>
#include "KSock.h"
KSock k;
using namespace std;
SOCKET getSocket(std::string ip,int port) {
sockaddr_in sClient;
sClient.sin_family = AF_INET;
sClient.sin_port = htons(port);
//此处或出现bug,解决办法详见下文
//inet_pton(AF_INET, "127.0.0.1", &sClient.sin_addr);
sClient.sin_addr.S_un.S_addr = inet_addr(ip.data());
SOCKET psock = socket(AF_INET, SOCK_DGRAM, 0);
return psock;
}
int main(int argc,TCHAR* argv[])
{
/* SOCKET sRecv = getSocket("127.0.0.1", 6868);
SOCKET sSend = getSocket("127.0.0.1", 6543);*/
SOCKET sRecv =0 ;
SOCKET sSend=0;
bool isRecv = true;
if (argc > 1) {
isRecv = false;
k.Init(0x2345, isRecv, sSend);
}
else {
k.Init(0x2345,isRecv, sRecv);
}
}
接下来是ai的方法:
KCPSender:
#include <iostream>
#include <winsock2.h> // 引入 Windows 套接字库
#include "ikcp.h" // 引入 KCP 的头文件
#pragma comment(lib, "ws2_32.lib") // 链接 winsock 库
// 获取当前时间戳(单位毫秒)
long getCurrentTime() {
LARGE_INTEGER frequency; // 时间计数器频率
LARGE_INTEGER counter; // 当前计数器值
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&counter);
return (counter.QuadPart * 1000) / frequency.QuadPart;
}
// 用于调用 sendto 的 UDP 发送函数
int udp_send(const char* buffer, int len, ikcpcb* kcp, void* user) {
// 获取 socket 和目标地址信息
SOCKET sock = *((SOCKET*)user);
sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(12345); // 服务端的端口号
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务端 IP 地址
// 通过 UDP socket 将数据发送到目标服务器
return sendto(sock, buffer, len, 0, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr));
}
int main() {
// 初始化 Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "Failed to initialize Winsock." << std::endl;
return -1;
}
// 创建 UDP socket
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET) {
std::cerr << "Failed to create socket." << std::endl;
WSACleanup();
return -1;
}
// 设置套接字选项(可选)
int timeout = 1000; // 设置接收超时时间为 1000ms
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<char*>(&timeout), sizeof(timeout));
// 初始化 KCP 实例
ikcpcb* kcp = ikcp_create(123 /*会话ID*/, &sock /*用户数据:实际传递socket*/);
if (!kcp) {
std::cerr << "Failed to create KCP instance." << std::endl;
closesocket(sock);
WSACleanup();
return -1;
}
// 设置 KCP 的发送函数回调。KCP 会调用 `udp_send` 发送底层数据。
kcp->output = udp_send;
// 配置 KCP 参数:快速模式,适合低延迟、实时的场景
ikcp_nodelay(kcp, 1, 10, 2, 1); // 参数配置:
// 第 1 个参数:1 打开快速模式,关闭流控
// 第 2 个参数:触发超时间隔(10ms)
// 第 3 个参数:加倍重传速率(2倍)
// 第 4 个参数:是否开启流控(1表示关闭)
ikcp_wndsize(kcp, 128, 128); // 设置发送窗口和接收窗口大小
std::cout << "KCP successfully initialized on Windows." << std::endl;
char buffer[512]; // 接收缓冲区
const char dataToSend[] = "Hello KCP via UDP in Windows"; // 要发送的数据
long nextUpdateTime = getCurrentTime(); // 下一次定时更新的时间
int n = 0;
while (true) {
long currentTime = getCurrentTime();
if (n < 1) {
// 向 KCP 输入待发送的数据
ikcp_send(kcp, dataToSend, strlen(dataToSend));
n++;
}
// 更新 KCP 状态(处理超时、重传等逻辑)
if (currentTime >= nextUpdateTime) {
ikcp_update(kcp, currentTime);
nextUpdateTime = currentTime + 10; // 下一次更新间隔为 10ms
}
// 从底层 UDP 接收数据包,并交给 KCP 输入进行解析
sockaddr_in fromAddr;
int addrLen = sizeof(fromAddr);
int recvLen = recvfrom(sock, buffer, sizeof(buffer), 0,
reinterpret_cast<sockaddr*>(&fromAddr), &addrLen);
if (recvLen > 0) {
ikcp_input(kcp, buffer, recvLen); // 将收到的 UDP 数据输入到 KCP
// 检查是否有完整的可靠数据到达应用层
char recvBuffer[512];
int recvDataLen = ikcp_recv(kcp, recvBuffer, sizeof(recvBuffer));
if (recvDataLen > 0) {
recvBuffer[recvDataLen] = '\0'; // 确保字符串尾部合法
std::cout << "Received via KCP: " << recvBuffer << std::endl;
}
}
Sleep(5); // 降低 CPU 占用,每次循环休眠 5 毫秒(Windows 平台)
}
// 清理资源
ikcp_release(kcp); // 释放 KCP 实例
closesocket(sock); // 关闭 UDP 套接字
WSACleanup(); // 清理 Winsock 库
return 0;
}
#include <iostream>
#include <winsock2.h> // Windows 套接字库
#include "ikcp.h" // KCP 协议库
#pragma comment(lib, "ws2_32.lib") // 链接 winsock 库
// 用于获取当前时间戳(单位为毫秒)
long getCurrentTime() {
LARGE_INTEGER frequency;
LARGE_INTEGER counter;
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&counter);
return (counter.QuadPart * 1000) / frequency.QuadPart;
}
// 本地 UDP 发送函数(可用于回复对端)
int udp_send(const char* buffer, int len, ikcpcb* kcp, void* user) {
SOCKET sock = *((SOCKET*)user);
sockaddr_in clientAddr;
memset(&clientAddr, 0, sizeof(clientAddr));
clientAddr.sin_family = AF_INET;
clientAddr.sin_port = htons(12345); // 回复目标的端口号
clientAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 默认回复同一地址
return sendto(sock, buffer, len, 0, reinterpret_cast<sockaddr*>(&clientAddr), sizeof(clientAddr));
}
int main() {
// 初始化 Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "Failed to initialize Winsock." << std::endl;
return -1;
}
// 创建 UDP socket
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET) {
std::cerr << "Failed to create socket." << std::endl;
WSACleanup();
return -1;
}
// 配置地址并绑定到本地
sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(12345); // 本地监听端口
serverAddr.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)) == SOCKET_ERROR) {
std::cerr << "Failed to bind socket." << std::endl;
closesocket(sock);
WSACleanup();
return -1;
}
// 初始化 KCP 实例
ikcpcb* kcp = ikcp_create(123 /*会话ID*/, &sock /*传递 socket 给发送回调*/);
if (!kcp) {
std::cerr << "Failed to initialize KCP." << std::endl;
closesocket(sock);
WSACleanup();
return -1;
}
// 设置 KCP 输出回调(用于将 ACK 等数据通过 UDP 返回给发送端)
kcp->output = udp_send;
// 配置 KCP 参数
ikcp_nodelay(kcp, 1, 10, 2, 1); // 第一个参数:打开快速模式;第二个参数:超时时间 10 毫秒;第三个参数:重传加倍;第四个参数:关闭流控
ikcp_wndsize(kcp, 128, 128); // 设置发送窗口和接收窗口大小
std::cout << "Server is ready to receive data at port 12345." << std::endl;
char buffer[512]; // UDP 接收缓冲区
long nextUpdateTime = getCurrentTime(); // 定时更新时间点
while (true) {
long currentTime = getCurrentTime();
// 从 UDP 接收到数据包
sockaddr_in fromAddr;
int addrLen = sizeof(fromAddr);
int recvLen = recvfrom(sock, buffer, sizeof(buffer), 0,
reinterpret_cast<sockaddr*>(&fromAddr), &addrLen);
if (recvLen > 0) {
std::cout << "Received raw UDP packet of length: " << recvLen << std::endl;
// 将数据输入到 KCP(让 KCP 对其解析、组装)
ikcp_input(kcp, buffer, recvLen);
// 检查并提取 KCP 接收缓冲区中的完整应用层数据
char recvBuffer[512]; // KCP 接收缓冲区
int recvDataLen = ikcp_recv(kcp, recvBuffer, sizeof(recvBuffer));
if (recvDataLen > 0) {
recvBuffer[recvDataLen] = '\0'; // 确保字符串结尾合法
std::cout << "Received via KCP: " << recvBuffer << std::endl;
}
}
// 定时驱动 KCP 协议状态更新(处理超时和 ACK 等逻辑)
if (currentTime >= nextUpdateTime) {
ikcp_update(kcp, currentTime);
nextUpdateTime = currentTime + 10; // 每隔 10 毫秒更新一次
}
Sleep(5); // 降低 CPU 占用率,每循环休眠 5 ms
}
// 清理资源
ikcp_release(kcp); // 释放 KCP 实例
closesocket(sock); // 关闭 UDP 套接字
WSACleanup(); // 清理 Winsock 库
return 0;
}
注意ai给的两个文件对应两个项目,再加入ikcp.h,ikcp.c即可运行,先运行接收端,然后运行客户端即可。
如果是我自己的(KSock),再加入ikcp.h,ikcp.c,然后编译后双击运行 (接收端),另外一个(发送端) 需要 加入参数即可(具体看main逻辑)
浙公网安备 33010602011771号