八、基本UDP套接字编程

news/2024/7/7 1:39:38

1.典型的UDP客户/服务器程序函数调用图

 

2. recvfrom和sendto函数

#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);

说明:recvfrom和sendto函数前面三个参数等同于read和write的三个参数。flags暂时不讨论。两个函数最后两个参数为NULL时可用于TCP。

3. UDP回射服务端程序示例

View Code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define SRV_PORT 7777
#define MAXLINE 4096

void dg_echo(int sockfd, struct sockaddr *cliAddr, socklen_t addrlen);

int main(int argc, char **argv)
{
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        perror("create socket failed");
        exit(-1);
    }

    struct sockaddr_in srvaddr;
    bzero(&srvaddr, sizeof(srvaddr));

    srvaddr.sin_family = AF_INET;
    srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    srvaddr.sin_port = htons(SRV_PORT);

    int bret = bind(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr));
    if( bret < 0)
    {
        perror("bind failed");
        close(sockfd);
        exit(-1);
    }

    struct sockaddr_in cliaddr;
    bzero(&cliaddr, sizeof(cliaddr));

    dg_echo(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
}

void dg_echo(int sockfd, struct sockaddr *cliAddr, socklen_t addrlen)
{
    char msg[MAXLINE];
    
    for( ;; )
    {
        socklen_t len = addrlen;
        int bCount = recvfrom(sockfd, msg, MAXLINE, 0, cliAddr, &len);
        if(bCount <= 0)
        {
            perror("recvfrom error");
            return;
        }
        
        int sCount = sendto(sockfd, msg, bCount, 0, cliAddr, len);
        if(sCount < 0)
        {
            perror("sendto error");
            return;
        }
    }
}

说明:大多数TCP服务器是并发的,而UDP服务器是迭代的。

4. UDP回射客户端程序示例

View Code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>

#define SRV_PORT 7777
#define MAXLINE 4096

void dg_cli(FILE *fp, int sockfd, struct sockaddr *cliAddr, socklen_t addrlen);

int main(int argc, char **argv)
{
    if(argc < 2)
    {
        printf("usage:udpcli <ip address>\n");
        exit(-1);
    }
    struct sockaddr_in srvaddr;
    bzero(&srvaddr, sizeof(srvaddr));

    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(SRV_PORT);
    //srvaddr.sin_port = htons(7);
    int ret = inet_pton(AF_INET, argv[1], &srvaddr.sin_addr);
    if(ret <= 0)
    {
        printf("inet_pton address error %s\n",argv[1]);
        exit(-1);
    }
    
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    dg_cli(stdin, sockfd, (struct sockaddr*)&srvaddr, sizeof(srvaddr));
    exit(0);
}

void dg_cli(FILE *fp, int sockfd, struct sockaddr *cliAddr, socklen_t addrlen)
{
    char sndmsg[MAXLINE];
    char rcvmsg[MAXLINE];
    while(fgets(sndmsg, MAXLINE, fp))
    {
        int sret = sendto(sockfd, sndmsg, strlen(sndmsg), 0, cliAddr, addrlen);
        if(sret < 0)
        {
            perror("sendto failed");
            return;
        }

        int rcount = recvfrom(sockfd, rcvmsg, MAXLINE, 0,  NULL, NULL);
        if(rcount < 0)
        {
            perror("recvfrom error");
            return;
        }
        rcvmsg[rcount] = 0;
        fputs(rcvmsg, stdout);
    }
}

说明:recvfrom函数最后面两个参数为NULL,说明客户端没有关注消息应答者。这样可能造成任何进程像本客户端IP+端口上发数据都会被认为是消息应答。

5. 数据报的丢失

      上面例子中UDP客户、服务器的传输是不可靠的,如果客户端发送的数据丢失,或是服务器的应答丢失,客户端recvfrom将永远阻塞。在后面将继续讨论如果增加UDP的可靠性。

6. 验证收到的响应

更改的程序

