UDP

UDP (User Datagram Protocol,用户数据报协议), 是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。

客户不与服务器建立连接,而是只管使用sendto函数给服务器发送数据报,其中必须指定目的地(服务器)的地址作为参数。

服务器不接受来自客户的连接,而是只管调用recvfrom函数,等待来自某个客户的数据到达。 

#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);

ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to, socklen_t *addrlen);

// 成功则返回读、写的字节数,出错则为-1。

第一个参数sockfd:描述符;

第二个参数buff:指向读入或写出的缓冲区的指针;

第三个参数nbytes:读写字节数;

第四个参数flags:调用操作方式;

第五个参数from/to:指向数据报发送者或接收者的协议地址(如IP地址和端口号)的套接字地址结构;

第六个参数addrlen:from/to参数的大小。

 

使用UDP写一个长度为0的数据报是可行的,没有数据的IP数据报只包含一个IP首部(IPv4为20字节,IPv6为40字节)和一个8字节UDP首部。

此时对于数据报协议,recvfrom返回0值是可接受的:它并不像TCP套接字上read返回0值来表示对端已关闭连接。

既然UDP是无连接的,因此也就没有诸如关闭一个UDP连接之类的事情。

如果recvfrom函数的from参数是一个空指针,相应的长度参数addrlen也必须为空指针,表示我们并不关心数据发送者的协议地址。

 

大多数TCP服务器是并发的(fork),大多数UDP服务器是迭代的。

UDP层中隐含有排队发生,每个UDP套接字都有一个接收缓冲区,到达该套接字的每个数据报都进入这个套接字接收缓冲区。

当进程调用recvfrom时缓冲区中的下一个数据报以FIFO顺序返回给进程,而缓冲区大小是有限的。

 

 从客户角度总结UDP客户/服务器:

 

从服务器角度总结UDP客户/服务器:

 服务器从到达的IP数据报中获取的信息:

 

UDP服务器:

#include <unp.h>
#include <iostream>
using namespace std;

void dg_echo(int sockfd, SA *cliaddr, socklen_t clilen)
{
    int n;
    socklen_t len = clilen;
    char msg[MAXLINE];
    for ( ; ; ){
        bzero(msg, MAXLINE);
        if ( (n = recvfrom(sockfd, msg, MAXLINE, 0, cliaddr, &len)) < 0){
            cout<<"recvfrom error:"<<errno<<endl;
            exit(0);
        }

        cout<<msg<<endl;

        if (sendto(sockfd, msg, n, 0, cliaddr, len) < 0){
            cout<<"sendto error:"<<errno<<endl;
            exit(0);
        }
    }
}

int main(int argc, char **argv)
{
    int sockfd;
    struct sockaddr_in servaddr, cliaddr;
    if  ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
        cout<<"socket error:"<<errno<<endl;
        exit(0);
    }
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(30001);

    if (bind(sockfd, (SA*)&servaddr, sizeof(servaddr)) < 0){
        cout<<"bind error:"<<errno<<endl;
        exit(0);
    }

    dg_echo(sockfd, (SA*)&cliaddr, sizeof(cliaddr));
    return 0;
}

UDP客户:

#include <unp.h>
#include <iostream>
using namespace std;

void dg_cli(FILE *fp, int sockfd, SA *pservaddr, socklen_t servlen)
{
    int n;
    char sendline[MAXLINE], recvline[MAXLINE + 1];
    while (fgets(sendline, MAXLINE, fp) != NULL){
        if (sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen) < 0){
            cout<<"sendto error"<<endl;
            exit(0);
        }

        if ( (n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0){
            cout<<"recvfrom error"<<endl;
            exit(0);
        }

        recvline[n] = 0;
        fputs(recvline, stdout);

        bzero(sendline, MAXLINE);
        bzero(recvline, MAXLINE);
    }
}

int main(int argc, char **argv)
{
    int sockfd;
    struct sockaddr_in servaddr;

    if (argc !=2){
        cout<<"usage: udpcli <IPaddress>"<<endl;
        exit(0);
    }
    if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
        cout<<"socket error"<<endl;
        exit(0);
    }
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(30001);
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
        cout<<"inet_pton error for "<<argv[1]<<endl;
        exit(0);
    }

    dg_cli(stdin, sockfd, (SA*)&servaddr, sizeof(servaddr));

    return 0;
}

 

发送固定数目的数据报到服务器:

#define NDG     2000
#define DGLEN   1400

void cli_send_n(FILE *fp, int sockfd, SA *pservaddr, socklen_t servlen)
{
    int n;
    char sendline[DGLEN];
    for (i = 0; i < NDG; ++i){
        sendto(sockfd, sendline, DGLEN, 0, pservaddr, servlen);
    }
}

服务器计数统计接收到的数据报:

static int g_count = 0;
static void recvfrom_int(int);

void count_dg(int sockfd, SA *cliaddr, socklen_t clilen)
{
    socklen_t len = clilen;
    char msg[MAXLINE];
    for ( ; ; ){
        len = clilen;
        recvfrom(sockfd, msg, MAXLINE, 0, cliaddr, &len);
        g_count++;
    }
}

static void recvfrom_int(int sigint)
{
    printf("\nreceived %d datagrams\n", count);
    exit(0);
}

在FreeBSD下UDP套接字接收缓冲区的默认大小为42080,即30个1400字节数据报的容纳空间。

服务器端增大套接字接收队列大小:

void count_dg(int sockfd, SA *cliaddr, socklen_t clilen)
{
    int n;
    socklen_t len = clilen;
    char msg[MAXLINE];

    n = 220 * 1024;
    setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));

    for ( ; ; ){
        len = clilen;
        recvfrom(sockfd, msg, MAXLINE, 0, cliaddr, &len);
        g_count++;
    }
}

 

posted @ 2016-04-30 11:35  LarryKnight  阅读(250)  评论(0编辑  收藏  举报