UDP套接字编程

来源:互联网 发布:雷霆战机网络连接超时 编辑:程序博客网 时间:2024/04/29 00:00

UDP编程简介

  1. TCP和UDP在传输层区别
    UDP是无连接不可靠的数据报协议。TCP提供面向连接的可靠字节流。

  2. 使用UDP常见应用
    DNS(域名系统),NFS(网络文件系统),SNMP(简单网络管理协议)

典型的UDP的Client和Server

(1)客户端不和服务器建立连接,只是使用sendto给服务器发送数据,必须指定服务器地址作为参数。(2)服务器不接受来自客户端的连接,而只是使用recvfrom,等待来自客户端的数据达到。recvfrom同时返回客户端的协议地址,用于服务器给客户端响应。        UDP Client                         UDP服务器                                           socket()                                              |                                                    |                                         bind()众所周知端口                                              |                                                  |        socket()                         recvfrom() <----------            |                                  |               |           |          数据请求                |                |    --->sendto()  ----------------->    阻塞,直到收到数据       |      |      |                                  |               |    |      |                                  |               |    |      |                               处理请求            |    |      |                                  |               |    |      |                                  |               |    |      |           数据应答                |               |    |---recvfrom() <-----------------     sendto() ------------           |           |           |         close()

重要函数recvfrom和sendto

recvfrom函数:

    #include <sys/socket.h>    ssize_t recvfrom(int sockfd,void* buff,size_t nbytes,int flags,struct sockaddr* from,socklen_t *addrlen);    参数:sockfd  : 套接字描述符          buff    : 用于存放数据的缓冲区          nbytes  : 缓冲区大小          flags   : 暂时总设置为0          from    : 用于存放UDP对端的套接字协议地址(输出参数)          addrlen : UDP对端的套接字协议地址字节大小(输出参数)    注意:最后两个参数from和addrlen可以得知该UDP数据是谁发送过来的。          如果设置from和addrlen为NULL,表示我们忽略对端信息。    返回值:        成功返回读取到的字节数,出错返回-1。返回值0是被允许的,不同于TCP中read返回0表示对端已经关闭。

sendto函数:

    #include <sys/socket.h>    ssize_t sendto(int sockfd,const void* buff,size_t nbytes,int flags,const struct sockaddr* to,socklen_t *addrlen);    参数:sockfd  : 套接字描述符          buff    : 要发送的数据内存          nbytes  : 数据大小          flags   : 暂时总设置为0          to      : 指向接收者的套接字地址结构(输入参数)          addrlen : 上述套接字地址结构to的字节大小(输入参数)    注意:最后两个参数to和addrlen告知该数据要发给谁。    返回值:成功返回写入的字节数,出错返回-1        写入字节长度为0是被允许的,在UDP下,会形成一个只包含20字节的IP首部和一个8字节的UDP首部,没        有数据的IP数据报。

UDP的回射实例

        fgets               sendto                      recvfromstdin---------->            ----------------------------------->                  UDP客户端                                     UDP服务器stdout<---------             <----------------------------------         fputs               recvfrom                     sendto说明:(1)通过socket指定参数SOCK_DGRAM创建UDP套接字。(2)bind指定server端本地地址为INADDR_ANY(0),端口为众所周知。(3)UDP中没有TCP中的EOF,因此recvfrom不会终止。(4)recvfrom是阻塞的,因此提供的是一个迭代服务器,而不是并发服务器。
**//UDPserver代码**#include <iostream>#include <sys/socket.h>#include <sys/types.h>#include <strings.h>#include <arpa/inet.h>#include <unistd.h>using namespace std;#define MAXLINE     1024#define SERV_PORT   29988void dg_echo(int sockfd,struct sockaddr* pcliaddr,socklen_t clilen){    int n;    socklen_t len;    char mesg[MAXLINE];    for(;;){        len = clilen;        n = recvfrom(sockfd,mesg,MAXLINE,0,pcliaddr,&len);        sendto(sockfd,mesg,n,0,pcliaddr,len);    }}int main(int argc , char** argv) {    int sockfd = -1;    struct sockaddr_in servaddr;    struct sockaddr_in cliaddr;    sockfd = socket(AF_INET , SOCK_DGRAM , 0);    bzero(&servaddr , sizeof(servaddr));    servaddr.sin_family = AF_INET;    servaddr.sin_addr.s_addr = htonl(0);    servaddr.sin_port = htons(SERV_PORT);    bind(sockfd , (struct sockaddr*)&servaddr , sizeof(servaddr));    dg_echo(sockfd , (struct sockaddr*)&cliaddr , sizeof(cliaddr));    return 0;}
**//UDPClient代码**#include <iostream>#include <sys/socket.h>#include <sys/types.h>#include <string.h>#include <arpa/inet.h>#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <strings.h>using namespace std;#define MAXLINE     1024#define SERV_PORT   29988void dg_cli(FILE* fp,int sockfd,const SA* pservaddr,socklen_t servlen){    int n;    char sendline[MAXLINE];    char recvline[MAXLINE+1];    socklen_t len;    struct sockaddr* preply_addr;    preply_addr = (struct sockaddr*)malloc(servlen);    while(fgets(sendline,MAXLINE,fp)!=NULL){        sendto(sockfd,sendline,strlen(sendline),0,pservaddr,servlen);        len = servlen;        n = recvfrom(sockfd,recvline,MAXLINE,0,preply_addr,&len);        if(len!= servlen || memcmp(pservaddr,preply_addr,len)!=0){            continue;        }        recvline[n] = 0;        fputs(recvline,stdout);    }}int main(int argc , char** argv) {    int sockfd = -1;    struct sockaddr_in servaddr;    if(argc != 2){        cout << "usage:udpcli <IPaddress>" << endl;        exit(-1);    }    sockfd = socket(AF_INET , SOCK_DGRAM , 0);    bzero(&servaddr , sizeof(servaddr));    servaddr.sin_family = AF_INET;    servaddr.sin_port = htons(SERV_PORT);    inet_pton(AF_INET , argv[1],&servaddr.sin_addr);    dg_cli(stdin,sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));    return 0;}

