说明
一般来说,同步套接字编程时会长时间阻塞所在线程的函数有
accept(TCP编程中服务器端等待客户端连接)
recv(TCP编程中等待对方的消息)
recvfrom(UDP编程中等待对方的消息)
另外send / sendto 函数一般情况下会很快返回,当然如果发送的信息足够多(很长很长,有永远那么长)的话也可以认为会阻塞线程一段时间, connect函数一般情况下不论能否连接都会很快返回。
所以会长时间造成所在线程阻塞的函数一般是accept, recv, recvfrom。 accept对应的网络注册事件是FD_ACCEPT,recv / recvfrom对应的网络注册事件是FD_READ。
对应FD_READ网络事件的函数还有WSARecvFrom,该函数既可以是重叠执行也可以是非重叠执行,取决于函数WSASocket函数中最后一个参数是否设置了重叠操作标识。
所以有这样一句话:重叠操作是异步操作的一种形式!
有这样一个结论:网络读事件异步操作的同时,其消息函数对应的读取(接收)函数(如WSARecv / WSARecvFrom)同时也可以重叠操作。
实例
该实例中,“发送”按钮是缺省按钮,发送消息的目的地址为本机地址(相当于QQ里的给自己发送消息),发送的同时也显示在自己的聊天框上。 有发送非空检查,当然这都是表面,重要的是基于消息的异步套接字编程。
下面是主要代码:
/75AsyncUdpDlg.h 类声明
#include "winsock2.h" //socket所在头文件
#pragma comment(lib, "ws2_32.lib") //连接库
#define LOW_VERSION 2 //请求2.0版本socket高字节
#define HIGH_VERSION 0 //请求2.0版本socket低字节
#define MAX_LENGTH 30 //聊天框接收区域最多显示的信息条数
#define MAX_INFO_SIZE 400 //发送消息的最大长度
#define WM_EVENT (WM_USER + 100) //自定义消息,用来接收socket数据
public:
~CMy75AsyncUdpDlg(); //添加了一个析构函数,用来关闭socket和释放资源
BOOL InitSocket(void); //初始化socekt
char* m_ip; //点分十进制IP地址
UINT m_unPort; //端口号
protected:
afx_msg LRESULT OnSock(WPARAM wParam, LPARAM lParam); //自定义消息对应的消息处理函数
private:
SOCKET sock; //本socket
SOCKADDR_IN m_addrCandidate; //对方地址结构体
UINT m_unCount; //信息条数计数
/75AsyncUdpDlg.cpp 类定义
//构造函数
CMy75AsyncUdpDlg::CMy75AsyncUdpDlg(CWnd* pParent /*=NULL*/)
: CDialog(CMy75AsyncUdpDlg::IDD, pParent)
{
//codes
m_ip = "127.0.0.1";
m_unPort = 4000;
sock = 0;
m_addrCandidate.sin_addr.s_addr = inet_addr(m_ip);
m_addrCandidate.sin_family = AF_INET;
m_addrCandidate.sin_port = htons(m_unPort);
m_unCount = 0;
}
//析构函数
CMy75AsyncUdpDlg::~CMy75AsyncUdpDlg()
{
if (sock) //如果sock存在
{
closesocket(sock); //关闭socket
WSACleanup(); //释放库的使用
}
}
//消息映射
BEGIN_MESSAGE_MAP(CMy75AsyncUdpDlg, CDialog)
//{{AFX_MSG_MAP(CMy75AsyncUdpDlg)
//codes
//}}AFX_MSG_MAP
ON_MESSAGE(WM_EVENT, OnSock)
END_MESSAGE_MAP()
//对话框初始化函数
BOOL CMy75AsyncUdpDlg::OnInitDialog()
{
//codes
InitSocket();
//codes
}
//初始化套接字InitSocket函数
BOOL CMy75AsyncUdpDlg::InitSocket(void)
{
WORD wVersionRequsted;
WSADATA wsaData;
wVersionRequsted = MAKEWORD(LOW_VERSION, HIGH_VERSION); //版本
CString strError;
//初始化
if (0 != WSAStartup(wVersionRequsted, &wsaData))
{
strError.Format("%d", WSAGetLastError());
CString str1 = "startup error, code is ";
str1 += strError;
MessageBox(str1);
return FALSE;
}
//版本协商是否正确
if (LOBYTE(wsaData.wVersion) != LOW_VERSION || HIBYTE(wsaData.wVersion) != HIGH_VERSION)
{
MessageBox("version error!");
return FALSE;
}
//建立套接字,当WSASocket中没有设置异步标识时,WSAScoket函数基本同socket函数
//sock = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, 0);
sock = socket(AF_INET, SOCK_DGRAM, 0); //这里socket同WSASocket
if (INVALID_SOCKET == sock)
{
CString str2 = "WSASocket error, code is ";
strError.Format("%d", WSAGetLastError());
str2 += strError;
MessageBox(str2);
return FALSE;
}
//建立套接字地址结构体
SOCKADDR_IN addrSock;
addrSock.sin_addr.S_un.S_addr = inet_addr(m_ip);
addrSock.sin_family = AF_INET;
addrSock.sin_port = htons(m_unPort);
int len = sizeof(SOCKADDR);
//绑定套接字到地址端口
if(SOCKET_ERROR == bind(sock, (SOCKADDR*)&addrSock, len))
{
CString str3 = "bind error, code is ";
strError.Format("%d", WSAGetLastError());
str3 += strError;
MessageBox(str3);
closesocket(sock);
WSACleanup();
return FALSE;
}
//注册异步读事件
if(SOCKET_ERROR == WSAAsyncSelect(sock, m_hWnd, WM_EVENT, FD_READ))
{
CString str4 = "WSAAsyncSelect error, code is ";
strError.Format("%d", WSAGetLastError());
str4 += strError;
MessageBox(str4);
closesocket(sock);
WSACleanup();
return FALSE;
}
return TRUE;
}
//网络读事件对应的消息函数
LRESULT CMy75AsyncUdpDlg::OnSock(WPARAM wParam, LPARAM lParam)
{
//wParam标识哪个套接字
//lParam低字标识何种网络事件,高字标识错误代码
switch (LOWORD(lParam))
{
case FD_READ:
{
CString strUser = "UserName"; //本聊天工具用户名
char recvBuffer[MAX_INFO_SIZE]; //接收缓冲区
memset(recvBuffer, '\0', sizeof(recvBuffer)); //清空
int len = sizeof(SOCKADDR); //结构体大小
WSABUF wsaBuf; //WSABUF结构体,结构体成员:char* buf, u_long len,缓冲区以及 //大小
wsaBuf.buf = recvBuffer; //结构体成员赋值
wsaBuf.len = sizeof(recvBuffer); //结构体成员赋值
DWORD numberOfBytes = 0; //存储本次接收的字节数
DWORD flags = 0; //标识位,为0即可
WSARecvFrom(sock, &wsaBuf, sizeof(wsaBuf) / sizeof(WSABUF), &numberOfBytes, &flags,
(SOCKADDR*)&m_addrCandidate, &len, NULL, NULL);
wsaBuf.buf[strlen(wsaBuf.buf)] = '\0'; //保证是null-terminated string
if (++m_unCount >= MAX_LENGTH) //限制聊天框接收区域信息条数
{
SetDlgItemText(IDC_EDIT_RECV, wsaBuf.buf);//如果超过则用最后一条接收到的来填充
m_unCount = 0;
}
else
{
((CEdit*)GetDlgItem(IDC_EDIT_RECV))->SetSel(-1); //滚屏
((CEdit*)GetDlgItem(IDC_EDIT_RECV))->ReplaceSel(wsaBuf.buf); //添加
}
break;
}
default:
break;
}
return 0;
}
//"发送"按钮函数
void CMy75AsyncUdpDlg::OnBtnSend()
{
// TODO: Add your control notification handler code here
CString strTime;
CTime time = CTime::GetCurrentTime();
strTime = time.Format("%H:%M:%S"); //取得本地时间
CString strTemp;
GetDlgItemText(IDC_EDIT_SEND, strTemp); //取得发送区域内容
if(strTemp.GetLength() < 1) //非空检查
{
MessageBox("不能发送空!");
GetDlgItem(IDC_EDIT_SEND)->SetFocus();
return;
}
else if (strTemp.GetLength() >= (MAX_INFO_SIZE - 22)) //信息长度限制,22为除信息外的附加信息长度(用户名时间等)
{
MessageBox("发送信息长度超过400字符");
GetDlgItem(IDC_EDIT_SEND)->SetFocus();
return;
}
CString strUser = "UserName";
CString strSend = strUser + " " + strTime + "\r\n" + strTemp + "\r\n";
WSABUF wsaBuf; //WSABUF结构体
wsaBuf.buf = strSend.GetBuffer(strSend.GetLength()); //CString--->char*
wsaBuf.len = strSend.GetLength() + 1; //GetLength()不包括中不包括'\0',因此人为发送一个'\0'
strSend.ReleaseBuffer(strSend.GetLength());
DWORD numberOfBytes = 0; //本次发送出去的字节数
DWORD flags = 0; //标识位,为0即可
if(0 != WSASendTo(sock, &wsaBuf, sizeof(wsaBuf) / sizeof(WSABUF), &numberOfBytes, flags, (SOCKADDR*)&m_addrCandidate, sizeof(SOCKADDR), NULL, NULL))
{
CString strError;
strError.Format("%d", WSAGetLastError()); //获得错误代码
CString str = "sendto error, code is ";
str += strError;
MessageBox(str);
GetDlgItem(IDC_EDIT_SEND)->SetFocus();
return;
}
SetDlgItemText(IDC_EDIT_SEND, "");
GetDlgItem(IDC_EDIT_SEND)->SetFocus();
if(++m_unCount >= MAX_LENGTH) //限制信息显示条数
{
SetDlgItemText(IDC_EDIT_RECV, strSend);
m_unCount = 0;
return;
}
((CEdit*)GetDlgItem(IDC_EDIT_RECV))->SetSel(-1);
((CEdit*)GetDlgItem(IDC_EDIT_RECV))->ReplaceSel(strSend);
}
这就是基于消息的UDP套接字编程实例。