步骤:
一、加载套接字库
二、创建套接字
三、套接字绑定
四、创建接收线程
五、启动发送
一、加载套接字库
#include <Afxsock.h>//需要添加此头文件
if(!AfxSocketInit())
{
AfxMessageBox("加载套接字库失败!");
return;
}
二、创建套接字
SOCKET m_socket;
m_socket = socket(AF_INET,SOCK_DGRAM,0);
if(INVALID_SOCKET==m_socket)
{
AfxMessageBox("套接字创建失败!");
return;
}
套接字需要定义成成员函数,因为在其他地方要使用。
三、套接字绑定
int retval;
SOCKADDR_IN addrSock;
addrSock.sin_family=AF_INET;
addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
if( bFlag )
{
//非0,绑定用户指定的端口
int port=wPort;
addrSock.sin_port=htons(port);
retval=bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR));
if( 0x00 != retval )
{
AfxMessageBox("套接字绑定失败!");
return ;
}
}
else
{
//0,绑定系统自动分配的端口
int port=wPort;//一般给个初值,不建议从0开始。可以从10000开始。
while(1)
{
addrSock.sin_port=htons(port);
retval=bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR));
if(0==retval)
{
//绑定成功
break;
}
//绑定失败,端口加一继续试
port++;
}
}
INADDR_ANY:当电脑有多个网卡时,就会有多个IP地址,绑定时使用这个值,可以接收来自所有网卡的数据。
四、创建接收线程
//由于接收函数是阻塞模式,为了使超时重传能够起作用,为接收函数新建个线程
typedef struct re
{
SOCKET sock; //已创建的套接字
CCom *pCom; //实例句柄或者对话框句柄pDlg
}RECVPARAM_COM;
RECVPARAM_COM *pRecvParam=new RECVPARAM_COM;
pRecvParam->pCom = this;
pRecvParam->sock = m_socket;
HANDLE hThread1 = CreateThread(NULL,0,RecvProc,(LPVOID)pRecvParam,0,NULL);
CloseHandle(hThread1);
//线程函数
DWORD WINAPI CCom::RecvProc(LPVOID lpParameter)
{
//接收参数
CCom *pCom = ((RECVPARAM_COM *)lpParameter)->pCom;
SOCKET sock = ((RECVPARAM_COM *)lpParameter)->sock;
int retval;
SOCKADDR_IN addrFrom;
int len=sizeof(SOCKADDR);
char UDP_RecvBuf[50]={0}; //接收缓冲区
FRAME_HDR *frame_hdr = (FRAME_HDR *)UDP_RecvBuf;
FRAME_BODY_14 *frame_body_14 = (FRAME_BODY_14 *)&UDP_RecvBuf[3];
while(1)
{
retval=recvfrom(sock,UDP_RecvBuf,sizeof(UDP_RecvBuf),0,(SOCKADDR*)&addrFrom,&len);//如果没有数据到达,此函数会阻塞。
//能走到这一步,说明要么发生了错误,要么已经接收到了数据
if(SOCKET_ERROR==retval)
{ //接收函数调用失败,UDP时一般是WSAECONNRESET错误,不重要,可以忽略
AfxMessageBox("接收函数调用失败!");
}
//接收数据过滤
if( (pCom->m_addrTo.sin_addr.S_un.S_addr == addrFrom.sin_addr.S_un.S_addr) &&
(pCom->m_addrTo.sin_port == addrFrom.sin_port) )
{ //是应答帧,解析数据,
//电脑作为通讯主动发起者时,建议对各个从机依次操作,即处理完一个再处理另一个,并且对从机的IP地址和端口进行过滤,才能保证收到的是有效的回应。
//电脑作为服务器端,一般都是通讯的被动接受者,可以接收来自不同IP地址和端口的数据,则不能过滤。
…………
}
}
//返回
return 0;
}
参数(套接字,接收缓冲区,接收数据长度,0,源地址,源地址长度指针)
recvfrom函数要放在死循环里,不停地等待数据的到来。当收到应答数据后,要通过标记通知主线程。
五、启动发送
//准备发送内容
FRAME_HDR *frame_hdr = (FRAME_HDR *)UDP_SendBuf;
FRAME_BODY_13 *frame_body_13 = (FRAME_BODY_13 *)&UDP_SendBuf[3];
frame_hdr->pass[0] = 0x55;
frame_hdr->pass[1] = 0xAA;
frame_hdr->type = FRAME_TYPE_SEND;
frame_body_13->type = m_bSubframeType;
//发送地址
m_addrTo.sin_family=AF_INET;
m_addrTo.sin_port=htons(1);\\对方端口号
m_addrTo.sin_addr.S_un.S_addr=htonl(m_dwIP);\\对方IP地址
//发送
sendto(m_socket,UDP_SendBuf,4,0,(SOCKADDR*)&m_addrTo,sizeof(SOCKADDR));
参数(套接字,发送缓冲区,发送数据长度,0,目标地址,目标地址长度)
一般不关注sendto的返回值,因为UDP是面向无连接的,所以必须由上层进行差错控制,是否发送成功只能通过对方的回应来判断。
连续重发时,中间需要加上延时,否则只有最后一条指令起作用。
//发送,防止丢包,多发几次
sendto(sock,UDP_SendBuf,sizeof(FRAME_HDR)+sizeof(FRAME_BODY_0F),0,(SOCKADDR*)&addrTo,sizeof(SOCKADDR));
Sleep(100);
sendto(sock,UDP_SendBuf,sizeof(FRAME_HDR)+sizeof(FRAME_BODY_0F),0,(SOCKADDR*)&addrTo,sizeof(SOCKADDR));
Sleep(100);
sendto(sock,UDP_SendBuf,sizeof(FRAME_HDR)+sizeof(FRAME_BODY_0F),0,(SOCKADDR*)&addrTo,sizeof(SOCKADDR));