View Code
void dg_cli(FILE *fp, int sockfd, struct sockaddr *cliAddr, socklen_t addrlen)
{
    char sndmsg[MAXLINE];
    char rcvmsg[MAXLINE + 1];
    
    struct sockaddr *replyAddr = malloc(addrlen);

    while(fgets(sndmsg, MAXLINE, fp))
    {

        int sret = sendto(sockfd, sndmsg, strlen(sndmsg), 0, cliAddr, addrlen);
        if(sret < 0)
        {
            perror("sendto failed");
            return;
        }
        socklen_t len = addrlen;
        int rcount = recvfrom(sockfd, rcvmsg, MAXLINE, 0,  replyAddr, &len);
        if(rcount < 0)
        {
            perror("recvfrom error");
            return;
        }
        if(len != addrlen || memcmp(cliAddr, replyAddr, len) != 0 )
        {
            char addr[56] = {0};
            inet_ntop(AF_INET, replyAddr, addr, len);
            printf("reply from %s(ignored)\n", addr);
            continue;
        }
        rcvmsg[rcount] = 0;
        fputs(rcvmsg, stdout);
    }
}

说明:如果服务器是多宿的主机,客户端发送数据给服务器,如果服务器没有把套接字绑定在一个IP上,其应答外出接口的IP地址将是服务器的主IP地址,可能不是客户端发送的目的IP地址。所以说上面测程序有可能失败。

解决办法:

  • 客户通过在DNS中查找服务器主机的名字来验证该主机的域名而不是IP;
  • 服务器给每个IP创建一个套接字,用bind绑定到各自的套接字,然后用select再从可读的套接字给出应答。

7. 服务器未运行

  • sendto将产生port unreachable错误,但是sendto却返回成功;
  • recvfrom将收不到数据,永远阻塞;
  • 规则:对于UDP套接字,它引发的异步错误不返回给它本身,除非它已经连接。所以只有在进程已经连接到对端后,错误才会返回。后面将说到UDP的connect函数。

8. UDP的connect函数

1)udp的connect没有三次握手过程,完全是本地行为;

2)既然有connect,我们就要区分未连接UDP套接字(默认)和已连接UDP套接字(调用connect);

3)已连接套接字和未连接套接字的区别:

    • 不用给输出指定目的IP和端口了,即不使用sendto而是改用wirte或send;
    • 不必使用recvfrom,而是使用read、recv或recvmsg;
    • UDP套接字引发的异步错误将返回给它们的进程。

4)UDP套接字多次调用connect可能出于以下目的:

    • 指定新地址的IP地址和端口号(对于TCP,connect只能调用一次)
    • 断开套接字:需要再次调用connect,把地址结构上的地址族成员(sin_family或sin6_family)设置成AF_UNSPEC

5)性能

    • 未连接套接字两次sendto执行步骤:连接套接字 -> 输出第一个数据报 -> 断开套接字连接 -> 连接套接字 -> 输出第二个数据报 ->断开套接字连接;
    • 已连接套接字两次write的步骤:连接套接字 -> 输出第一个数据报 -> 输出第二个数据报
    • 从上面可看出未连接的两次发送数据比已经连接的两次发送数据复杂的多,效率自然低得多。

9. 6中例子函数的修订

View Code
//使用connect
void dg_cli_2(FILE *fp, int sockfd, struct sockaddr *cliAddr, socklen_t addrlen)
{
    char sndmsg[MAXLINE];
    char rcvmsg[MAXLINE + 1];
    
    struct sockaddr *replyAddr = malloc(addrlen);

    while(fgets(sndmsg, MAXLINE, fp))
    {

        if( connect(sockfd, cliAddr, addrlen) < 0 )
        {
            perror("connect error");
            return;
        }
        
        if( write( sockfd, sndmsg, strlen(sndmsg) ) != strlen(sndmsg))
        {
            perror("write error");
            return;
        }
        socklen_t len = addrlen;
        int rcount = read(sockfd, rcvmsg, MAXLINE);
        if(rcount < 0)
        {
            perror("recvfrom error");
            return;
        }
        rcvmsg[rcount] = 0;
        fputs(rcvmsg, stdout);
    }
}

