UDP_Server

基于UDP 的通信

UDP_Server

加载库

先写框架

//加载库

//创建套接字

while(true){

//发送消息

//接收消息

}

//关闭套接字

//卸载库

善于使用帮助文档

https://learn.microsoft.com/en-us/windows/win32/api/winsock2/

可以在代码中选中,按F1,直接跳转

语法

返回值:int

[in]:输入参数-需要自己赋值

Version:版本

[out]:输出参数-函数赋值-需要为指针类型

LP:指针

LPWSADATA:WSADATA类型的指针

[in/out]:输入输出参数

返回值

如果成功, WSAStartup 函数返回零。 否则,它将返回下面列出的错误代码之一。

要求

#include<iostream>
 #include<winsock2.h>
 //同一个解决方案下"",不同解决方案下<>
 //导入依赖库
 #pragma comment(lib,"Ws2_32.lib")
 using namespace std;
 int main(){
    //加载库
     WORD version=MAKEWORD(2,2);
     WSADATA data;
     int err=WSAStartup(version,&data);
     //判断返回值
     if(0!=err){
         cout<<"WSASartup error!"<<endl;
         WSACleanup();
         return 1;
     }
     //判断库的版本号对不对
     if(2!=HIBYTE(data.wVersion)||2!=LOBYTE(data.wVersion)){
         cout<<"WSASartup version error"<<endl;
         WSACleanup();
         return 1;
     }else{
         cout<<"wVersion success"<<endl;
     }
 }

创建套接字

语法

af:地址族 ip地址类型-ipv4

type:socket类型-用户数据报

protocol:传输使用的协议-UDP

工具-错误查找里面可以根据返回的错误码查找相关错误

//创建套接字
 SOCKET sock=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
 if(INVALID_SOCKET==sock){
     cout<<"socket error"<<WSAGetLastError()<<endl;
     WSACleanup();
     return 1;
 }else{
     cout<<"socket success"<<endl;
 }

绑定网卡和端口号

告诉操作系统,当前进程使用的是什么类型的ip地址,哪个端口号,哪个ip

语法

[in] s

标识未绑定套接字的描述符。

绑定socket:该进程使用该socket的ip地址端口号用于接收,发送数据。

一个进程可以绑定多个socket。

[in] name

指向要分配给绑定套接字 的本地地址 的 sockaddr 结构的指针。

下面的 sockaddr 结构和sockaddr_in结构用于 IPv4。 其他协议使用类似的结构。

struct sockaddr {
         ushort  sa_family;
         char    sa_data[14];//数据顺序需要自己排列,比较麻烦
 };
 //这两个结构体的大小相同,内容相同,可以进行强转,不会丢失数据
 struct sockaddr_in {
         short   sin_family;
         u_short sin_port;
         struct  in_addr sin_addr;
         char    sin_zero[8];
 };

使用sockaddr_in更便于赋值。

struct in_addr sin_addr;//结构体套联合体

struct in_addr {
   union {
     struct {
       u_char s_b1;
       u_char s_b2;
       u_char s_b3;
       u_char s_b4;
     } S_un_b;
     struct {
       u_short s_w1;
       u_short s_w2;
     } S_un_w;
     u_long S_addr;
   } S_un;
 };

[in] namelen

name 参数指向的值的长度(以字节为单位)。通过指针,不能指定指针指向的空间大小。通过明确指针空间大小,可以避免指针越界。

返回值

如果未发生错误, 绑定 将返回零。 否则,它将返回SOCKET_ERROR,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。

//给结构体赋值
sockaddr_in addr;
addr.sin_family=AF_INET;
//数字大的端口号无人使用 涉及大小端问题 规定网络字节序 htons 转换成大端
addr.sin_port=htons(6666);
addr.sin_addr.S_un.S_addr = INADDR_ANY;
err=bind(sock,(sockaddr*)&addr,sizeof(addr));//指针和指针才可以强转
if(SOCKET_ERROR==err){
    cout<<"bind error"<<WSAGetLastError()<<endl;
    //关闭套接字
    closesocket();
    //卸载库
    WSACleanup();
    return 1;
}else{
    cout<<"bind success"<<endl;
}

发送接收数据

接收数据

int WSAAPI recvfrom(
  [in]                SOCKET   s,//本进程使用的套接字
  [out]               char     *buf,//数据接收从哪个地址开始
  [in]                int      len,//可接收的数据大小
  [in]                int      flags,//初始化标志位-0-默认方式接收
  [out]               sockaddr *from,//接收到的数据的来源地址
  [in, out, optional] int      *fromlen//地址的长度存储的位置
);

参数

[in] s

标识绑定套接字的描述符。

[out] buf

传入数据的缓冲区。

[in] len

buf 参数指向的缓冲区的长度(以字节为单位)。

[in] flags

一组选项,用于修改函数调用的行为,超出为关联套接字指定的选项。

[out] from

