• UDP编程


    一: socket编程中的几种地址

        Socket编程会遇到三种地址, 都是定义的结构体(struct):

        Struct in_addr
        {
            Unsigned int s_addr;
        }
        这是一个IPv4地址,在IPv4的报文中,源地址和目的地址用32bit表示。通常定义在netinet/in.h中。

        Struct sockaddr
        {
            Unsigned short sa_family;
            Char sa_data[14]; 
        };
        这就是一个socket地址, 不论什么使用socket的调用都须要这样的格式的地址,所以我们能够看到大部分程序在这里都进行了一个强制转换。
        sa_family表示地址类型,通常为AF_UNIX或者是AF_INET;
        sa_data[14],存储地址信息,比方IP地址,port号等等。
        sockaddr通常定义在bits/socket.h文件里

        因为sockaddr这个地址不太easy分析,因此就用了一种easy分析的地址来表示sockaddr, 这就是sockaddr_in产生的原因,通常sockaddr_in定义于netinet/in.h文件里
        Struct sockaddr_in{
            Unsigned short sa_family;
            Unsigned short sin_port;
            Struct in_addr sin_addr;
            Unsigned char sin_zero[8];
        };
        Sa_family表示地址类型,与sockaddr的sa_family相应
        Sin_port表示端口号
        Sin_addr表示IP地址
        Sin_zero[8]用于零填充,使得sockaddr_in和sockaddr可以转换。在使用sockaddr_in时,必须用bzero()或memset()把这个数组所有清零。

    二: UDP Socket编程

    UDP是不保证数据正确传输的协议,它不过对IP协议进行了简单的封装(添加了port),所以要保证传输数据的正确性须要程序猿自己来处理。UDP相对于TCP的一个优点就是没有建立连接时的开销,这样两个端点之间没有就必要维持连接。
    UDP Socket编程比TCP要简单的多。下面是UDP的编程步骤:

    第1步:向内核申请套接字,使用socket()函数,这是不论什么socket编程的第1步。
    第2步,绑定socket地址(也适用于TCP),对于server端是必须的,对于client是可选的。使用bind()函数。Bind的实质是将socket与本地地址绑定。本地地址主要指IP和port。对于一个主机来说,bind的主要意义在于绑定port。由于本地地址是知道的,通过试验,发现仅仅有绑定的地址仅仅能为 0.0.0.0或者本机的IP地址才有效。否则,指定一个网络地址(注意这个词),会导致收发数据不正常,不论什么一个可能成为client的IP地址都不能被绑 定。所谓的本地地址,即是如此。所以说bind的真正意义在于绑定port。对于UDPclient来说,假设不用bind,那么OS会自己主动分配一个当前已用port 号+1的port供其暂时使用。
    第3步,数据传输。对于UDP来说,由于没有建立连接,所以并不知道发送数据的目的地址,这就须要在send使指定目的地址,所以用sendto(), 当然也能够在第3部之前调用connect()进行一下连接,但对于UDP来说,不过记录对方的地址,这样就能够在发数据时不用指定地址(用 send()), 使用recv/recvfrom收数据,这两个函数的差别只在于,recvfrom会记录对方的socket地址,recvfrom有一个easy出错的地 方,就是关于from地址的长度,在调用时这个长度必须是结构体struct sockaddr的长度,由于在recvfrom的实现中将会用到这个值。Recv/send中的flag假设是0,那么这两个函数等同于read /write. 须要注意的是,系统的socket缓冲区中并没有对收到的数据作字符串式的结尾处理,所以分析报文须要时须要用到recv/recvfrom返回的实际读 取长度。
    第4步,结束,使用close()关闭套接字。

    正如前文所述,socket编程会遇到3种地址,事实上仅仅须要记住:全部的系统调用均使用sockaddr这样的地址。

    int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
    该函数比send()函数多了两个參数,to表示目地机的IP地址和port号信息,而tolen经常被赋值为sizeof (struct sockaddr)。Sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。
    int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
    from是一个struct sockaddr类型的变量,该变量保存源机的IP地址及port号。fromlen常置为sizeof(struct sockaddr)。当recvfrom()返回时,fromlen包括实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或 当出现错误时返

    三:创建用于通讯的Socket类

        #include "winsock.h"

        //UDPclient发送错误回调函数,回调函数能够通过自己定义消息更新页面
        typedef void (CALLBACK* ONUDPERROR)(void*,int nErrorCode);
        //UDPclient接收数据回调函数
        typedef void (CALLBACK* ONUDPRECV)(void*,char* buf,DWORD dwBufLen,sockaddr* saRecvAddress);

        class CUDP_CE
        {
        public:
            CUDP_CE(void);
            ~CUDP_CE(void);
        public:
            //打开UDP通讯
            DWORD Open(void* pOwner,int localPort, LPCTSTR remoteHost ,int remotePort);
            //关闭UDP通讯
            DWORD Close(void);
            //发送数据
            DWORD SendData(const char *buf, int len);
        private:
            //通讯线程函数
            static UINT RecvThread(LPVOID lparam);
        private:
            SOCKET m_UDPSocket;                           //UDP Socket通讯套接字
            struct sockaddr_in m_RemoteAddr;         //存储远程通讯地址
            HANDLE m_ExitThreadEvent;                //线程退出事件
            void * m_pOwner;                          //存储父对象句柄
            char m_recvBuf[4096] ;                    //接收数据缓冲区
        public:
            //UDP错误发生事件
            ONUDPERROR m_OnUdpError;
            //UDP接收数据事件
            ONUDPRECV  m_OnUdpRecv;
        };
        //构造函数
        CUDP_CE::CUDP_CE(void)
        {
            m_UDPSocket = 0;                       //UDP Socket通讯套接字
            m_pOwner = NULL;                      //存储父对象句柄
            ZeroMemory(m_recvBuf,4096) ;        //接收数据缓冲区
            m_OnUdpError = NULL;                //UDP错误发生事件
            m_OnUdpRecv = NULL;                    //UDP接收数据事件
        }

        //析构函数
        CUDP_CE::~CUDP_CE(void)
        {
        }

        /*
        *函数介绍:打开UDP通讯port
        *入口參数:pOwner: 指定父对象指针
        localPort: 指定远程UDPport
        romoteHost:指定远程IP地址
        remotePort:指定远程UDPport
        *出口參数:(无)
        *返回值:代表成功;-1,-2,-3等都代表失败
        */
        DWORD CUDP_CE::Open(void* pOwner,int localPort,LPCTSTR remoteHost,int remotePort)
        {
            WSADATA wsa;
            //传递父对象指针
            m_pOwner = pOwner;
            //载入winsock动态链接库
            if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
            {
                return -1;//代表失败
            }
            //创建UDP套接字
            m_UDPSocket = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
            if (m_UDPSocket == INVALID_SOCKET)
            {
                return -2;
            }

            //设置本地地址
            SOCKADDR_IN localAddr;
            localAddr.sin_family = AF_INET;
            localAddr.sin_port = htons(localPort);
            localAddr.sin_addr.s_addr=INADDR_ANY;

            //绑定地址
            if(bind(m_UDPSocket,(sockaddr*)&localAddr,sizeof(localAddr))!=0)
            {
                return -3;
            }

            //设置非阻塞通讯
            DWORD ul= 1;
            ioctlsocket(m_UDPSocket,FIONBIO,&ul);

            //创建一个线程退出事件
            m_ExitThreadEvent    = CreateEvent(NULL,TRUE,FALSE,NULL);

            //创建通讯线程
            AfxBeginThread(RecvThread,this);

            //设置对方地址
            m_RemoteAddr.sin_family = AF_INET;
            m_RemoteAddr.sin_port = htons(remotePort);
            //此处要将双字节转换成单字节
            char ansiRemoteHost[255];
            ZeroMemory(ansiRemoteHost,255);
            WideCharToMultiByte(CP_ACP,WC_COMPOSITECHECK,remoteHost,wcslen(remoteHost)
                ,ansiRemoteHost,wcslen(remoteHost),NULL,NULL);
            m_RemoteAddr.sin_addr.s_addr=inet_addr(ansiRemoteHost);

            return 1;
        }

        /*
        *函数介绍:关闭UDP通讯port
        *入口參数:(无)
        *出口參数:(无)
        *返回值:代表成功;-1,-2等都代表失败
        */
        DWORD CUDP_CE::Close(void)
        {
            //设置通讯线程退出事件,通知线程退出
            SetEvent(m_ExitThreadEvent);
            Sleep(1000);
            //关闭线程句柄
            CloseHandle(m_ExitThreadEvent);
            //关闭socket
            if (closesocket(m_UDPSocket) == SOCKET_ERROR)
            {
                return -1;
            }

            //释放socket资源
            if (WSACleanup() == SOCKET_ERROR)
            {
                return -2;
            }

            return 1;
        }

        /*
        *函数介绍:发送数据
        *入口參数:buf:缓冲区数据
        len:缓冲数据长度
        *出口參数:(无)
        *返回值:发送成功代表实际发送的字节数,否则返回-1
        */
        DWORD CUDP_CE::SendData(const char *buf, int len)
        {
            int nBytes = 0; //每次发送出去的字节
            int nSendBytes=0;  //已经发送出去的字节
            int nErrorCode =0; //错误码

            //发送数据
            while (nSendBytes < len)
            {
                //发送数据
                nBytes = sendto(m_UDPSocket,buf+nSendBytes,len-nSendBytes,0,(sockaddr*)&m_RemoteAddr,sizeof(m_RemoteAddr));
                if (nBytes==SOCKET_ERROR )
                {
                    nErrorCode = WSAGetLastError();
                    if (m_OnUdpError)
                    {
                        m_OnUdpError(m_pOwner,nErrorCode);
                    }
                    return -1;
                }
                //所有发送完成
                if (nSendBytes == len)
                {
                    break;
                }
                //循环发送
                Sleep(500);
                nSendBytes = nSendBytes + nBytes;
            }
            return nSendBytes;
        }

        /*
        *函数介绍:接收线程函数
        *入口參数:lparam : 指传进线程的參数
        *出口參数:(无)
        *返回值:无意义。
        */
        UINT CUDP_CE::RecvThread(LPVOID lparam)
        {
            //
            CUDP_CE *pSocket = (CUDP_CE*)lparam;
            fd_set fdRead;
            int ret;
            TIMEVAL    aTime;
            aTime.tv_sec = 1;
            aTime.tv_usec = 0;
            SOCKADDR_IN tmpAddr;
            int tmpRecvLen;
            int recvLen;
            int iErrorCode;

            while (TRUE)
            {
                //收到退出事件,结束线程
                if (WaitForSingleObject(pSocket->m_ExitThreadEvent,0) == WAIT_OBJECT_0)
                {
                    break;
                }
                //将set初始化空集合
                FD_ZERO(&fdRead);
                //将pSocket->m_UDPSocket套接字加入到集合中
                FD_SET(pSocket->m_UDPSocket,&fdRead);
                //调用select函数,推断套接字I/O状态
                ret = select(0,&fdRead,NULL,NULL,&aTime);   
                if (ret == SOCKET_ERROR)
                {
                    iErrorCode = WSAGetLastError();
                    if (pSocket->m_OnUdpError)
                    {
                        pSocket->m_OnUdpError(pSocket->m_pOwner,iErrorCode);
                    }
                    break;
                }

                //有事件发生
                if (ret > 0)
                {
                    //收到数据
                    if (FD_ISSET(pSocket->m_UDPSocket,&fdRead))
                    {       
                        //
                        tmpAddr.sin_family=AF_INET;            
                        tmpAddr.sin_port = htons(pSocket->m_RemoteAddr.sin_port);
                        tmpAddr.sin_addr.s_addr =INADDR_ANY;
                        tmpRecvLen = sizeof(tmpAddr);

                        //置空接收缓冲区
                        ZeroMemory(pSocket->m_recvBuf,4096);
                        //接收数据
                        recvLen = recvfrom(pSocket->m_UDPSocket,pSocket->m_recvBuf, 4096,0,(SOCKADDR*)&tmpAddr,&tmpRecvLen);
                        if (recvLen == SOCKET_ERROR)
                        {
                            iErrorCode = WSAGetLastError();
                            if (pSocket->m_OnUdpError)
                            {
                                pSocket->m_OnUdpError(pSocket->m_pOwner,iErrorCode);
                            }
                        }
                        else if (recvLen == 0)
                        {
                            iErrorCode = WSAGetLastError();
                            if (pSocket->m_OnUdpError)
                            {
                                pSocket->m_OnUdpError(pSocket->m_pOwner,iErrorCode);   
                            }
                        }
                        else
                        {
                            //调用回调函数将数据发送出去
                            if (pSocket->m_OnUdpRecv)
                            {
                                pSocket->m_OnUdpRecv(pSocket->m_pOwner,pSocket->m_recvBuf,recvLen,(SOCKADDR*)&tmpAddr);
                            }
                        }   

                    }
                }
            }
            TRACE(L"The read thread had exited./n");
            return 0;
        }

    四:操作串口类

    1:自己定义消息函数,当消息到达时,触发回调函数,回调函数通过发消息通知界面更新
        //UDP 接收数据消息
        #define WM_RECV_UDP_DATA WM_USER + 101
        afx_msg LONG OnRecvUdpData(WPARAM wParam,LPARAM lParam);
        ON_MESSAGE(WM_RECV_UDP_DATA,OnRecvUdpData)

        private:
            //UDP接收数据事件,回调函数
            static void CALLBACK OnUdpCERecv(void * pOwner,char* buf,DWORD dwBufLen,sockaddr * addr);
            //UDP通讯错误事件,回调函数
            static void CALLBACK OnUdpCEError(void * pOwner,int nErrorCode);
        private:
            //定义UDP通讯类变量
            CUDP_CE m_CEUdp;

    2:打开
        m_CEUdp.m_OnUdpRecv = OnUdpCERecv;
        m_CEUdp.m_OnUdpError = OnUdpCEError;
        DWORD nResult = m_CEUdp.Open(this,m_LocalPort,m_RemoteHost.GetBuffer(m_RemoteHost.GetLength()),m_RemotePort);

    3:关闭
            m_CEUdp.Close();   

    4:发送

        char * buf  =NULL;  //定义发送缓冲区
        DWORD dwBufLen = 0;   //定义发送缓冲区长度
        CString strSend = L"";

        //得到发送输入框
        CEdit *pEdtSendMsg = (CEdit*)GetDlgItem(IDC_EDT_SEND);
        ASSERT(pEdtSendMsg != NULL);

        //得到待发送的字符串
        pEdtSendMsg->GetWindowTextW(strSend);

        //将待发送的字符串转换成单字节,进行发送
        buf = new char[strSend.GetLength()*2+1];
        ZeroMemory(buf,strSend.GetLength()*2+1);
        //转换成单字节进行发送   
        WideCharToMultiByte(CP_ACP,WC_COMPOSITECHECK,strSend.GetBuffer(strSend.GetLength())
            ,strSend.GetLength(),buf,strSend.GetLength()*2,NULL,NULL);

        dwBufLen = strlen(buf) + 1;

        //发送数据
        m_CEUdp.SendData(buf,dwBufLen);   

        //释放内存
        delete[] buf;
        buf = NULL;

    5:接收,消息到达时,触发回调函数,回调函数发送消息触发自己定义消息函数,更新界面
        //UDP数据接收回调函数
        void CALLBACK CudpDlg::OnUdpCERecv(void * pOwner,char* buf,DWORD dwBufLen,sockaddr * addr)
        {
            BYTE *pRecvBuf = NULL; //接收缓冲区
            //得到父对象指针
            CudpDlg* pThis = (CudpDlg*)pOwner;
            //将接收的缓冲区复制到pRecvBuf种
            pRecvBuf = new BYTE[dwBufLen];
            CopyMemory(pRecvBuf,buf,dwBufLen);

            //发送异步消息,表示收到串口数据,消息处理完,应释放内存
            pThis->PostMessage(WM_RECV_UDP_DATA,WPARAM(pRecvBuf),dwBufLen);
        }

        //UDP报错回调函数
        void CALLBACK CudpDlg::OnUdpCEError(void * pOwner,int nErrorCode)
        {

        }

        // UDP接收数据处理函数
        LONG CudpDlg::OnRecvUdpData(WPARAM wParam,LPARAM lParam)
        {
            CString strOldRecv = L"";
            CString strRecv = L"";
            //串口接收到的BUF
            CHAR *pBuf = (CHAR*)wParam;
            //串口接收到的BUF长度
            DWORD dwBufLen = lParam;

            //接收框
            CEdit *pEdtRecvMsg = (CEdit*)GetDlgItem(IDC_EDT_RECV);
            ASSERT(pEdtRecvMsg != NULL);

            //得到接收框中的历史文本
            pEdtRecvMsg->GetWindowTextW(strOldRecv);

            //
            strRecv = CString(pBuf);
            //将新接收到的文本加入到接收框中
            strOldRecv = strOldRecv + strRecv +L"/r/n";
            pEdtRecvMsg->SetWindowTextW(strOldRecv);

            //释放内存
            delete[] pBuf;
            pBuf = NULL;
            return 0;
        }

  • 相关阅读:
    OEA框架学习:缓存
    2012年 博文整理
    技术支持经验总结
    OEA框架学习:元数据设计
    安装后新建Android出现“AndroidManifest.xml 系统找不到指定的文件”解决方案
    Android控件学习笔记之 GridView(实现九宫格)
    获取url地址中主机的域名
    C# 语音读取字符串
    JSON省市联动
    MOTO Droid手机自定义本地铃声设置方法
  • 原文地址:https://www.cnblogs.com/zfyouxi/p/3875277.html
Copyright © 2020-2023  润新知