TCP/IP网络编程学习笔记(四)基于UDP的服务器端/客户端

时间:2019-04-15
本文章向大家介绍TCP/IP网络编程学习笔记(四)基于UDP的服务器端/客户端,主要包括TCP/IP网络编程学习笔记(四)基于UDP的服务器端/客户端使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

一、概念

1.UDP套接字的特点

  • 不可靠的数据传输
  • 没有流控制

2.UDP的内部工作原理

  • 根据端口号将传到主机的数据包交付给最终的 UDP 套接字

3.UDP的使用场合

  • 实时传输视频或音频

二、相关函数

  • sendto 函数
#include<sys/socket.h>
// 功能:发送数据
// 参数:
//    sock--用于传输数据的 UDP 套接字文件描述符
//    buff--保存待传输数据的缓冲地址值
//    nbytes--待传输的数据长度
//    flags--可选参数,默认传递 0
//    to--存有目标地址信息的 sockaddr 结构体变量的地址值
//    addrlen--传递给参数 to 的地址值结构体变量长度
// 返回值:成功时返回传输的字节数,失败时返回 -1
ssize_t sendto(int sock,void* buff,size_t bytes,int flags,struct sockaddr* to,socklen_t addrlen);
  • recvfrom 函数
#include<sys/socket.h>
// 功能:接收数据
// 参数:
//    sock--用于接收数据的 UDP 套接字文件描述符
//    buff--保存接收数据的缓冲地址值
//    nbytes--可接收的最大字节数,故无法超过 buff 所指的缓冲大小
//    flags--可选参数,默认为 0
//    from--存有发送端地址信息的 sockaddr 结构体变量的地址值
//    addrlen--保存参数 from 的结构体变量长度的变量地址值
// 返回值:成功时返回接收的字节数,失败时返回 -1
ssize_t recvfrom(int sock,void* buff,size_t nbytes,int flags,struct sockaddr* from,socklen_t* addrlen);

三、基于UDP的回声服务器端/客户端

  • 服务器端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUF_SIZE 30

void error_handling(char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char* argv[]) {
    int serv_sock;
    struct sockaddr_in serv_addr, clnt_addr;
    char message[BUF_SIZE];
    
    if (argc != 2) {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (serv_sock == -1)
        error_handling("UDP socket creation error");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));

    if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
        error_handling("bind() error");

    while (1) {
        socklen_t clnt_addr_size = sizeof(clnt_addr);
        int str_len = recvfrom(serv_sock, message, BUF_SIZE, 0, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
        sendto(serv_sock, message, str_len, 0, (struct sockaddr*)&clnt_addr, clnt_addr_size);
    }

    close(serv_sock);
    return 0;
}
  • 客户端
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUF_SIZE 30

void error_handling(char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char* argv[]) {
    int sock;
    char message[BUF_SIZE];

    struct sockaddr_in serv_addr, from_addr;
    if (argc != 3) {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock == -1)
        error_handling("socket() error");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    while (1) {
        fputs("Insert message(Q to quit): ", stdout);
        fgets(message, sizeof(message), stdin);
        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;

        // 调用 sendto 函数时自动分配IP地址和端口号
        sendto(sock, message, strlen(message), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
        socklen_t addr_size = sizeof(from_addr);
        int str_len = recvfrom(sock, message, BUF_SIZE, 0, (struct sockaddr*)&from_addr, &addr_size);
        message[str_len] = 0;
        printf("Message from server: %s", message);
    }

    close(sock);
    return 0;
}
  • 运行结果:

四、UDP套接字存在数据边界

  • bound_host1.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUF_SIZE 30

void error_handling(char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char* argv[]) {
    int sock;
    struct sockaddr_in my_addr, your_addr;
    char message[BUF_SIZE];

    if (argc != 2) {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock == -1)
        error_handling("socket() error");

    memset(&my_addr, 0, sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    my_addr.sin_port = htons(atoi(argv[1]));

    if (bind(sock, (struct sockaddr*)&my_addr, sizeof(my_addr)) == -1)
        error_handling("bind() error");

    for (int i = 0; i < 3; i++) {
        sleep(3);
        socklen_t addr_size = sizeof(your_addr);
        int str_len = recvfrom(sock, message, BUF_SIZE, 0, (struct sockaddr*)&your_addr, &addr_size);
        printf("Message %d: %s \n", i + 1, message);
    }

    close(sock);
    return 0;
}
  • bound_host2.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUF_SIZE 30

void error_handling(char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char* argv[]) {
    int sock;
    char msg1[] = "Hi!";
    char msg2[] = "I am another UDP host!";
    char msg3[] = "Nice to meet you!";

    struct sockaddr_in your_addr;
    socklen_t your_addr_size;

    if (argc != 3) {
        printf("Usage: %s <IP>  <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock == -1)
        error_handling("socket() error");

    memset(&your_addr, 0, sizeof(your_addr));
    your_addr.sin_family = AF_INET;
    your_addr.sin_addr.s_addr = inet_addr(argv[1]);
    your_addr.sin_port = htons(atoi(argv[2]));

    sendto(sock, msg1, sizeof(msg1), 0, (struct sockaddr*)&your_addr, sizeof(your_addr));
    sendto(sock, msg2, sizeof(msg2), 0, (struct sockaddr*)&your_addr, sizeof(your_addr));
    sendto(sock, msg3, sizeof(msg3), 0, (struct sockaddr*)&your_addr, sizeof(your_addr));

    close(sock);
    return 0;
}
  • 运行结果:

五、已连接UDP套接字

  • 要与同一主机进行长时间通信,将UDP套接字转换为已连接套接字会提高效率,即调用 connect 函数注册IP地址和端口号,sendto,recvfrom 函数改用 write,read 函数
  • uecho_con_client.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUF_SIZE 30

void error_handling(char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char* argv[]) {
    int sock;
    struct sockaddr_in serv_addr;
    char message[BUF_SIZE];

    if (argc != 3) {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock == -1)
        error_handling("socket() error");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    while (1) {
        fputs("Insert message(Q to quit): ", stdout);
        fgets(message, sizeof(message), stdin);
        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;

        write(sock, message, strlen(message));
        int str_len = read(sock, message, sizeof(message) - 1);
        message[str_len] = 0;
        printf("Message from server: %s", message);
    }

    close(sock);
    return 0;
}
  • 运行结果:

参考书籍:《TCP/IP网络编程》尹圣雨 著,金果哲 译