【计算机网络】基于UDP的简单通讯(服务端)

news/2024/5/18 16:09:05 标签: 计算机网络, udp, 网络协议

文章目录

    • 流程
    • 代码实现
      • 加载库
      • 创建套接字
      • 绑定ip
      • 接收数据
      • 发送数据
      • 关闭套接字、卸载库

流程

我们UDP通讯就像是在做小买卖,主要就是进行收发数据
在这里插入图片描述

实现UDP协议的服务端需要经过五步操作:

  1. 加载库(Ws2_32.lib)
  2. 创建套接字(socket())
  3. 绑定IP(bind())
  4. 收发数据(recvfrom()、sendto())
  5. 关闭套接字、卸载库(closesocket()、WSACleanup())

代码实现

加载库

在加载库时我们使用一个WSAStartup接口函数,它的返回值是int类型,是用来看是否加载成功的,参数有两个,第一个是输入参数,为WORD类型,用来输入版本号,第二个是输出参数,为WSADATA结构体类型,输出参数一般都为指针类型,所以我们要创建三个变量。由于用到的函数和数据类型都是WinSock2.h库中的,所以我们要先加载头文件

#include<iostream>
#include<WinSock2.h>
using namespace std;

加载库:

    int err = 0;
    WORD version = MAKEWORD(2, 2);
    WSADATA wsaData;
    err = WSAStartup(version, &wsaData);
    //判断返回值
    if (0 != err) {
        cout << "WSAStartup error" << endl;
        return 1;
    }
    //判断加载的版本是否是2.2版本
    if (2 != HIBYTE(wsaData.wVersion) || 2 != LOBYTE(wsaData.wVersion)) {
        cout << "WSAStartup version error" << endl;
        //卸载库
        WSACleanup();
        return 1;
    }else {
            cout << "WSAStartup success" << endl;
    }

创建套接字

创建套接字我们使用socket()函数,它的返回值为SOCKET类型,如果返回INVALID_SOCKET那么创建失败,我们可以通过WSAGetLastError()来打印错误码

socket()有三个参数,都为int类型,第一个参数af是address family的缩写,我们使用AF_INET(ipv4),第二个参数是type,我们使用Udp协议的类型SOCK_DGRAM,第三个参数是protocol,我们使用UDP协议的IPPROTO_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

使用bind()函数,返回值为int类型,如果返回值为SOCK_ERROR那就说明绑定失败了,有三个输入参数,第一个参数为SOCKET,第二个参数为sockaddr*,他是一个结构体指针,第三个参数为指针长度

因为结构体为输入参数,所以我们要为里面的参数赋值,它一共有两个参数,第一个是一个ushort类型,第二个是char数组,那么我们对char数组赋值时会特别麻烦,因为要按照一定的顺序进行赋值,所以这里还给了另一个和sockaddr一样大小的数组——sockaddr_in,这个数组就是将char数组分解成了好几个变量,我们只需要对这几个变量进行赋值就可以了。第一个变量是ip地址类型,我们用的ipv4类型,第二个是端口号,第三个是ip地址

在定义端口号时,由于不同计算机可能存储方式不同,可能是大端存储也可能是小端存储,所以我们有一个规定——网络字节序,是TCP/IP中规定好的一种数据表示格式,可以保证数据在不同主机之间传输时能够被正确解释。用到一个函数htons(),再绑定IP地址时,因为我们是接收所有网卡收到的数据,所以我们对主机内任意网卡都进行绑定。

	//是操作系统里面注册端口和ip地址,也就是说当前操作系统收到发给某个端口号和ip地址的数据,就是咱么程序要接收的
	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(456789);  //转换成网络字节序,也就是大端存储,本机是小端存储
	addr.sin_addr.S_un.S_addr = INADDR_ANY;  //绑定所有网卡

	err = bind(sock,(sockaddr*)&addr,sizeof(addr));
	if (SOCKET_ERROR == err) {
		cout << "bind error" << endl;
		//关闭套接字
		closesocket(sock);
		//卸载库
		WSACleanup();
		return 1;
	}
	else {
		cout << "bind success" << endl;
	}

接收数据

接收数据我们使用recvfrom()函数,它的返回值有三种,如果接收数据成功就返回接收到的字节的个数,等于0就证明连接失败了,如果等于SOCK_ERROR就是接收失败了

函数的参数有六个,第一个为socket,意为使用哪个socket进行接收,第二个参数为char*,是一个输出参数,是用来接收数据的缓冲区,第三个参数为这个缓冲区的大小,第四个参数是一个标志位,用来决定当前的接收方式,我们在这里不做特殊设置,用默认的即可,下一个参数也是一个sockaddr *输出类型的参数,用来存放数据是从哪里来的,最后一个参数当然就是上一个参数的长度,但由于它属于是输出类型的参数,所以要变为指针类型

	int nRecvNum = 0;
	char recvBuf[1024] = "";
	sockaddr_in addrClient;
	int addrClientSize = sizeof(addrClient);
	while (true) {
		//4、接收数据
		nRecvNum = recvfrom(sock, recvBuf,sizeof(recvBuf),0, (sockaddr*)&addrClient,&addrClientSize);
		if (nRecvNum > 0) {
			//接收成功,打印一下接收到的数据内容和发送端的ip地址
			//"192.168.3.145"十进制四等分字符串类型ip地址
			//ulong类型的ip地址:addrClient.sin_addr.S_un.S_addr
			cout << "ip:" << inet_ntoa(addrClient.sin_addr) << " say: " << recvBuf << endl;
			//从ulong转换成字符串类型ip:inet_ntoa(addrClient.sin_addr);
			//从字符串类型转换成ulong类型的ip地址:inet_addr();
		}
		else {
			//接收失败,打印失败日志,结束循环
			cout << "recvfrom error" << WSAGetLastError() << endl;
			break;
		}
    }

