udp编程

udp编程模型

最简单的服务端, 监听本机任意ip,指定端口为9002

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

#define BUFSIZE 1024
int main(int argc, char *argv[]) {

    int sockfd;
    struct sockaddr_in serv;

    if(argc < 2) {
        printf("invalid format, no port\n");
        return -1;
    }

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket");
        return -1;
    }
    memset(&serv, 0, sizeof(struct sockaddr_in));
    serv.sin_family = AF_INET;
    serv.sin_addr.s_addr = htonl(INADDR_ANY); // 本机任意一个地址
    serv.sin_port = htons(atoi(argv[1]));

    if( bind(sockfd, (struct sockaddr *)&serv, sizeof(serv)) < 0) {
        perror("bind");
        return -1;
    }

    while(1) {
        int n;
        char rcvbuf[BUFSIZE], cliaddr[16];
        struct sockaddr_in client;
        socklen_t len = sizeof(client);

        n = recvfrom(sockfd, rcvbuf, BUFSIZE, 0 , (struct sockaddr *)&client, &len);
        if (n > 0) {
            rcvbuf[n] = 0;
            inet_ntop(AF_INET, &client.sin_addr.s_addr, cliaddr, sizeof(cliaddr));

            printf("recvfrom %s:%d content: %s", cliaddr, ntohs(client.sin_port), rcvbuf);
            sendto(sockfd, rcvbuf, n, 0, (struct sockaddr *)&client, len);
        }
    }
    return 0;
}

启动方法为 server 9002

最简单的客户端

未使用connect

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <error.h>
#include <stdlib.h>

#define MAXLINE 1024

int main(int argc, char *argv[]) {
    int sockfd, sport;
    struct sockaddr_in serv;
    char *serv_ip = 0;

    if(argc < 3) {
        printf("please checkout format\n");
        return -1;
    }
    serv_ip = argv[1];
    sport = atoi(argv[2]);

    if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket");
        return -1;
    }

    memset(&serv, 0, sizeof(struct sockaddr_in));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(sport);
    inet_pton(AF_INET, serv_ip, &serv.sin_addr);

    int n;
    char sendline[MAXLINE], recvline[MAXLINE];

    while(fgets(sendline, MAXLINE, stdin) != NULL) {
        n = sendto(sockfd, sendline, strlen(sendline), 0, (struct sockaddr *)&serv, sizeof(serv));
        if (n < 0) {
            perror("sendto");
            break;
        }
        n = recvfrom(sockfd, recvline, MAXLINE, 0, 0, 0);
        if ( n < 0 ) {
            perror("recvfrom");
            break;
        }
        recvline[n] = '\0';
        fputs(recvline, stdout);
    }
    return 0;
}

使用: client 127.0.0.1 9002

n = recvfrom(sockfd, recvline, MAXLINE, 0, 0, 0); 这条语句存在风险,任何进程不论是在与本客户进程相同的主机上还是在不同的主机上,都可以向本客户的IP地址和端口发送数据报。

于是在recvfrom时获取发送端的地址和端口 必须与 设定的连接的服务端地址和端口一致的判断:

 n = recvfrom(sockfd, recvline, MAXLINE, 0, (struct sockaddr *)&saddr, &len);
        if ( n < 0 ) {
            perror("recvfrom");
            break;
        }
        if(memcmp(&saddr, &serv, sizeof(struct sockaddr_in)) != 0) {
            printf("reply from %s (ignored) \n", inet_ntop(AF_INET, &saddr.sin_addr.s_addr, caddr, 16));
            continue;
       }

在多ip的主机启动,外出接口主ip为 172.17.147.135, 第二个IP为 172.18.0.1 , 得出测试结果如下所示:

[root@iz2zecj7a5r32f2axsctb9z echo2]# ./client 172.18.0.1 9002
hehe
reply from 172.17.147.135:9002 (ignored) 

出现客户端接收的目标源地址和设定的连接服务端的地址不一样,导致失败。

原因服务端没有绑定实际ip时,内核将为封装这些应答的IP数据报选择源地址,选为源地址的是外出接口的主IP地址

服务器进程未运行,只启动客户端的情况

tcpdump -i lo -vXle port 9002 and udp or icmp

只启动client

由图可见,当向一个没有绑定的端口服务发送udp时,会返回一个端口不可达的差错报文, 客户端将一直阻塞在recvfrom调用处。

基本规则:对于一个UDP套接字, 由它引发的异步错误并不反回给它,除非它已经连接。

客户端,使用connect

  • 使用connect后 peeraddr已经确定,则不能给输出操作指定目的ip地址和端口号。 该用write、send。 写到已连接套接字的任何内容都自动发送到由connect指定的协议地址(IP地址、端口)。使用sendto就不能指定目的地址,也就是第5个参数为NULL,第6个参数为0;
  • 不必使用recvfrom来获取数据报的发送者,该用read\recv\recvmsg。 在一个已经连接的UDP套接上,由内核为输入操作返回的数据报只有那些来自connect所指定协议地址的数据报。
  • 由已连接UDP套接字引发的异步错误会返回给所在进程,而未连接UDP套接字不接收任何异步错误
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <error.h>
#include <stdlib.h>
#include <unistd.h>

#define MAXLINE 1024
/*添加消息返回的来源判断*/

int main(int argc, char *argv[]) {
    int sockfd, sport;
    struct sockaddr_in serv;
    char *serv_ip = 0;

    if(argc < 3) {
        printf("please checkout format\n");
        return -1;
    }
    serv_ip = argv[1];
    sport = atoi(argv[2]);

    if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket");
        return -1;
    }

    memset(&serv, 0, sizeof(struct sockaddr_in));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(sport);
    inet_pton(AF_INET, serv_ip, &serv.sin_addr);

    if(connect(sockfd, (struct sockaddr *)&serv, sizeof(serv)) < 0) {
        perror("connect");
        return -1;
    }

    int n;
    char sendline[MAXLINE], recvline[MAXLINE];

    while(fgets(sendline, MAXLINE, stdin) != NULL) {

        n = write(sockfd, sendline, strlen(sendline));

        if (n < 0) {
            perror("send");
            break;
        }

        n = read(sockfd, recvline, MAXLINE);
        if ( n < 0 ) {
            perror("recvfrom");
            break;
        }
        recvline[n] = '\0';
        fputs(recvline, stdout);
    }
    return 0;
}

服务端未开启结果:

[root@iz2zecj7a5r32f2axsctb9z echo2]# ./client_connect 172.18.0.1 9003
this is a test
recvfrom: Connection refused

会返回异步icmp错误

文档更新时间: 2021-02-06 16:05   作者:周国强