yang131

导航

使用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逻辑)

 

posted on 2025-04-22 23:29  NoNight  阅读(126)  评论(0)    收藏  举报