本章讲简单共享UDP异步通讯例程,udp是一种无连接、不保证数据是否传递成功,比起tcp通讯,它优势是传输数据快,因为它忽略了可靠传输机制。
所谓异步就是接受数据的时候不会产生阻塞效果,函数要不采用回调机制,要不就是直接返回。
工程是用基于VS2010 MFC下的。
新建通讯类UDPClientClass.h
#include
#define UDP_READ WM_USER + 3
#define READ_MAX_BUFFER 32 * 1024
class UDPClientClass
{
private:
SOCKADDR_IN addrTo;
SOCKET sckClient;
WSABUF writeWsabuf;
WSABUF readWsabuf;
HWND curHwnd;
public:
UDPClientClass()
{
sckClient = INVALID_SOCKET;
}
~UDPClientClass()
{
Close();
}
BOOL Init()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return false;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return FALSE;
}
return true;
}
/**
打开UDP
返回值为true则打开,否则无法发送数据
*/
BOOL Open(const char *ip, UINT16 port, HWND hwnd)
{
if (!Init())
{
return FALSE;
}
sckClient = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, 0);
if (sckClient == INVALID_SOCKET)
{
return FALSE;
}
addrTo.sin_addr.S_un.S_addr = inet_addr(ip);
addrTo.sin_family = AF_INET;
addrTo.sin_port = htons(port);
curHwnd = hwnd; //绑定句柄和UDP_READ消息
if (WSAAsyncSelect(sckClient, curHwnd, UDP_READ, FD_READ) == SOCKET_ERROR)
{
return FALSE;
}
int nRecvBuf =READ_MAX_BUFFER; //接受缓冲区设置为32K
setsockopt(sckClient, SOL_SOCKET, SO_RCVBUF, (const char*)&nRecvBuf,sizeof(int));
return TRUE;
}
/**
接受数据
data:是接受数据的首指针
addrFrom:服务端的IP地址端口结构指针
*/
DWORD read(UINT8 *data, SOCKADDR_IN *addrFrom, WPARAM w, LPARAM l)
{
switch (LOWORD(l))
{
case FD_READ:
DWORD dwRead;
DWORD dwFlag = 0;
readWsabuf.len = READ_MAX_BUFFER;
readWsabuf.buf = (char *)data;
int len = sizeof(SOCKADDR);
if (WSARecvFrom(sckClient, &readWsabuf, 1, &dwRead, &dwFlag, (SOCKADDR *)addrFrom, &len, NULL, NULL) == SOCKET_ERROR)
{
return 0;
}
return dwRead;
}
return 0;
}
/**
发送数据
*/
int write(UINT8 *data, UINT32 len)
{
DWORD dwSend;
writeWsabuf.buf = (char *)data;
writeWsabuf.len = len;
if (sckClient == INVALID_SOCKET)//如果没有打开UDP,则无法发送数据,直接返回0
{
return 0;
}
if (WSASendTo(sckClient, &writeWsabuf, 1, &dwSend, 0, (SOCKADDR *)&addrTo, sizeof(SOCKADDR), NULL, NULL) == SOCKET_ERROR)
{
return 0;
}
return dwSend;
}
/**
判断是否打开UDP
*/
BOOL IsOpen()
{
if (sckClient != INVALID_SOCKET)
{
return TRUE;
}
return FALSE;
}
/**
关闭UDP
*/
void Close()
{
if (sckClient != INVALID_SOCKET)
{
WSACleanup();
sckClient = INVALID_SOCKET;
}
}
};
在stdafx.h添加代码,将出现调试窗口
#define MY_DEBUG 1
#if MY_DEBUG
#pragma comment( linker, "/subsystem:console /entry:wWinMainCRTStartup" )
#endif
上面有三个按钮,ID号分别为IDC_BUTTON_OPEN,IDC_BUTTON_CLOSE,IDC_BUTTON_SEND
对应Resources.h内容如下:
#define IDC_BUTTON_SEND 1000
#define IDC_BUTTON_OPEN 1001
#define IDC_BUTTON_CLOSE 1002
在UDPClientDlg.h头文件内容如下:
// UDPClientDlg.h : 头文件
//
#pragma once
#include "UDPClientClass.h"
// CUDPClientDlg 对话框
class CUDPClientDlg : public CDialogEx
{
// 构造
private:
UDPClientClass udpCliCls;
public:
CUDPClientDlg(CWnd* pParent = NULL); // 标准构造函数
// 对话框数据
enum { IDD = IDD_UDPCLIENT_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnBnClickedButtonSend();
afx_msg void OnBnClickedButtonClose();
afx_msg void OnBnClickedButtonOpen();
afx_msg LRESULT OnSock(WPARAM, LPARAM); //和UDP_READ绑定的函数
};
在UDPCleintDlg.cpp里面内容如下
// UDPClientDlg.cpp : 实现文件
//
#include "stdafx.h"
#include "UDPClient.h"
#include "UDPClientDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CUDPClientDlg 对话框
CUDPClientDlg::CUDPClientDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CUDPClientDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CUDPClientDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CUDPClientDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON_SEND, &CUDPClientDlg::OnBnClickedButtonSend)
ON_BN_CLICKED(IDC_BUTTON_CLOSE, &CUDPClientDlg::OnBnClickedButtonClose)
ON_BN_CLICKED(IDC_BUTTON_OPEN, &CUDPClientDlg::OnBnClickedButtonOpen)
ON_MESSAGE(UDP_READ, &CUDPClientDlg::OnSock) //消息绑定UDP_READ
END_MESSAGE_MAP()
// CUDPClientDlg 消息处理程序
BOOL CUDPClientDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(true);
GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(false);
GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(false);
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CUDPClientDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CUDPClientDlg::OnQueryDragIcon()
{
return static_cast(m_hIcon);
}
LRESULT CUDPClientDlg::OnSock(WPARAM wParam, LPARAM lParam)
{
UINT8 *data = new UINT8[READ_MAX_BUFFER];
SOCKADDR_IN addrFrom;
UINT32 len = udpCliCls.read(data, &addrFrom, wParam, lParam);
if (len > 0)
{
printf("***UDP client recv\r\n", len);
printf("ip = %08x port = %5d\r\n", addrFrom.sin_addr.S_un.S_addr, ntohs(addrFrom.sin_port));
for (DWORD i = 0; i < len; i++)
{
printf("%d=%02x\r\n", i, data[i]);
}
}
delete data;
data = NULL;
return 0;
}
void CUDPClientDlg::OnBnClickedButtonOpen()
{
// TODO: 在此添加控件通知处理程序代码
if (!udpCliCls.IsOpen())
{
if (udpCliCls.Open("127.0.0.1", 1111, this->m_hWnd))
{
GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(false);
GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(true);
GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(true);
}
}
}
void CUDPClientDlg::OnBnClickedButtonClose()
{
// TODO: 在此添加控件通知处理程序代码
if (udpCliCls.IsOpen())
{
udpCliCls.Close();
GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(true);
GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(false);
GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(false);
}
}
void CUDPClientDlg::OnBnClickedButtonSend()
{
// TODO: 在此添加控件通知处理程序代码
UINT8 a[10] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
if (udpCliCls.IsOpen())
{
if (udpCliCls.write(a, 10))
{
printf("***UDP client send\r\n", a);
for (DWORD i = 0; i < 10; i++)
{
printf("%d=%02x\r\n", i, a[i]);
}
}
}
}
编译运行,并且打开网口调试工具,开UDP Server,监听1111端口,运行示意图如下: