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

文章目录

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

流程

我们UDP通讯就像是在做小买卖,主要就是进行收发数据
【计算机网络】基于UDP的简单通讯(服务端)_第1张图片

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

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

代码实现

加载库

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

#include
#include
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检查关闭即可

【计算机网络】基于UDP的简单通讯(服务端)_第2张图片

【计算机网络】基于UDP的简单通讯(服务端)_第3张图片

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

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

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

【计算机网络】基于UDP的简单通讯(服务端)_第4张图片

你可能感兴趣的:(计算机网络(网络编程),计算机网络,udp,网络协议)