WSAAnsycSelect.docx
- 文档编号:2030893
- 上传时间:2023-05-02
- 格式:DOCX
- 页数:25
- 大小:23.84KB
WSAAnsycSelect.docx
《WSAAnsycSelect.docx》由会员分享,可在线阅读,更多相关《WSAAnsycSelect.docx(25页珍藏版)》请在冰点文库上搜索。
6.使用异步套接字编写网络聊天室(afxsockinit用到的是1.1版本。
如果要求其它版本需要自己调winsock);
Windows套接字在两种模式下执行I/O操作,阻塞和非阻塞。
在阻塞模式下,在I/O操作完成前,执行操作的Winsock函数会一直等待下去,不会立即返回程序(将控制权交还给程序)。
而在非阻塞模式下,Winsock函数无论如何都会立即返回.
Windows Sockets为了支持Windows消息驱动机制,使应用程序开发者能够方便地处理网络通信,它对网络事件采用了基于消息的异步存取策略.
Windows Sockets的异步选择函数WSAAsyncSelect()提供了消息机制的网络事件选择,当使用它登记的网络事件发生时,Windows应用程序相应的窗口函数将收到一个消息,消息中指示了发生的网络事件,以及与事件相关的一些信息.
1)加载套接字库,进行版本协商,包含头文件,链接库文件,这次请示的是2.2
版本!
2)在类CChatDlg中增加一个成员变量m_socket,在析构函数中释放这个变量
3)利用WSASocket()创建套接字(数据报类型的UDP型的)
BOOL CWsaChatDlg:
:
InitSocket()
{
m_socket = WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,0);
if(m_socket==INVALID_SOCKET)
{
MessageBox("创建套字失败!
");
return FALSE;
}
SOCKADDR_IN SockAddr;
SockAddr.sin_family = AF_INET;
SockAddr.sin_port = htons(5500);
SockAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
int dwren = bind(m_socket,(SOCKADDR*)&SockAddr,sizeof(SOCKADDR));
if(SOCKET_ERROR==dwren)
{
MessageBox("绑定失败!
");
return FALSE;
}
if(SOCKET_ERROR==WSAAsyncSelect(m_socket,m_hWnd,WM_SOCK,FD_READ))
{
MessageBox("注册网络读事件失败!
");
return FALSE;
}
return TRUE;
}
4)然后调用WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ)为网络事件定义消息!
此时如果发生FD_READ消息,系统会发送UM_SOCK消息给应用程序!
程序并不会阻塞在这儿了!
以上是在BOOL CChatDlg:
:
OnInitDialog()完成5)然后完成消息响应!
头文件中:
#define UM_SOCK WM_USER+1
afx_msg void OnSock(WPARAM,LPARAM);
源文件中:
ON_MESSAGE(UM_SOCK,OnSock)
实现消息响应函数:
void CChatDlg:
:
OnSock(WPARAM wParam,LPARAM lParam)
{
switch(LOWORD(lParam))//Wparam附加参数指出那个socek发生事件,Lparam
的低字表示什么事件,高字表示错误代码。
{
case FD_READ:
WSABUF wsabuf;
wsabuf.buf=new char[200];
wsabuf.len=200;
DWORD dwRead;
DWORD dwFlag=0;
SOCKADDR_IN addrFrom;
int len=sizeof(SOCKADDR);
CString str;
CString strTemp;
HOSTENT *pHost;
if(SOCKET_ERROR==WSARecvFrom(m_socket,&wsabuf,1,&dwRead,&dwFlag,
(SOCKADDR*)&addrFrom,&len,NULL,NULL))
{
MessageBox("接收数据失败!
");
return;
}
pHost=gethostbyaddr((char*)&addrFrom.sin_addr.S_un.S_addr,4,AF_INET
);
//str.Format("%s说 :
%s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf);
str.Format("%s说 :
%s",pHost->h_name,wsabuf.buf);
str+="\r\n";
GetDlgItemText(IDC_EDIT_RECV,strTemp);
str+=strTemp;
SetDlgItemText(IDC_EDIT_RECV,str);
break;
}
}
6)完成数据发送的功能!
void CChatDlg:
:
OnBtnSend()
{
// TOD Add your control notification handler code here
DWORD dwIP;
CString strSend; WSABUF wsabuf;
DWORD dwSend;
int len;
CString strHostName;
SOCKADDR_IN addrTo;
HOSTENT* pHost;
if(GetDlgItemText(IDC_EDIT_HOSTNAME,strHostName),strHostName=="")
{
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
}
else
{
pHost=gethostbyname(strHostName);
addrTo.sin_addr.S_un.S_addr=*((DWORD*)pHost->h_addr_list[0]);
}
addrTo.sin_family=AF_INET;
addrTo.sin_port=htons(6000);
GetDlgItemText(IDC_EDIT_SEND,strSend);
len=strSen
D.GetLength();
wsabuf.buf=strSen
D.GetBuffer(len);
wsabuf.len=len+1;
SetDlgItemText(IDC_EDIT_SEND,"");
if(SOCKET_ERROR==WSASendTo(m_socket,&wsabuf,1,&dwSend,0,
(SOCKADDR*)&addrTo,sizeof(SOCKADDR),NULL,NULL))
{
MessageBox("发送数据失败!
");
return;
}}
7)完成将主机名转换为IP地址的功能,以前将IP地址转换为主机名的功能
嘿嘿,由于此程式是采用异步套接字选择机制(非阻塞)使发送端和接收端都在同一单线程里实现!
和阻塞情况下需要采用多线程实现在功能是一样的,并且性能同样出色。
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 对于一个
windows网络编程初学者,下面方法是经典入门。
初学者建议不要用MFC提供的类,而用windows API做一个简单服务器和客户端,这样有助于对socket编程机制的理解。
为了简单起见,应用程序是基于MFC的标准对话框。
Winsock用WINDOWS API实现:
(1)服务器端有两个线程:
主线程 — 你需要编写以下函数来实现
#define NETWORK_EVENT USER_MESSAGE+100 file:
//定义网络事件
sockaddr_in clientaddr; file:
//暂时存放客户端IP地址
file:
//自己定义消息映射函数,将上面定义的网络事件映射到处理函数
file:
//OnNetEvent为网络事件处理函数,它在下面定义
ON_MESSAGE(NETWORK_EVENT, OnNetEvent);
在你对话框中的初始化函数中调用下面的初始化网络的子函数
BOOL InitNetwork() file:
//初始化网络
{
file:
//初始化TCP协议
BOOL ret = WSAStartup(MAKEWORD(2,2), &wsaData);
if(ret !
= 0)
{
MessageBox("初始化套接字失败!
");
return FALSE;
}
file:
//创建服务器端套接字
SOCKET serverSocket
= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(serverSocket == INVALID_SOCKET)
{
MessageBox("创建套接字失败!
");
closesocket(m_Socket);
WSACleanup();
return FALSE;
}
file:
//绑定到本地一个端口上
sockaddr_in localaddr;
localaddr.sin_family = AF_INET;
localaddr.sin_port = htons(1688);
localaddr.sin_addr.s_addr = 0;
if(bind(serverSocket ,(const struct sockaddr*)&localaddr,
sizeof(sockaddr)) == SOCKET_ERROR)
{
MessageBox("绑定地址失败!
");
closesocket(m_Socket);
WSACleanup();
return FALSE;
}
file:
//注册网络异步事件,m_hWnd为应用程序的主对话框或主窗口的句柄
WSAAsyncSelect(serverSocket, m_hWnd, NETWORK_EVENT,
FD_ACCEPT | FD_CLOSE | FD_READ | FD_WRITE);
listen(serverSocket, 5); file:
//设置侦听模式
return TRUE;
}
file:
//定义网络事件的响应函数
void OnNetEvent(WPARAM wParam, LPARAM lParam)
{
file:
//调用API函数,得到网络事件类型
int iEvent = WSAGETSELECTEVENT(lParam);
file:
//得到发出此事件的客户端套接字
SOCKET pSock = (SOCKET)wParam;
switch(iEvent)
{
case FD_ACCEPT:
file:
//客户端连接请求
{
OnAccept();
break;
}
case FD_CLOSE:
file:
//客户端断开事件:
{
OnClose(pSock);
break;
}
case FD_READ:
file:
//网络数据包到达事件
{
OnReceive(pSock);
break;
}
case FD_WRITE:
file:
//发送网络数据事件
{
OnSend(pSock);
break;
}
default:
break;
}
}
void OnAccept(SOCET pSock) file:
//响应客户端连接请求函数
{
int len = sizeof(sockaddr);
file:
//调用API函数,接受连接,并返回一个新套接字
file:
//还可以获得客户端的IP地址
SOCKET clientSocket = accept(serverSocket,
(struct sockaddr*)&clientaddr, &len);
file:
//为新的socket注册异步事件,注意没有Accept事件
if(WSAAsyncSelect(clientSocket ,m_hWnd, IP_EVENT,
FD_CLOSE | FD_READ | FD_WRITE) == SOCKET_ERROR)
{
MessageBox("注册异步事件失败!
");
return;
}
file:
//自编函数,将此客户端的相关信息保存下来:
套接字、
// IP地址、登陆时间
saveClientSocket(clientSocket,clientAddr,currentTimer);
}
void OnClose(SOCET pSock)
{
file:
//自编函数,结束与相应的客户端的通信,释放相应资源并做相应处理
endClientSocket(pSock);
}
void OnSend(SOCET pSock)
{
file:
//自编函数,在给客户端发数据时做一些预处理
handleOnSend(pSock);
}
void OnReceive(SOCET pSock)
{
recv(...); file:
//调用API函数,读出网络缓冲区中的数据包
file:
//自编函数,将此数据包和发出此数据的客户端
file:
//clientSocket封装成一条网络消息
buildNetMsg(...);
file:
//自编函数,将此网络消息放入一个消息队列中,由工作线程去处理
saveNetMsg(...);
SetEvent(...); file:
//用事件对象触发工作线程
}
客户端登陆后,随即把自己的计算机名发给服务器,服务器接到后,把它保存下来。
这样服务器就可以显示所有在线客户端的信息了,包括:
客户端计算机名、
IP地址、登陆时间等。
注意:
客户端没有OnAccept()函数,但有OnConnect()函数。
工作线程 —
在你的应用程序初始化时,创建并启动一个工作线程
AfxBeginThread(WorkThread,this,THREAD_PRIORITY_NORMAL);
file:
//this可能为应用程序的主对话框或主窗口的句柄
UINT WorkThread(LPVOID pParam)
{
while
(1)
{
file:
//等待多重事件到来
int ret = WaitForMultipleObject(...);
switch(ret)
{
case OBJECT_0:
{
if(bNewNetMsg) file:
//查看网络消息队列是否有新的网络消息
{
readNetMsg(...); file:
//如有新的网络消息,则读出
handleNetMsg(...); file:
//处理此网络消息
}
break;
}
case OBJECT_0 + 1:
{
file:
//做退出处理
break;
}
default:
break;
}
return 0;
}
客户端为单线程,登陆服务器时,用connect()函数给服务器发连接请求;
客户端没有OnAccept()函数,但有OnConnect()函数。
在OnConnect()函数里做发连接请求时的预处理;
在OnReceive()函数里响应并处理网络数据;
在OnClose()函数里响应服务器的关闭事件;
在OnSend()函数里做发数据时的预处理;
如果你还想实现各客户端之间的在线交流(即所谓的聊天室),你在客户端还可以基于UDP协议
再做一套多点对多点的局域网组播模型模型,以后在和你聊,你先把上面的程序实现。
以上的I/O异步模型基于Windows的消息机制,另外还可以用事件模型、重叠模型或完成端口模型,
建议你参考Windows网络编程指南之类的书。
如果你能对上面的机制很熟练,你肯定已经对Winsock编网络程序的机制有一定理解,接下来你可以进行更精彩的编程, 不仅可以在网上传输普通数据,而且还
以传输语音、视频数据,你还可以自己做一个聊天室,和你的同学在实验室的局域网里可以共同分享你的成果。
2
由于程序是服务器端和客户端是一体的,所以我加了一个BOOL server变量,来判断是服务器端,还是客户端。
点击菜单“文件”->“启动服务器”,则server
=TRUE,表示程序实例是服务器端;反之,server=FALSE,表示程序实例是客户端。
程序对话框在WM_INITDIALOG消息响应中使用WSAStartup初始化
Socket
if(WSAStartup(WINSOCK_VERION,&ws))
{
MessageBox(hwnd,"Winsock初始化失败", szDlgTitle,MB_OK|MB_ICONSTOP);
WSACleanup();
return FALSE;
}//初始化
在WM_CLOSE消息响应中释放Socket:
if(connected_skt !
= INVALID_SOCKET)
{ closesocket(connected_skt);
}
if( skt !
= INVALID_SOCKET )
{
closesocket(skt);
}
if( WSACleanup() !
= 0 )
{
MessageBox(hwnd, "不能释放Winsocket",szDlgTitle,MB_OK );
}
2.1服务器端
监听,按下监听之后调用CreateServer(hwnd)函数。
因为Socket已被初始化,所以这里就创建一个socket:
skt=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
创建成功,接着就绑定创建的socket:
bind(skt,(SOCKADDR*)&addr,sizeof(addr);
绑定成功之后,监听这个socket:
listen(skt,MAX_CONNECTED_NUM );
监听成功之后,就开始选择监听客户端的连接事件:
if( WSAAsyncSelect(skt,hwnd,SOCKETMSG,
FD_ACCEPT) == SOCKET_ERROR )
{
MessageBox(hwnd,"WSAAsyncSelect() 失败", szDlgTitle,MB_OK);
return FALSE;
}
当有客户端连接的时候,
WSAAsyncSelect(skt,hwnd,SOCKETMSG,FD_ACCEPT)函数拦截到这一事件,函数就向程序发送消息SOCKETMSG(这是一个自定义消息),然后程序处理这一消息:
有客户端连接就接受该连接,并创建一个新的用来与客户端通信的socket:
connected_skt,原来最初创建的socket――skt就继续监听有没有客户端连接事件。
if((connected_skt=accept(skt,(struct sockaddr *)&clientaddr,&Len))== INVALID_SOCKET )
{
MessageBox(hwnd,"接受客户端的Socket连接失败", szDlgTitle,MB_OK);
return FALSE;
}
SetDlgItemText(hwnd,IDC_REVTXT,"已经接受客户端连接");
//连接上了,然后监听客户端的FD_READ和关闭
WSAAsyncSelect(connected_skt, hwnd,SOCKETMSG,FD_READ|FD_CLOSE);
先做服务器端应用程序。
用MFC向导做一个基于对话框的应用程序SocketSever,注意
第三步中不要选上Windwos Sockets
选项。
在做好工程后,创建一个SeverSock,将它设置为异步非阻塞模式,并为它注册各种网络异步事件,然后与自定义的网络异步事件联系上,最后还要将它设置为监听模式。
在自定义的网络异步事件的回调函数中,你可以得到各种网络异步事件,根据它们的类型,做不同的处理。
下面将详细介绍如何编写相关代码。
在SocketSeverDlg.h文件的类定义之前增加如下定义:
#define NETWORK_EVENT
WM_USER+166 file:
//定义网络事件SOCKET ServerSock; file:
//
服务器端Socket
在类定义中增加如下定义:
class CSocketSeverDlg :
CDialog
{
public:
SOCKET ClientSock[CLNT_MAX_NUM]; file:
//存储与客户端通信的
Socket的数组
/*各种网络异步事件的处理函数*/
void OnClose(SOCKET CurSock); file:
//对端Socket断开
void OnSend(SOCKET CurSock); file:
//发送网络数据包
void OnReceive(SOCKET CurSock); file:
//网络数据包到达
void OnAccept(SOCKET CurSock); file:
//客户端连接请求
BOOL InitNetwork(); file:
//初始化网络函数
void OnNetEvent(WPARAM wParam, LPARAM lParam); file:
//异步事件回调函数
…
};
在SocketSeverDlg.cpp文件中增加消息映射,其中OnNetEvent是异步事件回调函数名:
ON_MESSAGE(NETWORK_EVENT,OnNetEvent)
定义初始化网络函数,在SocketSeverDlg.cpp文件的OnInitDialog()中调此函数即可。
BOOL CSocketSeverDlg:
:
InitNetwork()
{
WSADATA wsaData;
//初始化TCP协议
BOOL ret = WSAStartup(MAKEWORD(2,2), &wsaData);
if(ret !
= 0)
{
MessageBox("初始化网络协议失败!
");
return FALSE;
}
//创建服务器端套接字
ServerSock = socket(AF_IN
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- WSAAnsycSelect