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));
    }
}
时间: 2024-10-27 08:41:35

Socket编程实践(12) --UDP编程基础的相关文章

Socket编程实践(13) --UDP编程基础(1)

UDP特点 无连接,面向数据报(基于消息,不会粘包)的数据传输服务; 不可靠(可能会丢包),但一般情况下UDP更加高效;     UDP客户/服务基本模型 UDP基础API 1.Recvfrom SYNOPSIS #include <sys/types.h> #include <sys/socket.h> ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr,

Socket编程实践(14) --UDP编程基础(2)

使用UDP注意事项 1.UDP报文可能会丢失.重复.乱序 2.UDP缺乏流量控制:当缓冲区写满以后,由于UDP没有流量控制机制,因此会覆盖缓冲区. 3.UDP协议数据报文截断:如果接收到的UDP数据报大于缓冲区,报文可能被截断,后面的部分会丢失. 4.使用UDP: recvfrom返回0,不代表连接关闭,因为UDP是无连接的. 而且sendto可以发送数据0包(只含有UDP首部[20字节]); 5.ICMP异步错误 观察现象:关闭UDP服务端,启动客户端,从键盘接受数据后,再发送数据.UDP客户

Socket编程实践(12) --select实现超时I/O API[比较难于理解/代码较多]

read_timeout函数封装 //读超时函数,但不包含读操作 int read_timeout(int fd, long waitSec) { int returnValue = 0; if (waitSec > 0) { fd_set readSet; FD_ZERO(&readSet); //清零 FD_SET(fd,&readSet); //添加 struct timeval waitTime; waitTime.tv_sec = waitSec; waitTime.tv_

基于socket的TCP和UDP编程

一.概述 TCP(传输控制协议)和UDP(用户数据报协议是网络体系结构TCP/IP模型中传输层一层中的两个不同的通信协议. TCP:传输控制协议,一种面向连接的协议,给用户进程提供可靠的全双工的字节流,TCP套接口是字节流套接口(STream socket)的一种. UDP:用户数据报协议.UDP是一种无连接协议.UDP套接口是数据报套接口(datagram Socket)的一种. 二.TCP和UDP介绍 1)基本TCP客户—服务器服务器 服务器是指在网络环境下运行相应的应用软件,为网上用户提供

Linux下的socket编程实践(十) 基本UDP编程细节

在我的这两篇博客中,简单介绍并实现了基于UDP(TCP)的windows(UNIX下流程基本一致)下的服务端和客户端的程序,本文继续探讨关于UDP编程的一些细节. http://blog.csdn.net/nk_test/article/details/47733307 http://blog.csdn.net/nk_test/article/details/47756381 下图是一个简单的UDP客户/服务器模型: 我在这里也实现了一个简单的UDP回射服务器/客户端: /**实践: 实现一个基

linux系统socket通信编程实践

简单介绍并实现了基于UDP(TCP)的windows(UNIX下流程基本一致)下的服务端和客户端的程序,本文继续探讨关于UDP编程的一些细节. 下图是一个简单的UDP客户/服务器模型: .imageplus-append-lu-img-txt { overflow: hidden; margin: 10px 0 } .imageplus-append-nova-txt { border: 1px solid #f2f2f2; font-family: Microsoft YaHei; line-

Java笔记二十三.网络编程基础与UDP编程

网络编程基础与UDP编程 转载请表明出处:http://blog.csdn.net/u012637501(嵌入式_小J的天空) 一.网络编程基础 1.TCP/IP协议:TCP/IP协议是一个非常实用的网络应用程序通信协议,包括TCP(传输控制协议)和IP地址(计算机唯一标识号). 2.IP地址:IP在互联网中能唯一标识一台计算机,是每一台计算机的唯一标识(身份证),通过这个标识号来指定接收数据的计算机和识别发送数据的计算机,该标识号即为IP地址. (1)Ipv4:指在计算机中IP地址用4个字节(

Socket编程实践(6) --TCP粘包原因与解决

流协议与粘包 粘包的表现 Host A 发送数据给 Host B; 而Host B 接收数据的方式不确定 粘包产生的原因 说明 TCP 字节流,无边界 对等方,一次读操作,不能保证完全把消息读完 UDP 数据报,有边界 对方接受数据包的个数是不确定的 产生粘包问题的原因分析 1.SQ_SNDBUF 套接字本身有缓冲区 (发送缓冲区.接受缓冲区) 2.tcp传送的端 mss大小限制 3.链路层也有MTU大小限制,如果数据包大于>MTU要在IP层进行分片,导致消息分割. 4.tcp的流量控制和拥塞控

Socket编程实践(6) --TCPNotes服务器

僵尸进程过程 1)通过忽略SIGCHLD信号,避免僵尸进程 在server端代码中加入 signal(SIGCHLD, SIG_IGN); 2)通过wait/waitpid方法.解决僵尸进程 signal(SIGCHLD,onSignalCatch); void onSignalCatch(int signalNumber) { wait(NULL); } 3) 假设多个客户端同一时候关闭, 问题描写叙述如以下两幅图所看到的: watermark/2/text/aHR0cDovL2Jsb2cuY