这几天看protobuf udp都快有点不耐烦了。udp其实感觉写起来比TCP的东西还简单一些,先是写了个控制台的,写了之后想做个界面的,看起来牛逼一点。动手写后,发现mfc里面字符转换啥的,很麻烦,网上找的东西在vs2017里面都用不了。又不想用vc那套老东西了,所以又得找新的资料。反正找的超级烦,本来东西也不是很复杂,但是还是花了不少时间。今天晚上有时间就把东西给记下来了,过几天再实践一下protobuf。
UDP基本操作函数
其实函数和TCP的都差不多。
1. socket()函数
SOCKET socket(int af, int type, int protocol)
- af IPv4就写AF_INET
- type 写SOCK_DGRAM
- protocol 填0就可以了。因为通过前面两个参数一般就可以确定后面的这个是什么类型,前面填了AF_INET,SOCK_DGRAM 适用的就是UDP了。
2. bind()函数
这里服务器调用就可以了,将本机IP地址,选定的一个端口与前面的套接字绑定。客户端的话,就可以不用了。
int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);
- 如果bind()返回成功,就会返回0。
- 如果返回不成功,可以调用GetLastError() 函数,通过错误编码查看是什么错误。
3. 数据的接收和发送
哈哈,这里不像TCP那里那么麻烦,还有一堆函数。服务器绑定地址后,就可以直接发送接受消息了。当然客户端创建成功套接字后也就可以直接接受和发送了。
我用的是sendto(),recvfrom()。
1). sendto()
int sendto(
_In_ SOCKET s,//前面创建的套接字
_In_ const char *buf,//要发送的消息
_In_ int len,//消息长度
_In_ int flags,
_In_ const struct sockaddr *to,//要发的目的地址
_In_ int tolen//to的长度值
);
- to 就是要送的目的端的地址,所以发送之前得赋值号目的端的IP,端口号等。
2). recvfrom()
int recvfrom(
_In_ SOCKET s,//前面创建的套接字
_Out_ char *buf,//接受消息的数据缓冲区
_In_ int len,//缓冲区长度
_In_ int flags,//作用我也没试过,填0就是 哈哈
_Out_ struct sockaddr *from,//指向装有源地址的缓冲区 传个未赋值的地址指针就行,会返回出发送方的地址
_Inout_opt_ int *fromlen//指向from缓冲区长度值。
返回
);
- from fromlen两个都是指针类型
- recvform()有阻塞作用,就是运行到这一句程序会暂停,直到有消息发送过来。
4. 关闭连接
调用closesocket()。
源码
- 服务器端
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
using namespace std;
#pragma comment (lib,"ws2_32.lib")
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);//这里因为是Windows下所以要先加载这个
SOCKET sersock = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_in sockAddr;//创建一个sockaddr_in 结构体,分配一个端口号
memset(&sockAddr, 0, sizeof(sockaddr));
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//填本机的IP也可以
sockAddr.sin_port = htons(8888);
int test;
test = bind(sersock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));//将套接字与地址绑定
if(test != 0)//如果出错可以了通过这个找的错误编码
{
DWORD err;
err = GetLastError();
cout << "error : " << err << endl;
return -1;
}
char buf[100] = { 0 };
sockaddr_in clnAddr;
int len = sizeof(clnAddr);
memset(&clnAddr,0,len);//清空这个结构体
recvfrom(sersock, buf, 100, 0, (SOCKADDR*)&clnAddr, &len);//等待客户端发送数据
//如果recvfrom()返回成功,clnAddr会的填入对方的IP地址和端口,所以后面用这个地址又可以继续向对方发送消息了
cout << buf << endl;
cout << ntohs(clnAddr.sin_port) << endl;
strcpy(buf, "hhh");
sendto(sersock, buf, 100, 0, (SOCKADDR*)&clnAddr, len);//向前面的clnAddr方发送消息 就是发送的时候的得知道对方的地址、端口号
closesocket(sersock);
WSACleanup();
system("pause");
}
- 客户端
#include <iostream>
#include <string>
#include <winsock2.h>
#include <ws2tcpip.h>
using namespace std;
#pragma comment (lib,"ws2_32.lib")
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET clnsock = socket(PF_INET, SOCK_DGRAM, 0);
sockaddr_in sockAddr;//这里存储的是服务器的地址、端口号
memset(&sockAddr, 0, sizeof(sockaddr));
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(8888);
char buf[100] = { 0 };
cin >> buf;
int len = sizeof(sockAddr);
sendto(clnsock, buf, strlen(buf), 0, (SOCKADDR*)&sockAddr, len);//向服务器发送消息
memset(&sockAddr, 0, sizeof(sockAddr));//然后将这个结构体清空,再用在下面的recvfrom()函数里面,会发现里面又填充了发送方的地址
recvfrom(clnsock, buf, 100, 0, (SOCKADDR*)&sockAddr, &len);
cout << buf << endl;
closesocket(clnsock);
WSACleanup();
system("pause");
}