浅墨浓香

想要天亮进城,就得天黑赶路。

导航

第14章 UDP编程(1)_UDP客户端服务器模型

Posted on 2017-04-06 11:25  浅墨浓香  阅读(385)  评论(0编辑  收藏  举报

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\n");
        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)\n", 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\n", 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\n", 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\n", 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]# 
 */