指向 sockaddr 结构中的缓冲区的可选指针,该缓冲区将在返回时保存源地址。

[in, out, optional] fromlen

指向 from 参数指向的缓冲区大小(以字节为单位)的可选指针。

返回值

如果未发生错误, recvfrom 将返回收到的字节数。 如果连接已正常关闭,则返回值为零。 否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。

int recvNum=0;
int sendNum=0;
char recvBuf[9999]="";
char sendBuf[9999]="";
sockaddr_in addrClient;
int addrClientSize=sizeof(addrClient);
while(true){
    
    recvNum=recvfrom(sock,recvBuf,sizeof(recvBuf),0,(sockaddr*)&addrClient,&addrClientSize);
    if(recvNum>0){
        cout<<"ip"<<inet_ntoa(addrcClient.sin_addr)<<"say:"<<recvBuf<<endl;
    }else{
        cout<<"recv error"<<WSAGetLastError()<<endl;
        break;
    }
    //发送数据
    gets(sendBuf);
    sendNum=sendto(sock,sendBuf,sizeof(sendBuf),0,(sockaddr*)&addrClient,addrClientSize);
    if(SOCKET_ERROR==sendNum){
        cout<<"send error"<<WSAGetLastError()<<endl;
        break;
    }else{
        cout<<"send success"<<endl;
    }
}

closesocket(sock);
WSACleanup();

return 0;

发送数据

sendto 语法

int WSAAPI sendto(
  [in] SOCKET         s,//发送使用的socket
  [in] const char     *buf,//从哪个地址开始写入数据
  [in] int            len,//可使用的空间大小
  [in] int            flags,//使用的接收方式
  [in] const sockaddr *to,//发送给哪个ip地址所在的位置
  [in] int            tolen//该IP地址所占的长度
);

参数

[in] s

标识可能连接的 () 套接字的描述符。使用的套接字。

[in] buf

指向包含要传输的数据的缓冲区的指针。

[in] len

buf 参数指向的数据的长度(以字节为单位)。

[in] flags

一组指定调用方式的标志。0-最简单的接收方式。

[in] to

指向包含目标套接字地址 的 sockaddr 结构的可选指针。

[in] tolen

由 to 参数指向的地址的大小(以字节为单位)。

返回值

如果未发生错误, sendto 将返回发送的总字节数,这可能小于 len 指示的数量。 否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 检索特定的错误代码。

地址形式转换

IP地址分类:

  1. 字符串类型 十进制四等分类型“192.168.3.222”,方便用户查看。
  2. ulong类型可存储更长的数据。

两种地址转换

字符串->ulong

  1. inet_addr();

ulong->字符串

  1. inet_ntoa();//实际上是in_addr转字符串

完整代码

#include<iostream>
//头文件不在当前解决方案<> 在当前解决方案" "
#include<winsock2.h >
//导入依赖库
#pragma comment(lib,"Ws2_32.lib")//立即导入 lib-library
using namespace std;

