zoukankan      html  css  js  c++  java
  • 第14章 UDP编程(1)_UDP客户端服务器模型

    1. UDP编程模型

    (1)UDP客户端服务器模型

     

      ①客户端可以不调用bind()而直接与服务器通讯。

      ②UDP是无连接的,因此服务端不需要调用accept和listen客户端也无需调用connect函数

    (2)数据传输

      ①发送数据

    头文件

    #include <sys/socket.h>

    函数

    ssize_t send(int sockfd, const void* buf, size_t nbytes, int flag);

    ssize_t send(int sockfd, const void* buf, size_t nbytes, int flag,

    const struct sockaddr* destaddr, socklen_t destlen);

    ssize_t sendmsg(int sockfd, const struct msghdr* msg, int flag);

    参数

    struct msghdr{
        void* msg_name; //optional address
        socklen_t msg_namelen; //address size in bytes
        struct iovec* msg_iov;    //array of I/O buffers
        int msg_iovlen;              //number of elements in array
        void* msg_control;         //ancillary data
        socklen_t msg_controllen; //number of ancillary bytes;
        int msg_flags;                //flags for received message
    };

    功能

    发送数据

    返回值

    返回:成功返回发送字节数,出错返回-1。

      ②接收数据

    头文件

    #include <sys/socket.h>

    函数

    ssize_t recv(int sockfd, const void* buf, size_t nbytes, int flag);

    ssize_t recvfrom(int sockfd, const void* buf, size_t nbytes, int flag, const struct sockaddr* addr, socklen_t addrlen);

    ssize_t sendmsg(int sockfd, const struct msghdr* msg, int flag);

    参数

    与send*函数类似

    功能

    接收数据

    返回值

    返回消息的字节数,无消息返回0,出错返回-1。

    【编程实验】利用UDP获取服务器的当前时间

    //time_udp_server.c

    #include <sys/socket.h>
    #include <netdb.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <signal.h>
    #include <time.h>
    #include <memory.h>
    
    int sockfd;
    
    void sig_handler(int signo)
    {
        if(signo == SIGINT){
            printf("server close
    ");
            close(sockfd);
            exit(1);
        }
    }
    
    //输出客户端的信息
    void out_addr(struct sockaddr_in* addr)
    {
        char ip[16];
        int port = 0;
        memset(ip, 0, sizeof(ip));
        inet_ntop(AF_INET, &addr->sin_addr.s_addr, ip, sizeof(ip));
        port = ntohs(addr->sin_port);
    
        printf("client: %s(%d)
    ", ip, port);
    }
    
    //与客户端进行通信
    void do_service()
    {
        struct sockaddr_in cliAddr;
        socklen_t len = sizeof(cliAddr);
        char buff[1024];
        memset(buff, 0, sizeof(buff));
    
        //接受客户端的数据报文
        //使用recvfrom而不使用recv函数的主要目的是为了获取客户端信息
        if(recvfrom(sockfd, buff, sizeof(buff), 0, 
                    (struct sockaddr*)&cliAddr, &len) < 0){
            perror("recvfrom error");
        }else{
            out_addr(&cliAddr);
            printf("client send info: %s
    ", buff);
    
            //向客户端发送数据报文
            long int t = time(0);
            char* ptr = ctime(&t);
            size_t size = strlen(ptr) * sizeof(char);
            if(sendto(sockfd, ptr, size, 0,(struct sockaddr*)&cliAddr, len) < 0){
                perror("sendto error"); //cliAddr己经从recvfrom那里得到了客户端的信息
            }
        }
    }
    
    int main(int argc, char* argv[])
    {
        if(argc < 2){
            printf("usage: %s port
    ", argv[0]);
            exit(1);
        }
        
        //注册ctrl-c信号处理函数
        if(signal(SIGINT, sig_handler) == SIG_ERR){
            perror("signal sigint error");
            exit(1);
        }
    
        /*步骤1: 创建socket*/
        sockfd = socket(AF_INET, SOCK_DGRAM, 0); //SOCK_DGRAM为UDP协议
        if(sockfd < 0){
            perror("socket error");
            exit(1);
        }
    
        //设置socket的相关选项
        int ret;
        int opt = 1;//1表示启动该选项
        //设置为可重新使用端口,每次启动该端口时,原来对该端口使用将失效
        if((ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) < 0){
            perror("setsockopt error");
            exit(1);
        }
    
        /*步骤2: 调用bind函数将socket和地址进行绑定*/
        struct sockaddr_in servAddr;
        memset(&servAddr, 0, sizeof(servAddr));
        servAddr.sin_family = AF_INET;  //IPv4
        servAddr.sin_port = htons(atoi(argv[1])); //port
        servAddr.sin_addr.s_addr = INADDR_ANY;  //ip
        if(bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0){
            perror("bind error");
            exit(1);
        }
    
        /*步骤3:与客户端进行双向的数据通信*/
        while(1){
            do_service();
        }
    
        return 0;
    }
    /* 输出结果
     * [root@localhost 14.udp]# gcc -o bin/time_udp_client src/time_udp_client.c 
     * [root@localhost 14.udp]# bin/time_udp_server 8888                         
     * client: 127.0.0.1(48929)
     * client send info: hello world!
     * client: 127.0.0.1(32953)
     * client send info: hello world!
     * ^Cserver close
     * [root@localhost 14.udp]#
     */

    //time_udp_client.c

    #include <sys/socket.h>
    #include <netdb.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <memory.h>
    
    int main(int argc, char* argv[])
    {
        if(argc < 3){
            printf("usage: %s ip port
    ", argv[0]);
            exit(1);
        }
    
        /*步骤1:创建socket*/
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(sockfd < 0){
            perror("socket error");
            exit(1);
        }
    
        /*步骤2: 调用recvfrom和sendto等函数和服务端双向通信*/
        struct sockaddr_in servAddr; //封装服务器的地址信息
        memset(&servAddr, 0, sizeof(servAddr));
        servAddr.sin_family = AF_INET; //IPv4
        servAddr.sin_port = htons(atoi(argv[2]));//端口
        inet_pton(AF_INET, argv[1], &servAddr.sin_addr.s_addr);
     
        //在UDP能否调用connect来与服务端连接?
        //TCP中调用connect会经过三次握手,建立起双方的连接。
        //但UDP中调用connect并没有建立真正的连接,而是在内核中记录了通讯双方的地址信息(如IP、por)
        //当UDP中调用了connect后,以后可以直接使用send而不必使用sendto来发送消息给对方。
        //此外,还有一个好处就是因为sock记录了客户端的IP,使用该sockfd可以只接收指定服务器发来的消息,而不会
        //接收除服务器以外其他地方发来的消息。
        if(connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0){
            perror("connect error");
            exit(1);
        }
    
        char buff[1024] = "hello world!";
        //向服务器发送数据报文
        if((sendto(sockfd, buff, sizeof(buff), 0, (struct sockaddr*)&servAddr, sizeof(servAddr))) < 0){
            perror("sendto error");
            exit(1);
        }else{
            //接受服务器端的数据报文
            memset(buff, 0, sizeof(buff));
            size_t size;
            
            //1.为什么recv里没有指定服务器地址,却可以发送成功?
            //因为如果之前的sendto发送成功,则sockfd(是个结构体)里将保存通讯双方的信息,这里就可以直接使用这个sockfd来通讯
            //2.为什么不需要判断recv的返回值为0?(0表示对方己关闭连接)
            //因为UDP是无连接的通信,通信双方是没有建立连接的,数据被传到链路层以后发送方就可以关闭,因此这里不需判断是否为0.
            if((size = recv(sockfd, buff, sizeof(buff), 0)) < 0){
                perror("recv error");
                exit(1);
            }else{
                printf("%s", buff);
            }
        }
    
        close(sockfd);
    
        return 0;
    }
    /*输出结果
     * [root@localhost 14.udp]# bin/time_udp_client 127.0.0.1 8888          
     * Sat Mar 18 10:01:09 2017
     * [root@localhost 14.udp]# bin/time_udp_client 127.0.0.1 8888
     * Sat Mar 18 10:01:12 2017
     * [root@localhost 14.udp]# 
     */
  • 相关阅读:
    基于Appium的自动化case开发及case分层结构设计
    功能自动化接入持续集成方案
    Windows上部署Appium自动化测试框架
    Mac上部署Appium测试框架
    Appium原理简述
    开篇
    数据结构和算法动态可视化
    Request实现简易注册登录
    过滤器解决中文乱码
    简易登录拦截(没有登录前直接访问主页则跳转到登录页)
  • 原文地址:https://www.cnblogs.com/5iedu/p/6672623.html
Copyright © 2011-2022 走看看