用户数据报协议UDP是一种不可靠、无连接的协议,主要适用于不需要对数据报进行排序和流量控制的场合,它的速度效率较高。
使用UDP进行网络编程的流程如下图所示。
UDP进行网络编程的控制台程序基础代码如下。
Server服务器端
#include<stdio.h>
#include<iostream>
#include<Winsock2.h>
#include<Windows.h>
#pragma comment(lib,"WS2_32.lib")
using namespace std;
int main() {
//加载Winsock库
WSADATA wsaData;
int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
/*MAKEWORD声明调用不同的Winsock版本。例如MAKEWORD(2,2)就是调用2.2版,MAKEWORD(1,1)就是调用1.1版。
不同版本是有区别的,例如1.1版只支持TCP / IP协议,而2.0版可以支持多协议。
2.0版有良好的向后兼容性,任何使用1.1版的源代码、二进制文件、应用程序都可以不加修改地在2.0规范下使用。
此外winsock 2.0支持异步 1.1不支持异步.*/
//判断是否成功创建Winsock库
if (ret != 0) {
cout << "WSAStarup error" << endl;
return 0;
}
cout << "WSAStarup success" << endl;
//创建套接字,套接字穿件成功后,返回套接字的句柄
SOCKET s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
/*SOCKET socket{
int af, //用来制定套接字使用的地址格式,一般固定使用AF_INET
int type, //制定套接字类型。SOCK_STREAM流套接字(TCP),SOCK_DGRAM数据报套接字(UDP),SOCK_RAW原始套接字
int protocol //配合type使用,用来制定协议的类型可以是IPPROTO_TCP或IPPROTO_UDP,当type参数明确指定使用TCP或者UDP,参数可以为0
}*/
//判断套接字是否创建成功
if (s == INVALID_SOCKET) {
cout << "socket error" << endl;
return 0;
}
cout << "socket success" << endl;
//设置套接字网络地址信息,使用结构体sockaddr_in设置地址信息
sockaddr_in server;
server.sin_family = AF_INET; //协议族。AF_INET表示使用IP地址家族
server.sin_port = htons(5000); //设置端口号
server.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//设置IP地址。INADDR_ANY系统自动使用当前主机配置的额IP地址
//通过bing()为套接字关联本地地址
int r = bind(s, (LPSOCKADDR)&server, sizeof(server));
/*int bind{
SOCKET s, //套接字句柄
const struct sockaddr* name, //要关联的本地地址
int namelen //地址长度
}*/
//判断地址是否绑定成功
if (r == SOCKET_ERROR) {
cout << "bind error" << endl;
return 0;
}
cout << "bind success" << endl;
//声明一个网络地址结构体用于接受和发送函数中
sockaddr_in addr;
int addrlen = sizeof(addr);
//接受缓冲区
char recvbuf[1024];
//发送缓冲区
char sendbuf[1024];
//循环进行信息接发送
while (true) {
//接受函数recvfrom,返回值是接收到的数据个数
int nRecv = recvfrom(s, recvbuf, 1024, 0, (sockaddr*)&addr, &addrlen);
/*int recvfrom{
SOCKET s, //用来接受数据的套接字
char FAR* buf, //接受数据的字符缓冲区
int len, //准备接受的字节数或buf缓冲区长度
int flags, //一般为0
struct sockaddr FAR* from, //SOCKADDR结构,为接受到的客户端地址信息的sockaddr_in结构
int FAR* fronlen, //地址结构的长度
}*/
//判断是否接受到数据,并输出接收到数据的客户端ip地址和接受到的数据
if (nRecv > 0) {
recvbuf[nRecv] = '\0'; //将没有接受到数据的空间隔离开
cout << "client sended:(" << inet_ntoa(addr.sin_addr) << ");" << recvbuf << endl;
//inet_ntoa将一个网络字节顺序的32位IP地址转为字符串
}
//输入需要发送的数据
cout << "input the sending data:" << endl;
cin >> sendbuf;
//发送函数sendto,并判断是否发送成功
if (sendto(s, sendbuf, strlen(sendbuf), 0, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
/*int sendto{
SOCKET s, //用来发送数据的套接字
char FAR* buf, //发送数据的字符缓冲区
int len, //发送的字节数
int flags, //一般为0
const struct sockaddr FAR* to, //一个包含目标地址和端口号的sockaddr_in结构
int FAR* tolen, //sockaddr_in结构的大小
}*/
cout << "send error" << endl;
return 0;
}
cout << "send success" << endl;
}
//关闭套接字
closesocket(s);
//释放Winsock库和WSAStartup的调用对应
WSACleanup();
return 0;
}
Client客户端
#include<stdio.h>
#include<iostream>
#include<WinSock2.h>
#include<Windows.h>
#pragma comment(lib,"WS2_32.lib")
using namespace std;
int main() {
WSAData wsaData;
int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (ret != 0) {
cout << "wsastartup error" << endl;
return 0;
}
cout << "wsastartup success" << endl;
SOCKET s;
s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s == INVALID_SOCKET) {
cout << "socket error" << endl;
return 0;
}
cout << "socket success" << endl;
//设置服务器网络地址信息
sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(5000);
server.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //127.0.0.1表示本机地址
//inet_addr将一个有小数点分隔的十进制IP地址字符串转化为二进制表示的IP地址
//发送缓存区
char sendbuf[1024];
sockaddr_in addr;
int addrlen = sizeof(addr);
//接受缓存区
char recvbuf[1024];
while (true) {
//发送
cout << "input the sending data:" << endl;
cin >> sendbuf;
int r = sendto(s, sendbuf, strlen(sendbuf), 0, (sockaddr*)&server, sizeof(server));
if (r == SOCKET_ERROR) {
cout << "send error" << endl;
return 0;
}
//接受
int rr = recvfrom(s, recvbuf, 1024, 0, (sockaddr*)&addr, &addrlen);
if (rr > 0) {
recvbuf[rr] = '\0';
cout << "server:(" << inet_ntoa(addr.sin_addr) << "):" << recvbuf << endl;
}
}
closesocket(s);
WSACleanup();
return 0;
}
注意:
运行时需要先打开服务器(运行服务器程序),再打开客户端(运行客户端程序)。
只有先启动了服务器后,客户端在启动时才能连接到服务器。否则先打开客户端再启动服务器,客户端在启动时会连接服务器失败。
提示
如果在运行时出现了下面的错误
error C4996: ‘inet_ntoa’: Use inet_ntop() or InetNtop() instead or define _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings
解决方法在笔者的上一篇文章VS2017使用inet_ntoa()产生错误的解决方法