注意:上述代码可能出现问题:
(1)如果客户端sendto的数据丢失(比如网络问题),则recvfrom一直阻塞。
(2)如果客户端sendto数据达到,但是服务器发出数据没有达到客户端,则客户端也将一直处于阻塞。

UDP产生ICMP异步错误

做如下测试:    (1)不启动UDPServer;    (2)开启tcpdump抓包:sudo tcpdump -i lo    (3)启动UDPClient:./UDPClient 127.0.0.1,发送数据。结果:    此时并没有发生回射,在tcpdump我们发现如下信息:    15:41:39.535289 IP localhost.50872 > localhost.29988: UDP, length 7    15:41:39.535300 IP localhost > localhost: ICMP localhost udp port 29988         unreachable, length 43    第一条显示本地端口50872向本地端口29988服务器端口发送数据,长度为7字节UDP    第二条显示回了一条ICMP包: udp port 29988 unreachable,端口不可达。总结:    服务并没有开启时会响应一个port unreachable的ICMP错误,但是该错误不会返回    给客户端,我们称这样错误为异步错误。产生异步错误原因:    该异步错误由sendto引起,UDP输出操作sendto仅仅表示在接口输出队列中具有存放数据报的空间,并不    代表发送成功。该ICMP错误是之后真正发送时才产生。解决异步错误的方法:    对于一个UDP套接字,由它引起的异步错误并不返回给它,除非它已经连接。在进程将其UDP套接字连接    到恰恰一个对端后,这些异步错误才返回给进程。

UDP的connect函数

udp使用connect函数必要性:    解决udp无法接受异步错误的问题。tcp和udp使用connect区别:    (1)没有tcp连接的三次握手协议    (2)内核只是检查是否存在一个立即可知的错误(例如上述的ICMP错误)    (3)内核记录对端的IP和port。    (4)立即返回。未连接的UDP套接字:使用socket创建UDP套接字默认如此。已连接的UDP套接字:调用connect结果。udp套接字调用connect影响:    (1)发送数据时不能再指定IP和Port。即不能使用sendto,改用write和send        任何写入该套接字的数据会自动发往coonect指定对端。    (2)不必再使用recvfrom接收数据,改用read,recv,recvmsg。    (3)引发的任何异步错误,会立即返回给当前进程。对一个udp多次调用connect:    (1)可以指定新的IP和Port    (2)把套接字地址结构地址族成员指定为AF_UNSPEC,则断开套接字。如果此时返回一个FAFNOSUPPORT    错误,可以忽略。

客户端connect接收异步错误

void dg_cli2(FILE* fp,int sockfd,const SA* pservaddr,socklen_t servlen){        int n;        char sendline[MAXLINE];        char recvline[MAXLINE+1];        connect(sockfd,(SA*)pservaddr,servlen);        while(fgets(sendline,MAXLINE,fp)!=NULL){            write(sockfd,sendline,strlen(sendline));            n = read(sockfd,recvline,MAXLINE);            if(n<0){                cout << strerror(errno) << endl;                return ;            }            recvline[n] = 0;            fputs(recvline,stdout);        }    }
调用connect指定对端IP和PORT;使用read替换recvfrom,使用write替换sendto。注意:如果服务器没有启动,在connect时并不会出错,当进行read时,则会返回错误:Connection refused。    如果使用的是TCP,则会进行三次握手,Connection refused则会在connect时就返回给进程。

UDP缺乏流量控制

测试实例:客户端不间断的向server发送大量数据,服务器不间断的接收数据。
    //客户端发送数据    #define NDG 2000    #define DGLEN   1400    void dg_cli(FILE* fp,int sockfd,const SA* pservaddr,socklen_t servlen){        int i;        char sendline[DGLEN];        for(i=0;i<NDG;i++){            sendto(sockfd,sendline,DGLEN,0,pservaddr,servlen);        }    }    //服务器不间断接收数据并统计    static int count;    void dg_echo(int sockfd,SA* pcliaddr,socklen_t clilen) {        socklen_t len;        char mesg[MAXLINE];        signal(SIGINT,recvfrom_int);        for(;;){            len = clilen;            recvfrom(sockfd,mesg,MAXLINE,0,pcliaddr,&len);            count++;        }    }    static void recvfrom_int(int signo){        cout<<"received " << count << " datagrams" << endl;        exit(0);    }
使用命令netstat -s -udp 可以查看系统网络数据包信息,如下:    Udp:        245306 packets received        402051 packets to unknown port received.        3712 packet receive errors        651115 packets sent        RcvbufErrors: 3712        IgnoredMulti: 1    可以查看接收到包,没有接收的包,接收错误的包,以及发送的包。测试结果:    Client不控制流量的发送,Server不间断的接收,有一定概率接收的包小于发送的包。    也就是说:有包丢失了。结论:    发送或者接收UDP套接字有一定缓冲区,当缓冲区已经满时再发送数据,会导致数据的丢失。    同时一般发送端速度较快,接收端较慢,UDP发送端淹没接收端是轻而易举的。设置UDP缓冲区大小:    int n = 220 * 1024;    setsockopt(sockfd,SOL_SOCKET,SO_RECBUF,&n,sizeof(n));
0 0
原创粉丝点击