int main()
{
	//1.加载库(使用库函数)/静态库 添加头文件,导入依赖库(lib+库名)
	
	// 加载库相当于把exe拷入到
	//动态库 添加头文件,导入依赖库 动态库dll和exe放在一起然后才能用
	//W-windows S-socket A-api Windows里面关于Socket的一个接口
	WORD version = MAKEWORD(2, 2);//输入参数需要赋值
	WSADATA data;//输出参数不需要赋值
	int err = WSAStartup(version,&data);//未定义标识符 没有导入库
	//实现在库里面实现 除了需要引用头文件,还需要导入依赖库
	
	//函数参数填完了需要判断返回值
	//判断函数调用是否成功 成功就继续,失败就停止 return
	if(0!=err){
		cout << "WSAStartup error" << endl;
		return 1;
	}

	//判断库的版本号对不对
	//函数调用成功了但是库的版本号可能不对,因此需要判断版本号是否正确
	if (2 != HIBYTE(data.wVersion) || 2 != LOBYTE(data.wVersion)) {
		cout << "WSAStartup version error " << endl;
		//卸载库
		WSACleanup();
		return 1;
	}
	else {
		cout << "WSAStartup success" << endl;
	}


	//2.创建套接字(通信的基石,得先有套接字后面才能用)

	SOCKET sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (INVALID_SOCKET == sock) {
		cout << "socket error" << "  " << WSAGetLastError() << endl;
		//卸载库
		WSACleanup();
		return 1;
	}
	else {
		cout << "socket succuss" << endl;
	}

	//3.绑定网卡和端口号,(告诉系统可以接收发给哪个网卡和端口号的数据)
	//告诉操作系统,当前进程使用的是什么类型的ip地址,哪个端口号,哪个ip
	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(67890);//数字大的端口号无人使用//端口 涉及大小端问题 规定网络字节序 转换成大端
	addr.sin_addr.S_un.S_addr = INADDR_ANY;//地址 绑定任意(所有)网卡 
	//ip地址分两种类型:字符串类型 十进制四等分类型"192.168.3.222"(方便用户看) ulong类型(更长)
	//两种类型相互转换 字符串类型转 ulong: inet_addr()
	//ulong转字符串: inet_ntoa() 实际上是in_addr转字符串
	err=bind(sock, (sockaddr*)&addr, sizeof(addr));//指针和指针才能强转
	if (SOCKET_ERROR == err) {
		cout << "bind error" << WSAGetLastError() << endl;//只有网络操作的时候WSAGetLastError可用
		closesocket(sock);//一个进程可以有多个套接字,需要指定关闭哪个套接字
		WSACleanup();
		return 1;
	}
	else {
		cout << "bind success" << endl;
	}
	//传输数据:发送和接收数据,是一个循环
	//先接收数据的是服务端

	int nRecvNum = 0;//局部变量定义在循环的外面 提高效率 避免反复的申请空间
	//减少申请空间的时间
	int nSendNum = 0;
	char recvBuf[9999] = "";
	char sendBuf[9999] = "";
	sockaddr_in addrClient;
	int addrClientSize = sizeof(addrClient);
	while (true) {
		//4.接收数据(阻塞函数 有一步再进行下一步)
		//接收使用的套,接收到的数据,数据的大小,接收的方式,接收方的地址,地址的长度
		nRecvNum=recvfrom(sock,recvBuf,sizeof(recvBuf),0,(sockaddr*)&addrClient,&addrClientSize);
		if (nRecvNum > 0) {
			//打印接收到的数据 接收的ip地址ulong转char
			cout << "ip:"<< inet_ntoa(addrClient.sin_addr)<<" say:" << recvBuf << endl;
		}
		else {
			cout << "recv error" << WSAGetLastError() << endl;
			break;
		}
		//5.发送数据
		gets_s(sendBuf);
		nSendNum=sendto(sock,sendBuf,sizeof(sendBuf),0,(sockaddr*)&addrClient,addrClientSize);
		if (SOCKET_ERROR==nSendNum) {
			cout << "send error" << WSAGetLastError() << endl;
			break;

		}
		else{
			cout << "send success" << endl;
		}
	}
	//6.关闭套接字
	closesocket(sock);

	//8.卸载库
	WSACleanup();


	return 0;
	//inet_nota 函数太老系统不适配,关闭SDL 安全检查
}

完整代码2

#include<iostream>
#include<winsock2.h>
using namespace std;
#pragma comment(lib,"Ws2_32.lib")
int main() {
//1 加载库
	WORD version = MAKEWORD(2, 2);
	WSADATA data;
	int err=WSAStartup(version,&data);
	if (0 != err) {
		cout << "WSAStartup err" << endl;
		return 1;
	}
	else {
		cout << "WSASTartup success" << endl;
	}
	if (2 != HIBYTE(data.wVersion) || 2 != LOBYTE(data.wVersion)) {
		cout << "Version err" << endl;
		WSACleanup();
		return 1;
	}
	else {
		cout << "Version right" << endl;
	}
//2创建套接字
	SOCKET sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
	if (INVALID_SOCKET == sock) {
		cout << "socket err " << WSAGetLastError()<<endl;
		WSACleanup();
		return 1;
	}
	else {
		cout << "socket success" << endl;
	}
	sockaddr_in addr;
	addr.sin_addr.S_un.S_addr = INADDR_ANY;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(56789);
	err = bind(sock,(sockaddr*)&addr,sizeof(addr));
	if (SOCKET_ERROR == err) {
		cout << "bind error" << GetLastError() << endl;
		closesocket(sock);
		WSACleanup();
		return 1;
	}else{
		cout << "bind success" << endl;
	
	}
	char sendBuf[9999] = "";
	char recvBuf[9999] = "";
	int sendBuflen = sizeof(sendBuf);
	int recvBufsize = sizeof(recvBuf);
	sockaddr_in addrTo;
	int addrToLen = sizeof(addrTo);
	while (true) {
		//3循环接受数据
		int recvNum = recvfrom(sock,recvBuf,recvBufsize,0,(sockaddr*)&addrTo,&addrToLen);
		if (recvNum<0) {
			cout << "recvv erro" << WSAGetLastError() << endl;
			break;
		}
		else {
			cout << "ip:" << inet_ntoa(addrTo.sin_addr) << "  say:" <<recvBuf<< endl;
		}
	//4发送数据
		gets_s(sendBuf);
		int sendNum = sendto(sock, sendBuf, sendBuflen, 0, (sockaddr*)&addrTo, sizeof(addrTo));
		if (SOCKET_ERROR == sendNum) {
			cout << "send error" << WSAGetLastError() << endl;
			break;
		}
		else {
			cout << "send success" << endl;
		}
	}



//5关闭套接字
	WSACleanup();
//6卸载库
	closesocket(sock);

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值