LINUX下UDP网络编程 read()/write()与recv()/send()与sendto()/recvfrom()

1. read()/write()与recv()/send()与sendto()/recvfrom()

1.1 read()/write() :

ssize_t read(int fd, void *buf, size_t count);

ssize_t write(int fd, const void *buf, size_t count);

read()和write()经常会代替recv()和send(),通常情况下,看程序员的偏好。
使用read()/write()和recv()/send()时最好统一。

1.2 recv()/send() :

1.2.1 send

send:是一个系统调用函数,用来发送消息到一个套接字中,和write,sendto功能相似。

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

buffer : 发送缓冲区首地址
length : 发送的字节数
flags : 发送方式(通常为0)

返回值:
成功:实际发送的字节数
失败:-1, 并设置errno

    // 下面的两个函数等效:
    write(sockfd,buf,le);
    send(sockfd,buf,len,0);
    ssize_t类型在之前的read/write中描述符哦,相当于long。

    sockfd:接收消息的套接字的文件描述符。

    buf:要发送的消息。

    len:要发送的字节数。

    flags:flags参数表示下列标志中的0个或多个

    MSG_CONFIRM :用来告诉链路层,

    MSG_DONTROUTE:不要使用网关来发送数据,只发送到直接连接的主机上。通常只有诊断或者路由程序会使用,
                    这只针对路由的协议族定义的,数据包的套接字没有。

    MSG_DONTWAIT :启用非阻塞操作,如果操作阻塞,就返回EAGAIN或EWOULDBLOCK  //常用

    MSG_EOR :当支持SOCK_SEQPACKET时,终止记录。

    MSG_MORE :调用方有更多的数据要发送。这个标志与TCP或者udp套接字一起使用

    MSG_NOSIGNAL :当另一端中断连接时,请求不向流定向套接字上的错误发送SIGPIPE ,EPIPE 错误仍然返回。

    MSG_OOB:在支持此概念的套接字上发送带外数据(例如,SOCK_STREAM类型);底层协议还必须支持带外数据    //常用

1.2.2 recv

recv:是一个系统调用函数,用来发送消息到一个套接字中,和write,sendto功能相似。

    #include <sys/types.h>
    #include <sys/socket.h>

    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    返回值:
        成功:实际接收的字节数
        失败:-1, 并设置errno
    
    sockfd : 指定接收端套接字描述符
    buffer : 接收缓冲区首地址
    length : 接收的字节数
    flags : 接收方式(通常为0)

1.3 sendto(),recvfrom() :

这两个函数一般在使用UDP协议时使用

1.3.1 sendto()

    #include <sys/types.h>
    #include <sys/socket.h>
    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                const struct sockaddr *dest_addr, socklen_t addrlen);

返回值:
成功:实际发送的字节数
失败:-1, 并设置errno

sendto函数如果在连接状态(SOCK_STREAM, SOCK_SEQPACKET)下使用,后面的两个参数dest_addr和addrlen被忽略的,如果不把他们设置成null和0,
会返回错误EISCONN,如果把他们设置成NULL和0,发现实际上不是连接状态,会返回错误EISCONN。正常连接下,它和send同等。

    //下面的两个也是等效的:
    send(sockfd, buf, len, flags);
    sendto(sockfd, buf, len, flags, NULL, 0);

其他情况下,目标地址是由dest_addr给出,addrlen指定其大小。

1.3.2 recvfrom()

   #include <sys/types.h>
   #include <sys/socket.h>

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

2. 套接字的关闭

2.1 close()函数

#include <unistd.h>

int close(int fd);
用途:关闭读写通信。

2.2 shutdown()函数

#include <sys/socket.h>

int shutdown(int sockfd, int how);

3. UDP编程 API

UDP循环服务器模型:

socket(...);

bind(...);

while(1)
{
   recvfrom(...);
   process(...);
   sendto(...);
}

3.1 UDP编程 server 范例

#include <cstdio>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>

#define SERV_PORT 5050
#define SERV_IP_ADDR "192.168.0.158"

int main()
{
    int fd = -1;
    int result = 0;
    struct sockaddr_in mysockaddr;
    struct sockaddr_in clientscokaddr;
    socklen_t clientaddrlen;
    char botton[64];

    /* 1. 创建socket fd */
    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        perror("socket");
        return -1;
    }

    /*优化4: 允许绑定地址快速重用 */
    int b_reuse = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int));

    /*2. 绑定 */
    /*2.1 填充struct sockaddr_in结构体变量 */
    bzero((void*)&mysockaddr, sizeof(mysockaddr));
    mysockaddr.sin_family = AF_INET;
    mysockaddr.sin_port = htons(SERV_PORT);//网络字节序的端口号
    mysockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    /*2.2 绑定 */
    result = bind(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr));
    if (result != 0) {
        perror("bind");
        return -1;
    }

    /*3. 调用recvfrom 接收客户端发来的信息 */
    /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址和端口号*/
    int fd_client;

    pid_t pid;
    clientaddrlen = sizeof(clientscokaddr);
    
    while (1) {
        bzero((void*)botton, 64);
        bzero((void*)&clientscokaddr, clientaddrlen);
       
        do {
            result = recvfrom(fd, botton, 63, 0, (struct sockaddr*)&clientscokaddr, &clientaddrlen);
        } while (result < 0 && EINTR == errno);

        if (-1 == result) {
            perror("recvfrom");
            break;
        }

        char ipv4_addr[16];
        if (!inet_ntop(AF_INET, (void*)&clientscokaddr.sin_addr, ipv4_addr, clientaddrlen)) {
            perror("inet_ntop");
            return -1;
        }

        printf("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs(clientscokaddr.sin_port));
       

        printf("message is:%s\n", botton);
    }

    close(fd);
    return 0;
}

3.2 UDP编程 client 范例

#include <cstdio>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>

#define DEBUG 0

void usage(char* s) {
    printf("\nThis is udp demo!\n");
    printf("\nUsage:\n\t %s serv_ip serv_port", s);
    printf("\n\t serv_ip: udp server ip address");
    printf("\n\t serv_port: udp server port(serv_port > 5000)\n\n");
}

int main(int argc, char* argv[])
{
    int fd = -1;
    int result = 0;
    int SERV_PORT = -1;
    char* SERV_IP_ADDR = NULL;
    struct sockaddr_in mysockaddr;
    char MESSAGE[] = "hELLo wOLd!!!\n";

    /* 在启动程序时 要加上IP 端口,如:test.out 192.168.1.31 5001 从而提高程序通用性*/
    /* 下面的程序是用来检测输入的参数是否合法 */

#if DEBUG
    SERV_PORT = 5050;
    SERV_IP_ADDR = "192.168.0.158";
#else
    if (argc != 3) {
        usage(argv[0]);
        return -1;
    }

    SERV_PORT = atoi(argv[2]);
    if (SERV_PORT <= 5000) {
        usage(argv[0]);
        return -1;
    }
    SERV_IP_ADDR = argv[1];
#endif
    /* 1. 创建socket fd */
    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        perror("socket");
        return -1;
    }
    /*2.连接服务器 写入数据 */

    /*2.1 填充struct sockaddr_in结构体变量 */
    bzero((void*)&mysockaddr, sizeof(mysockaddr));
    mysockaddr.sin_family = AF_INET;
    mysockaddr.sin_port = htons(SERV_PORT); //网络字节序的端口号
#if 0    
    mysockaddr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
#else
    result = inet_pton(AF_INET, SERV_IP_ADDR, (void*)&mysockaddr.sin_addr);
    if (result != 1) {
        perror("inet_pton");
        return -1;
    }
#endif
    /*2.2  读写数据*/
    printf("Client staring...OK!\n");

    while (1) {
        do {
            result = sendto(fd, MESSAGE, strlen(MESSAGE), 0, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr));
        } while (result < 0 && EINTR == errno);   //判断是否阻塞了     
        if (result < 0) {
            perror("connect");
            break;
        }
        sleep(10);
    }

    close(fd);
    return 0;
}

3.3 UDP编程 server 改进版 范例

上一个server代码通过轮询的方式实现了接收/回复数据,但是有个问题,
当处理过程复杂 需要一段时间才能处理完的的时候,则会造成资源浪费无法快速响应等问题。
为了解决这个问题,可以采用resv-process结构,即:接收采用轮询,处理则创建进程的方式来解决问题。

#include <cstdio>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>

#define SERV_PORT 5050
#define SERV_IP_ADDR "192.168.0.158"

int fun_process(int fd,char* botton,sockaddr_in clientscokaddr, socklen_t clientaddrlen);
void handler(int signo) {   //自动回收子进程 防止僵尸进程

    if (signo == SIGCHLD)
    {
        printf("Recall SIGINT\n");
        waitpid(-1, NULL, WNOHANG);
    }
}

