MFC的UDP编程实现[通俗易懂]

2022-10-04 19:07:23 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

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函数,将这些过程全放进去即可。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/196203.html原文链接:https://javaforall.cn

0 人点赞