一: 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;
    }

UDP编程的更多相关文章

  1. Linux学习四:UDP编程(上)

    关于UDP和TCP对比优缺,这里就不说了. 使用UDP代码所掉用的函数和用于TCP的函数非常类似,这主要因为套接口库在底层的TCP和UDP的函数上加了一层抽象,通过这层抽象使得编程更容易,但失去了一些 ...

  2. [C# 网络编程系列]专题七:UDP编程补充——UDP广播程序的实现

    转自:http://www.cnblogs.com/zhili/archive/2012/09/03/2666974.html 上次因为时间的关系,所以把上一个专题遗留下的一个问题在本专题中和大家分享 ...

  3. [C# 网络编程系列]专题六:UDP编程

    转自:http://www.cnblogs.com/zhili/archive/2012/09/01/2659167.html 引用: 前一个专题简单介绍了TCP编程的一些知识,UDP与TCP地位相当 ...

  4. Socket编程实践(12) --UDP编程基础

    UDP特点 无连接,面向数据报(基于消息,不会粘包)的传输数据服务; 不可靠(可能会丢包, 乱序, 反复), 但因此普通情况下UDP更加高效; UDP客户/服务器模型 UDP-API使用 #inclu ...

  5. 【Socket编程】通过Socket实现UDP编程

    通过Socket实现UDP编程 UDP通信: 1.UDP协议(用户数据报协议)是无连接.不可靠.无序的. 2.UDP协议以数据报作为数据传输的载体. 3.使用UDP进行数据传输时,首先需要将要传输的数 ...

  6. 网络编程之UDP编程

    网络编程之UDP编程 UDP协议是一种不可靠的网络协议,它在通信的2端各建立一个Socket,但是这个Socket之间并没有虚拟链路,这2个Socket只是发送和接受数据的对象,Java提供了Data ...

  7. Twisted UDP编程技术

    实战演练1:普通UDP UDP是一种无连接对等通信协议,没有服务器和客户端概念,通信的任何一方均可通过通信原语直接和其他方通信 1.相对于TCP,UDP编程只需定义DatagramProtocol子类 ...

  8. 五十六、linux 编程——UDP 编程模型

    56.1 UDP 编程模型 56.1.1 编程模型 UDP 协议称为用户数据报文协议,可靠性比 TCP 低,但执行效率高 56.1.2 API (1)发送数据 函数参数: sockfs:套接字文件描述 ...

  9. 【网络编程1】网络编程基础-TCP、UDP编程

    网络基础知识 网络模型知识 OSI七层模型:(Open Systems Interconnection Reference Model)开放式通信系统互联参考模型,是国际标准化组织(ISO)提出的一个 ...

  10. 转:【专题七】UDP编程补充——UDP广播程序的实现

    上次因为时间的关系,所以把上一个专题遗留下的一个问题在本专题中和大家分享下,本专题主要介绍下如何实现UDP广播的程序,下面就直接介绍实现过程和代码以及运行的结果. 一.程序实现 UDP广播程序的实现代 ...

随机推荐

  1. 将页面中指定表格的数据导入到Excel中

    function AutoExcel(){   var oXL = new ActiveXObject("Excel.Application"); //创建应该对象   var o ...

  2. Google java编程技术规范

    不遵循规范的程序猿,不是好的coder. 学习java有一段时间了,一直想找java编程技术规范来学习一下,幸而网络资源丰富,各路玩家乐于分享,省去了好多麻烦,姑且算站在网友的肩上,砥砺前行. /** ...

  3. hibernate的formula如何使用

    之前用过hibernate的formula记得很好用,但是这次用到想不起来怎么用了,结果去网上查结果发现大多都是无用信息. 最终搞定了,还是在这里记录一下,省的忘记. 我用formula的目的在于字典 ...

  4. BZOJ 1715: [Usaco2006 Dec]Wormholes 虫洞

    Description John在他的农场中闲逛时发现了许多虫洞.虫洞可以看作一条十分奇特的有向边,并可以使你返回到过去的一个时刻(相对你进入虫洞之前).John的每个农场有M条小路(无向边)连接着N ...

  5. how to develop mobile web

    http://blog.templatemonster.com/2010/05/11/how-make-mobile-website-6-easy-tips/ http://mobile.smashi ...

  6. 【BZOJ 1191】 [Apio2010]特别行动队 (斜率优化)

    dsy1911: [Apio2010]特别行动队 [题目描述] 有n个数,分成连续的若干段,每段的分数为a*x^2+b*x+c(a,b,c是给出的常数),其中x为该段的各个数的和.求如何分才能使得各个 ...

  7. 最简单的CRC32源码-逐BYTE法

    从按BIT计算转到按BYTE计算,要利用异或的一个性质,具体见前面的文章<再探CRC >. 其实方法跟逐BIT法是一样的,我们只是利用异或的性质,把数据分成一BYTE一BYTE来计算,一B ...

  8. HTML input标签的checked属性与Razor解析

    在HTML中,input标签可以通过type属性设置为checkbox.同时,也就包含了一个checked属性.对于这个checked属性,有一个特别的地方就是,它可以不需要属性值就可以表示是否选择了 ...

  9. minicom-2.4安装配置

    minicom-2.4安装说明 1.#tar –zxvf minicom-2.4.tar.gz 解压开有连个文件,minicom-2[1].4.tar.gz  和minirc.dfl rpm包方式# ...

  10. MySQL优化器join顺序

    前一篇介绍了cost的计算方法,下面测试一下两表关联的查询: 测试用例 CREATE TABLE `xpchild` ( `id` int(11) NOT NULL, `name` varchar(1 ...