示例中返回的错误码为:ECONNREFUSED,映射成消息串是“Connection refused”

10. UDP的流量控制

      UDP发送端发送大量数据给接收端,消息淹没接收端是比较容易的,这会造成大量的数据被接收端丢弃。当然,通过改变接收端缓冲区的大小可以缓解这个情况,但是没根本解决问题。

 

11. 使用select的TCP和UDP回射服务器程序

 

转载于:https://www.cnblogs.com/4tian/archive/2012/08/11/2634121.html


http://www.niftyadmin.cn/n/3501634.html

相关文章

C# WPF中设置图标时出现TypeConverterMarkupExtension异常

2019独角兽企业重金招聘Python工程师标准>>> 异常内容为&#xff1a;System.Windows.Baml2006.TypeConverterMarkupExtension 是因为有些地方比如菜单和左上角默认的图标等&#xff0c;只能使用ico格式的文件&#xff0c;如果设置的是png格式的文件&#xff0c;就会…

Eclipse中使用JUnit4单元测试 初级 中级 高级

通过前 2 篇文章&#xff0c;您一定对 JUnit 有了一个基本的了解&#xff0c;下面我们来探讨一下JUnit4 中一些高级特性。 一、 高级 Fixture 上一篇文章中我们介绍了两个 Fixture 标注&#xff0c;分别是 Before 和 After &#xff0c;我们来看看他们是否适合完成如下功能…

C#中的string

这篇文章我来总结一些string相关知识。 System.String类型&#xff1a;平时在编程中对于string的用法应该是特别频繁的。通常我们会把string,int ,float放在一起比较&#xff0c;由于后面两个都是值类型&#xff0c;所以非常想当然的会认为string也会是值类型&#xff0c;这是错…

Python学习笔记13—错误和异常

常见的异常&#xff1a; 处理单个异常 #!/usr/bin/env Python # codingutf-8 class Calculator(object):is_raise Falsedef calc(self, express):try:return eval(express)except ZeroDivisionError:if self.is_raise:print "zero can not be division."else:raise …

linux命令文件系统里关键字,Linux长文认识Linux和一些操作(统计关键字权限修改等等)...

Linux长文认识Linux和一些操作(统计关键字权限修改等等)【Linux】长文认识Linux和一些操作(统计关键字、权限修改等等)文章目录一、Linux总体认识1、Linux的特点2、Linux的版本3、Linux下的文件系统4、Linux和Windows系统有什么不同二、Linux下的常用操作1、快捷键2、vim里的一…

首页调单个产品分类的推荐产品,最新产品和热卖商品

首页调单个产品分类的推荐产品&#xff0c;最新产品和热卖商品 在index.php文件里面有一段代码就是获得推荐产品&#xff0c;最新产品和热卖商品的&#xff0c;但是那是对所有分类而言的。其实要调单个分类呢&#xff0c;很简单&#xff0c;$act !empty($_GET[act]) ? $_GET[…

青年古筝演奏家吴莉

吴莉&#xff0c;出生于贵州。青年古筝演奏家&#xff0c;中国音乐家协会古筝专业委员会委员。国际、国内古筝大赛双料金奖得主。现执教于广州星海音乐学院民乐系。 吴莉6岁随陈灵芝习筝&#xff0c;1989年考入中央民族大学附中&#xff0c;师从海木兰。1995年考入中央音乐学院…

实验四linux文件系统,实验4Linux文件系统命令.doc

实验四 Linux常用文件命令一、实验目的在本次实验中&#xff0c;将介绍一些基本的Linux文件系统命令&#xff0c;并通过一些实际的例子使学生边学边用&#xff0c;让大家尽快熟悉Linux文件系统&#xff0c;加深对文件、目录、文件系统等概念的理解。了解文件系统管理的基本概念…