zoukankan      html  css  js  c++  java
  • 通信编程:UDP 套接字和广播通信

    UDP 套接字

    工作流程

    UDP 用户数据报协议提供的是无连接的、不可靠的数据传输服务,但是不需要像 TCP 那样建立连接,所以传输效率高。

    数据收发

    UDP 使用的发送数据的函数是 sendto(),比较不同的是发送数据时需要填写接收方的地址。

    int
    WSAAPI
    sendto(
        _In_ SOCKET s,
        _In_reads_bytes_(len) const char FAR * buf,
        _In_ int len,
        _In_ int flags,
        _In_reads_bytes_(tolen) const struct sockaddr FAR * to,
        _In_ int tolen
        );
    

    接收数据的函数是 recvfrom(),接收数据时需要把发送方的地址存出来。

    int
    WSAAPI
    recvfrom(
        _In_ SOCKET s,
        _Out_writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf,
        _In_ int len,
        _In_ int flags,
        _Out_writes_bytes_to_opt_(*fromlen, *fromlen) struct sockaddr FAR * from,
        _Inout_opt_ int FAR * fromlen
        );
    

    sendto() 和 recvfrom() 的参数相似:

    参数 说明
    s 用来发送数据的套接字
    buf 指向发送数据的缓冲区
    len 要发送数据的长度
    flags 一般指定为 0
    to/from 指向一个包含目标地址和端口号的 sockaddr in 结构
    tolen/fromlen sockaddr in 结构的大小

    程序编写

    功能设计

    模拟实现 TCP 协议通信过程,要求编程实现服务器端与客户端之间双向数据传递。也就是在一条 TCP 连接中,客户端和服务器相互发送一条数据即可。

    initsock.h

    #include <winsock2.h>
    #pragma comment(lib, "WS2_32")  // 链接到 WS2_32.lib
    
    class CInitSock
    {
    public:
        /*CInitSock 的构造器*/
        CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
        {
            // 初始化WS2_32.dll
            WSADATA wsaData;
            WORD sockVersion = MAKEWORD(minorVer, majorVer);
            if (::WSAStartup(sockVersion, &wsaData) != 0)
            {
                exit(0);
            }
        }
    
        /*CInitSock 的析构器*/
        ~CInitSock()
        {
            ::WSACleanup();
        }
    };
    

    服务器

    #include "initsock.h"
    #include <iostream>
    using namespace std;
    CInitSock initSock;     // 初始化Winsock库
    
    int main()
    {
        // 创建套接字
        SOCKET s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if (s == INVALID_SOCKET)
        {
            cout << "Failed socket()" << endl;
            return 0;
        }
    
        // 填充sockaddr_in结构
        sockaddr_in sin;
        sin.sin_family = AF_INET;
        sin.sin_port = htons(4567);
        sin.sin_addr.S_un.S_addr = INADDR_ANY;
    
        // 绑定这个套接字到一个本地地址
        if (::bind(s, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
        {
            cout << "Failed bind()" << endl;
            return 0;
        }
    
        cout << "服务端已启动!\n" << endl;
    
        // 进入循环
        char buff[1024];
        sockaddr_in addr;
        int nLen = sizeof(addr);
        while (TRUE)
        {
            //接收数据
            int nRecv = ::recvfrom(s, buff, 1024, 0, (sockaddr*)&addr, &nLen);
            if (nRecv > 0)
            {
                buff[nRecv] = '\0';
                cout << " 接收到" << ::inet_ntoa(addr.sin_addr) << "的数据:" << buff << endl;
            }
    
            // 发送数据
            char szText[] = "你好,客户端!";
            ::sendto(s, szText, strlen(szText), 0, (sockaddr*)&addr, sizeof(addr));
        }
        ::closesocket(s);
    }
    

    客户端

    #include "initsock.h"
    #include <iostream>
    using namespace std;
    
    CInitSock initSock;     // 初始化Winsock库
    
    int main()
    {
        // 创建套接字
        SOCKET s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if (s == INVALID_SOCKET)
        {
            printf("Failed socket() %d \n", ::WSAGetLastError());
            return 0;
        }
    
        // 也可以在这里调用bind函数绑定一个本地地址
        // 否则系统将会自动安排
    
        // 填写远程地址信息
        sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(4567);
        //填写服务器程序所在机器的IP地址
        addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    
        // 发送数据
        char szText[] = "你好,服务器!";
        ::sendto(s, szText, strlen(szText), 0, (sockaddr*)&addr, sizeof(addr));
        
        char buff[1024];
        sockaddr_in client_addr;
        int nLen = sizeof(client_addr);
        int nRecv = ::recvfrom(s, buff, 1024, 0, (sockaddr*)&client_addr, &nLen);
        if (nRecv > 0)
        {
            buff[nRecv] = '\0';
            cout << "  接收到" << ::inet_ntoa(client_addr.sin_addr) << "的数据:" << buff << endl;
        }
    
        ::closesocket(s);
        return 0;
    }
    

    运行效果

    广播通信

    广播

    利用广播(broadcast)可以将数据发送给本地子网上的每个机器,同时需要有一些线程在机器上监听到来的数据。广播的缺点是如果多个进程都发送广播数据,网络就会阻塞,网络性能便会受到影响。

    广播模式设置

    套接字创建之后,可以使用套接字选项和 ioctl 命令操作它的属性,以改变套接字的默认行为。I/O 控制命令缩写为 ioctl,它也影响套接字的行为,获取和设置套接字选项的函数分别是 getsockopt 和 setsockopt,函数调用出错时返回 SOCKET ERROR。

    int
    WSAAPI
    setsockopt(
        _In_ SOCKET s,
        _In_ int level,
        _In_ int optname,
        _In_reads_bytes_opt_(optlen) const char FAR * optval,
        _In_ int optlen
        );
    
    int WSAAPI getsockopt(_In_ SOCKET s, _In_ int level, _In_ int optname, _In_reads_bytes_opt_(optlen) const char FAR * optval, _In_ int optlen);
    
    参数 说明
    s 套接字句柄
    level 指定此选项被定义在哪个级别,如 SOL SOCKET、IPPROTO_TCP、IPPROTO_IP 等
    optname 套接字选项名称,如 SO ACCEPTCONN
    optval 指定一个缓冲区,所请求的选项的值将会被返回到这里
    optlen 指定上面 optval 所指缓冲区的大小,返回所需的大小
    SOBROADCAST 选项设置套接字传输和接收广播消息,如果给定套接字已经被设置为接收或者发送广播数据,查询此套接字选项将返回TRUE,此选项对于那些不是SOCK_STREAM类型的套接字有效。
    ::setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char*)&bBroadcast, sizeof(BOOL));
    

    代码编写

    功能设计

    实现服务器端与客户端之间广播数据传递,服务器端向所有客户端发送“今天是个好日子!”广播消息,客户端收到并在本地显示。

    initsock.h

    #include <winsock2.h>
    #pragma comment(lib, "WS2_32")  // 链接到 WS2_32.lib
    
    class CInitSock
    {
    public:
        /*CInitSock 的构造器*/
        CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
        {
            // 初始化WS2_32.dll
            WSADATA wsaData;
            WORD sockVersion = MAKEWORD(minorVer, majorVer);
            if (::WSAStartup(sockVersion, &wsaData) != 0)
            {
                exit(0);
            }
        }
    
        /*CInitSock 的析构器*/
        ~CInitSock()
        {
            ::WSACleanup();
        }
    };
    

    Sender

    对于 UDP 来说存在一个特定的广播地址 255.255.255.255,广播数据都应该发送到这里。发送方程序在创建套接字后使用 setsockopt 函数打开 SO_BROADCAST选项,然后设置广播地址 255.255.255.255,使用 sendto 函数向端口号 54321 不断发送广播数据。

    #include "initsock.h"
    #include <iostream>
    #include <windows.h>
    using namespace std;
    
    CInitSock theSock;
    
    int main()
    {
        SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0);
        // 有效SO_BROADCAST选项
        BOOL bBroadcast = TRUE;
        ::setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char*)&bBroadcast, sizeof(BOOL));
    
        // 设置广播地址和广播端口号
        SOCKADDR_IN bcast;
        bcast.sin_family = AF_INET;
        bcast.sin_port = htons(54321);
        bcast.sin_addr.s_addr = INADDR_BROADCAST;
    
        // 发送广播
        cout << "  开始向端口发送广播数据... \n" << endl;
        char sz[] = "今天是个好日子! \r\n";
        while (TRUE)
        {
            time_t now = time(0);    // 基于当前系统的当前日期/时间
            char* dt = ctime(&now);
            ::sendto(s, sz, strlen(sz), 0, (sockaddr*)&bcast, sizeof(bcast));
            cout << dt << "发送广播:" << sz << endl;
            ::Sleep(5000);
        }
        return 0;
    }
    

    Recver

    服务器需要使用 recvfrom 函数,向端口号 54321 接收广播数据。

    #include "initsock.h"
    #include <iostream>
    #include <windows.h>
    using namespace std;
    
    CInitSock theSock;
    
    int main()
    {
        SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0);
    
        // 首先要绑定一个本地地址,指明广播端口号
        SOCKADDR_IN sin;
        sin.sin_family = AF_INET;
        sin.sin_port = htons(54321);
        sin.sin_addr.S_un.S_addr = INADDR_ANY;
        if (::bind(s, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
        {
            cout << " bind() failed!" << endl;
            return 0;
        }
    
        // 接收广播
        cout << "  开始在端口接收广播数据... \n" << endl;
        SOCKADDR_IN addrRemote;
        int nLen = sizeof(addrRemote);
        char sz[256];
        while (TRUE)
        {
            int nRet = ::recvfrom(s, sz, 256, 0, (sockaddr*)&addrRemote, &nLen);
            if (nRet > 0)
            {
                sz[nRet] = '\0';
                time_t now = time(0);    // 基于当前系统的当前日期/时间
                char* dt = ctime(&now);
                cout << dt << "接收到广播:" << sz << endl;
            }
        }
        return 0;
    }
    

    运行效果


    参考资料

    《Windows 网络与通信编程》,陈香凝 王烨阳 陈婷婷 张铮 编著,人民邮电出版社

  • 相关阅读:
    php 仿百度文库
    Linux PHP实现仿百度文库预览功能
    linux下设置环境变量
    Nginx出现413 Request Entity Too Large错误解决方法
    python例子-urllib,urllib2练习题合集.
    linux问题-CentOS7和以往版本的变化
    linux问题-CentOS7中搭建HTTP,FTP服务,改变提示颜色
    shell脚本编程-例子_使用expect下载ftp文件
    centos中安装rpm包报错——No KEY
    shell脚本编程-例子_服务器存活监控
  • 原文地址:https://www.cnblogs.com/linfangnan/p/15569898.html
Copyright © 2011-2022 走看看