发送数据

发送数据使用的是sendto()函数,他也需要卸载循环里,接在上面接收数据后面即可,比如我们发送一个“hahaha”,sendto函数返回值为int类型,如果等于SOCKET_ERROR,那么就是发送失败,它也有六个参数,和接收数据也十分相似,首先是发送用到的socket,然后是发送数据缓冲区和缓冲区大小,然后是标志位,最后是要发送的目标和它的大小,这些都为输入参数

因为我们这里是服务端,所以谁给我们发我们就会给谁一个hahaha,所以目标我们就填接收数据时用来接收的sockaddr

    char msg[] = "hahaha";
    nSendNum = sendto(sock,msg,sizeof(msg),0,(sockaddr*)&addrClient, addrClientSize);
    if (SOCKET_ERROR == nSendNum) {
        //发送失败,打印失败日志,结束循环
        cout << "sendto error" << WSAGetLastError() << endl;
        break;
    }

关闭套接字、卸载库

关闭套接字用到的函数为closesocket(),卸载库就是WSACleanup(),这两个函数在上面也都用到过了,这里就不在赘述了

	closesocket(sock);
	WSACleanup();

现在代码部分我们都写好了,还有一些可能需要的操作,首先我们在尝试运行的时候会发现inet_ntoa会报错,我们可以到项目属性中去将SDL检查关闭即可

在这里插入图片描述

在这里插入图片描述

再次运行,我们会发现出现了许多无法解析的外部符号的错误,那么是因为编译期找不到函数的实现,那么这些函数都是我们直接调用的,所以解决方法就是加载所需要的库

#pragma comment(lib,"Ws2_32.lib")

那么到此为止,我们的UDP服务端就写好了,测试一下也没什么问题,接下来我们就要写客户端了

在这里插入图片描述


http://www.niftyadmin.cn/n/5050238.html

相关文章

通俗讲解MobileNet-v1/v2/v3网络

MobileNet网络是由google团队在2017年提出的&#xff0c;专注于移动端或者嵌入式设备中的轻量级CNN网络。相比传统卷积神经网络&#xff0c;在准确率小幅降低的前提下大大减少模型参数与运算量。(相比VGG16准确率减少了0.9%&#xff0c;但模型参数只有VGG的1/32)。MobileNet网络…

【OpenSSL】OpenSSL实现Base64

Base 64概述和应用场景 概述 Base64就是将二进制数据转换为字符串的一种算法。 应用场景 邮件编码xml或则json存储二进制内容网页传递数据URL数据库中以文本形式存放二进制数据可打印的比特币钱包地址base58Check(hash校验)网页上可以将图片直接使用Base64表达公私密钥的文…

Redis初步学习

简单了解 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存数据库&#xff0c;也被称为数据结构服务器。它以键值对的形式存储数据&#xff0c;并提供了丰富的数据结构&#xff0c;如字符串、列表、集合、有序集合、哈希表等。以下是对Redis的详细介绍…

实用笔记-java配置

防止备忘&#xff0c;作备忘录所用&#xff01;&#xff01;&#xff01; 1.获取配置文件中的值 /**** application配置文件自定义属性文件*/ Component Configuration ConfigurationProperties(prefix"test") //接收application.yml中的dscg下面的属性 Data publi…

Purple-Pi-OH OHOS SDK编译手册

一、源码获取 1.1 源码获取 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;1234 $ mkdir purple-pi #将下载的ido_purple_pi_oh_ohos3.2_sdk.tgz拷贝到purple-pi $ cd purple-pi $ md5sum ido_purple_pi_oh_ohos3.2_sdk.tgz e6ca2d96aa7c628992ae0bbf4d14c2ca …

基于R语言分位数回归丨线性回归假设与分位数函数、线性分位数回归 、贝叶斯分位数回归、超越线性分位数回归等

目录 专题一 线性回归假设与分位数函数讲解 专题二 线性分位数回归 【代码实践】 专题三 贝叶斯分位数回归【代码实践】 专题四 超越线性分位数回归&#xff08;一&#xff09;【代码实践】 专题五 超越线性分位数回归&#xff08;二&#xff09;【代码实践】 更多应用 回…

Qt_C++读写NFC标签Ntag支持windows国产linux操作系统

本示例使用的发卡器&#xff1a;Android Linux RFID读写器NFC发卡器WEB可编程NDEF文本/智能海报/-淘宝网 (taobao.com) ntag2标签存储结构说明 #include "mainwindow.h" #include "./ui_mainwindow.h" #include <QDebug> #include "QLibrary&…

02-Scala变量与数据类型

注释 ​ Scala注释使用和Java完全一样。注释是一个程序员必须要具有的良好编程习惯。将自己的思想通过注释先整理出来&#xff0c;再用代码去体现。 单行注释多行注释文档注释 变量与常量 常量&#xff1a;在程序执行的过程中&#xff0c;其值不会被改变的变量 Java中变量…