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

UDP特点

   无连接,面向数据报(基于消息,不会粘包)的传输数据服务;

   不可靠(可能会丢包, 乱序, 反复), 但因此普通情况下UDP更加高效;


UDP客户/服务器模型

 

 

UDP-API使用

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

/**实践: 实现一个基于UDP的echo回声server/client**/
//server端代码
void echoServer(int sockfd);
int main()
{
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1)
        err_exit("socket error");

    struct sockaddr_in servAddr;
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(8001);
    if (bind(sockfd, (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)
        err_exit("bind error");

    echoServer(sockfd);
}
void echoServer(int sockfd)
{
    char buf[BUFSIZ];
    ssize_t recvBytes = 0;
    struct sockaddr_in clientAddr;
    socklen_t addrLen;
    while (true)
    {
        memset(buf, 0, sizeof(buf));
        addrLen = sizeof(clientAddr);
        memset(&clientAddr, 0, addrLen);
        recvBytes = recvfrom(sockfd, buf, sizeof(buf), 0,
                             (struct sockaddr *)&clientAddr, &addrLen);
        //假设recvBytes=0, 并不代表对端连接关闭, 由于UDP是无连接的
        if (recvBytes < 0)
        {
            if (errno == EINTR)
                continue;
            else
                err_exit("recvfrom error");
        }

        cout << buf ;
        if (sendto(sockfd, buf, recvBytes, 0,
                   (const struct sockaddr *)&clientAddr, addrLen) == -1)
            err_exit("sendto error");
    }
}
/**client端代码**/
void echoClient(int sockfd);
int main()
{
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1)
        err_exit("socket error");
    echoClient(sockfd);
    cout << "Client exiting..." << endl;
}
void echoClient(int sockfd)
{
    struct sockaddr_in servAddr;
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servAddr.sin_port = htons(8001);
    char buf[BUFSIZ] = {0};
    while (fgets(buf, sizeof(buf), stdin) != NULL)
    {
        if (sendto(sockfd, buf, strlen(buf), 0,
                   (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)
            err_exit("sendto error");
        memset(buf, 0, sizeof(buf));
        int recvBytes = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
        if (recvBytes == -1)
        {
            if (errno == EINTR)
                continue;
            else
                err_exit("recvfrom error");
        }
        cout << buf ;
        memset(buf, 0, sizeof(buf));
    }
}

实践解析:编译执行server,在两个终端里各开一个client与server交互,能够看到server具有并发服务的能力。用<Ctrl+C>关闭server,然后再执行server,此时client还能和server联系上。和前面TCP程序的执行结果相比較。我们能够体会无连接的含义。udp 协议来说。server与client 的界限更模糊了,仅仅要知道对等方地址(ip和port) 都能够主动发数据。

 

UDP编程注意事项

   1.UDP报文可能会丢失(超时重传)、反复、乱序(维护一个序号)

   2.UDP缺乏流量控制:当缓冲区写满以后,由于UDP没有流量控制机制,因此会覆盖缓冲区。

   3.UDP协议数据报文截断:假设对端发送的UDP数据报大于本地接收缓冲区,报文可能被截断,后面的部分会丢失(而不是像我们想象的下一次能够接收到)。

   4.recvfrom能够返回0。并不代表连接关闭。由于UDP是无连接的, 代表发送端没有发送不论什么数据[sendto能够发送数据0包(仅仅含有UDP+IP首部40B)]。

   5.ICMP异步错误

      观察现象:使用上例,关闭UDP服务端,启动客户端,从键盘接受数据后。再发送数据。假设recvfrom中flags标志为0, 且client端没有调用connect的情况下, UDP客户端堵塞在recvfrom位置(见測试代码3);

      说明:

         1)UDP发送报文的时,仅仅把数据copy到发送缓冲区

在服务器没有起来的情况下,能够发送成功

         2)所谓ICMP异步错误是指:发送的报文的时候,没有错误,接受报文recvfrom的时候,回收到ICMP应答.

         3)异步的错误。无法返回未连接的套接字, 因此假设上例我们调用了connect, 是能够收到该异步ICMP报文的;

   6.UDP调用connect

      1)UDP调用connet,并没有三次握手,仅仅是维护了一个(和对等方的)状态信息, 因此我们能够看到即使server没有开启, client端的connect依旧还能够正确返回的!(測试代码如測试代码2)

      2)一但调用connect, 发送能够使用send/write, 接收能够使用recv/read函数(见測试代码3)

   7.UDP外出接口的确定:

      假设客户端有多个IP地址。由connect /sendto 函数提供的远程地址的參数。系统会选择一个合适的出口。比方Server的IP是192.168.2.10, 而客户端如今的IP有 192.168.1.32 和 192.168.2.75 那么会自己主动选择192.168.2.75 这个IP出去。

/**測试1: 測试注意点3, UDP报文截断, recvfrom返回-1, errno值为EAGAIN**/
int main()
{
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1)
        err_exit("socket error");

    struct sockaddr_in servAddr;
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(8001);
    if (bind(sockfd, (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)
        err_exit("bind error");
    //给自己发送数据
    if (sendto(sockfd, "ABCDE", 5, 0,
               (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)
        err_exit("sendto error");

    for (int i = 0; i < 5; ++i)
    {
        char ch;
        int recvBytes =  recvfrom(sockfd, &ch, 1, MSG_DONTWAIT, NULL, NULL);
        if (recvBytes == -1)
        {
            if (errno == EINTR)
                continue;
            else if (errno == EAGAIN)
                err_exit("recvfrom error");
        }
        else
            cout << "char = " << ch << ", recvBytes = " << recvBytes << endl;
    }
}
/**測试2:将client端echoClient函数的代码改造例如以下, 注意是在server端尚未开启时执行该程序**/
void echoClient(int sockfd)
{
    struct sockaddr_in servAddr;
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servAddr.sin_port = htons(8001);
    // UDP client端调用connect
    if (connect(sockfd, (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)
        err_exit("connect error");

    char buf[BUFSIZ] = {0};
    while (fgets(buf, sizeof(buf), stdin) != NULL)
    {
        if (sendto(sockfd, buf, strlen(buf), 0,
                   (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)
            err_exit("sendto error");
        memset(buf, 0, sizeof(buf));
        int recvBytes = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
        if (recvBytes == -1)
            err_exit("recvfrom error");
        cout << buf ;
        memset(buf, 0, sizeof(buf));
    }
}
/**測试3: client端在调用connect之后调用send, 而不是send**/
void echoClient(int sockfd)
{
    struct sockaddr_in servAddr;
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servAddr.sin_port = htons(8001);
    // UDP client端调用connect
    if (connect(sockfd, (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)
        err_exit("connect error");

    char buf[BUFSIZ] = {0};
    while (fgets(buf, sizeof(buf), stdin) != NULL)
    {
        if (send(sockfd, buf, strlen(buf), 0) == -1)
            err_exit("send error");
        memset(buf, 0, sizeof(buf));
        int recvBytes = recv(sockfd, buf, sizeof(buf), 0);
        if (recvBytes == -1)
            err_exit("recv error");
        cout << buf ;
        memset(buf, 0, sizeof(buf));
    }
}


版权声明:本文博客原创文章,博客,未经同意,不得转载。

原文地址:https://www.cnblogs.com/hrhguanli/p/4630567.html