int main()
{
    int fd = -1;
    int result = 0;
    pid_t pid;
    struct sockaddr_in mysockaddr;
    struct sockaddr_in clientscokaddr;
    socklen_t clientaddrlen;
    char botton[64];

    /* 1. 创建socket fd */
    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        perror("socket");
        return -1;
    }

    /*优化4: 允许绑定地址快速重用 */
    int b_reuse = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int));

    /*2. 绑定 */
    /*2.1 填充struct sockaddr_in结构体变量 */
    bzero((void*)&mysockaddr, sizeof(mysockaddr));
    mysockaddr.sin_family = AF_INET;
    mysockaddr.sin_port = htons(SERV_PORT);//网络字节序的端口号
    mysockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    /*2.2 绑定 */
    result = bind(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr));
    if (result != 0) {
        perror("bind");
        return -1;
    }

    /*3. 调用recvfrom 接收客户端发来的信息 */
    /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址和端口号*/
    clientaddrlen = sizeof(clientscokaddr);
    signal(SIGCHLD, handler);

    while (1) {
        bzero((void*)botton, 64);
        bzero((void*)&clientscokaddr, clientaddrlen);
       
        do {
            result = recvfrom(fd, botton, 63, 0, (struct sockaddr*)&clientscokaddr, &clientaddrlen);
        } while (result < 0 && EINTR == errno);

        if (-1 == result) {
            perror("recvfrom");
            break;
        }
        pid = fork();
        if (pid < 0) {
            perror("fork");
            break;
        }
        else if (0 == pid) {
            result = fun_process(fd, botton,clientscokaddr, clientaddrlen);
            close(fd);
            return result;
        }     
    }

    close(fd);
    return 0;
}

int fun_process(int fd, char* botton,sockaddr_in clientscokaddr, socklen_t clientaddrlen) {
    char ipv4_addr[16];
    int result;
    char botton_re[64] = "recv success!";
    printf("23333\n");
    if (!inet_ntop(AF_INET, (void*)&clientscokaddr.sin_addr, ipv4_addr, clientaddrlen)) {
        perror("inet_ntop");
        return -1;
    }

    printf("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs(clientscokaddr.sin_port));

    printf("message is:%s\n", botton);
    do {
        result = sendto(fd, botton_re, strlen(botton_re), 0, (struct sockaddr*)&clientscokaddr, sizeof(clientscokaddr));
    } while (result < 0 && EINTR == errno);   //判断是否阻塞了     
    if (result < 0) {
        perror("connect");
        return -1;
    }
    
    return 0;
}

3.4 UDP编程 client 改进版 范例

与server改进版 相对应

#include <cstdio>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>

#define DEBUG 0

void usage(char* s) {
    printf("\nThis is udp demo!\n");
    printf("\nUsage:\n\t %s serv_ip serv_port", s);
    printf("\n\t serv_ip: udp server ip address");
    printf("\n\t serv_port: udp server port(serv_port > 5000)\n\n");
}

int main(int argc, char* argv[])
{
    int fd = -1;
    int result = 0;
    int SERV_PORT = -1;
    char* SERV_IP_ADDR = NULL;
    struct sockaddr_in mysockaddr;
    char MESSAGE[] = "hELLo wOLd!!!\n";
    char botton[64] = {0};
#if DEBUG
    SERV_PORT = 5050;
    SERV_IP_ADDR = "192.168.0.158";
#else
    if (argc != 3) {
        usage(argv[0]);
        return -1;
    }

    SERV_PORT = atoi(argv[2]);
    if (SERV_PORT <= 5000) {
        usage(argv[0]);
        return -1;
    }
    SERV_IP_ADDR = argv[1];
#endif
    /* 1. 创建socket fd */
    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        perror("socket");
        return -1;
    }
    /*2.连接服务器 写入数据 */

    /*2.1 填充struct sockaddr_in结构体变量 */
    bzero((void*)&mysockaddr, sizeof(mysockaddr));
    mysockaddr.sin_family = AF_INET;
    mysockaddr.sin_port = htons(SERV_PORT); //网络字节序的端口号
#if 0    
    mysockaddr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
#else
    result = inet_pton(AF_INET, SERV_IP_ADDR, (void*)&mysockaddr.sin_addr);
    if (result != 1) {
        perror("inet_pton");
        return -1;
    }
#endif
    /*2.2  读写数据*/
    printf("Client staring...OK!\n");
    struct sockaddr_in clientscokaddr;
    socklen_t clientaddrlen;
    clientaddrlen = sizeof(clientscokaddr);

    while (1) {
        do {
            result = sendto(fd, MESSAGE, strlen(MESSAGE), 0, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr));
        } while (result < 0 && EINTR == errno);   //判断是否阻塞了     
        if (result < 0) {
            perror("connect");
            break;
        }
        
        sleep(1);

        bzero((void*)botton, 64);
        bzero((void*)&clientscokaddr, clientaddrlen);
        
        do {
            result = recvfrom(fd, botton, 63, MSG_DONTWAIT, (struct sockaddr*)&clientscokaddr, &clientaddrlen);
        } while (result < 0 && EINTR == errno);
        printf("message is:%s\n", botton);
        
    }

    close(fd);
    return 0;
}