MFC的UDP编程实现
1、编程原理
UDP是面向非连接的通信协议,比TCP协议简单很多。无论是服务器端还是客户端,其通信过程概括为:
创建套接字(socket)-->绑定(bind)-->发送send(或接收recv)-->关闭套接字(closesocket)
2、特殊地址:
在实际通信网络中,我们几乎不会用到“0.0.0.0"和“127.0.0.1”这样的IP地址。但是在一台计算机上,特别用于某些测试用途时,这类地址就有用武之地了。
(1)环回地址:127.0.0.1,该地址可用于本地计算机测试接收功能,即本地计算机绑定一IP地址(如192.168.1.2)时,可向环回地址发送信息M,则本地计算机可收到“反馈”回来的同样信息M(具有服务端性质)
(2)全零网络地址:0.0.0.0,可作为源地址,表示整个网络,即“任意地址”
3、重要函数
(1)创建套接字函数socket()
函数原型:SOCKET PASCAL FAR socket( int af, int type, int protocol);
返回值说明:成功返回套接字,失败返回INVALID_SOCKET;
创建流套接字(TCP)时,如:m_socket = socket(AF_INET,SOCK_STREAM,0)
创建数据报套接字(UDP),如:m_socket = socket(AF_INET,SOCK_DGRAM,0)
在成功创建套接字之后,需要填充sockaddr_in结构体作为网络函数参数:
struct sockaddr_in
{
shortint sin_family;//地址协议
unsigned short int sin_port;//端口号
struct in_addr sin_addr;//IP地址
unsigned char sin_zero[8];// sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
}
如在VS2010中,有:
//SOCKADDR_INaddrSock;//SOCKADDR_IN为sockadd_in的宏定义,此变量在头文件中定义
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS2))->GetAddress(sourceIP);//获取控件上IP地址
addrSock.sin_family=AF_INET;
addrSock.sin_port=htons(6000);
addrSock.sin_addr.S_un.S_addr=htonl(sourceIP);//sourceIP表示运行该程序的主机IP
(2)绑定函数bind()
函数原型:int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR *name,int namelen)
返回值说明:绑定成功,返回0值,否则返回-1(SOCKET_ERROR)
如:
int retval;
retval = bind(m_socket,(SOCKADDR*)&addrSock, sizeof(SOCKADDR));//SOCKADDR是sockaddr的宏定义
*(3)创建线程函数CreateThread()
创建线程后,立即开启,调度线程函数:
RECVPARAM *pRecvParam=newRECVPARAM;
pRecvParam->sock=m_socket;
pRecvParam->hwnd=m_hWnd;
HANDLE hThread=CreateThread(NULL,0,RecvProc,(LPVOID)pRecvParam,0,NULL);//RecvProc为线程函数
CloseHandle(hThread);
//线程函数
DWORD WINAPICMyChatDlg::RecvProc(LPVOIDlpParameter)
{
//lpParmeter为创建线程是所提交的函数参数
SOCKETsock=((RECVPARAM*)lpParameter)->sock;
HWNDhwnd=((RECVPARAM*)lpParameter)->hwnd;
deletelpParameter; //释放对象
SOCKADDR_IN addrFrom;
int len=sizeof(SOCKADDR);
char recvBuf[200];
char tempBuf[300];
int retval;
while(TRUE) //创建线程的必要性
{
retval=recvfrom(sock,recvBuf,200,0,(SOCKADDR*)&addrFrom,&len);//获取套接字接收内容
if(SOCKET_ERROR==retval)
break;
sprintf(tempBuf,"%s说: %s",inet_ntoa(addrFrom.sin_addr),recvBuf);
::PostMessage(hwnd,WM_RECVDATA,0,(LPARAM)tempBuf);//提交消息,触发消息响应
}
return 0;
}
分析:
struct RECVPARAM
{
SOCKET sock;
HWND hwnd;
};//在头文件中定义该结构体
线程的创建是通过函数CreateThread来实现的,调用成功返回句柄和一个id。
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程的安全属性,NULL为缺省值
DWORD dwStackSize, //线程堆栈的大小,0为系统缺省值
LPTHREAD_START_ROUTINE lpStartAddress, //线程函数的起始地址可为线程函数名
LPVOID lpParameter, //传递给线程函数的参数,重要!
DWORD dwCreationFlags, //线程创建后是否立即启动,0表示立即启动
LPDWORD lpThreadId //线程的ID号
);
(4)获取接收信息的recvfrom函数(经socket接收数据):
函数原型:ssize_trecvfrom(int sockfd,void *buf,int len,unsigned int flags, struct sockaddr*from,socket_t *fromlen);
参数含义详见:http://baike.baidu.com/view/1744189.htm
功能描述:该函数接收来自套接字的数据,数据存到缓冲区,并从sockaddr中可读取到相关网络参数(如接收数据的源地址等)
(5)发送函数函数sendto()
函数原型:intPASCAL FAR sendto (
IN SOCKET s,
IN const char FAR *buf,
IN int len,
IN int flags,
IN const structsockaddr FAR *to,
IN int tolen);
如:sendto(m_socket,strSend,strSend.GetLength()+1,0,(SOCKADDR*)&addrTo,sizeof(SOCKADDR));
4、关键点:
(1)UDP实现过程简单,关键是了解每个过程所需要函数及其使用方法
(2)为UDP通信创建线程,是设计更加合理
(3)套接字创建之后很重要的一步是填充sockaddr_in,绑定的成功与否与该结构体具有紧密的关系。
(4)如果是基于人机交互的实现模式,UDP通信之前的工作可以分成几个模块,而这些模块,注意要共用一个套接字(如在类中定义一个SOCKET变量)。如果有默认式的UDP通信模式,可以将UDP通信之前的工作放在一起,即定义一个initial函数,将这些过程全放进去即可。