首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >通过网络格式字符串构造sockaddr_storage初始化

通过网络格式字符串构造sockaddr_storage初始化
EN

Code Review用户
提问于 2023-02-23 18:42:06
回答 2查看 549关注 0票数 8

我正在用C编写一个网络功能包装库,希望让用户用一个格式字符串初始化一个结构sockaddr_storage。

格式列表:"proto:host:port/servicename“示例:"tcp:localhost:8080","udp:8.8.8.8:dns”

可能的协议串: tcp,tcp4,tcp6,udp,udp4,udp6

我现在有一些问题。

  • 我习惯了很多评论吗?
  • 我经常注册变量吗?
  • 这段代码可读吗?
  • 你看到我可以减少的部分代码了吗?

我得到了以下时间:-查看我的主要功能

代码语言:javascript
运行
复制
2:2001::124:2144:153:80 - 0.000163 ms
3:172.217.16.195:80 - 0.000135 ms
0:127.0.0.1:8080 - 0.000013 ms
0:127.129.41.24:463 - 0.000005 ms

为什么时间过得很快?

代码:

代码语言:javascript
运行
复制
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <time.h>

#include <netinet/ip.h>
#include <arpa/inet.h>
#include <netdb.h>

typedef enum {tcp, tcp4, tcp6, udp, udp4, udp6} network_id;

typedef struct Addr {
    network_id proto;       /* protocol (can be): tcp, tcp4, tcp6, udp, udp4, udp6, _unix (unix), unixgram, unixpacket */
    char addr[40];          /* string containng either ipv4 or ipv6 address */
    int port;               /* port number */
} Addr;


#ifndef strncpy_s
int strncpy_s(char *dest, size_t destsz, const char *src, size_t n)
{
    if(dest == NULL)
        return -1;

    while(destsz-- && n-- && *src != '\0')
        *dest++ = *src++;
    *dest = '\0';

    return 0;
}
#endif

/* compare functions that compared until delimiter is found either in str1 or in str2 */
int compare_until_delim(const char *str1, const char *str2, char delim)
{
    /* *str1 != delim xor *str2 == 0 and *str2 != delim xor *str2 == 0 */
    while((!(*str1 != delim) != !(*str1 == '\0')) && (!(*str2 != delim) != !(*str2 == '\0'))) {
        if(*str1 != *str2)
            return false;
        str1++;
        str2++;
    }

    if(*str1 != *str2)
        return false;

    return true;
}

/* protocol table containing supported protocol for socket configuration */
const char *protocol_table[] = 
{
    "tcp:",
    "tcp4:",
    "tcp6:",
    "udp:",
    "udp4:",
    "upd6:",
};

/* simple compare until dilimiter check for the protocol table above */
int check_protocol(const char *str)
{
    int i, table_size;
    table_size = sizeof(protocol_table) / sizeof(*protocol_table);

    for(i = 0; i < table_size ; i++) {
        if(compare_until_delim(protocol_table[i], str, ':'))
            return i;
    }
    return -1; /* not found */
}
 
/** ipstr_to_addr - initializes struct Addr and struct sockaddr_in by string containing specific ip address format
 *  return@int:         (0 on success, -2 = inet_pton error, -1 = parsing error at start index 0, 1...n = error at index)
 *  param@ipstr:        format string like format "proto:host:service" 
 *  param@addr:         struct Addr (internal use) (contains printable addr infos, except protocol) if NULL only the _sockaddr_storage
 *  param@_sockaddr_in: struct sockaddr_in, will be setup regarding to the format string
*/
int ipstr_to_addr(const char *ipstr, Addr *addr, struct sockaddr_storage *_sockaddr_storage, int *socktype)
{

    assert(_sockaddr_storage != NULL);

    register const char *start_ip, *end_ipv6;
    register const char *service_start;
    register char *domain_p;
    struct Addr local;


    char domain_buffer[256]; /* will temporary hold ipv4 and domain format to convert later from */
    bool ipv6;
    int proto, port, err;

    if(addr == NULL)
        addr = &local;

    domain_p = domain_buffer;
    /* set service start to beginning of ipstr and use it as a seperator to service/port */
    service_start = ipstr;
    ipv6 = false;
    err = 0;
    
    /* get internal protocol id  */
    proto = check_protocol(ipstr);
    if(proto == -1) {
        err = -1;
        goto cleanup_error;
    }

    service_start = service_start + strlen(protocol_table[proto]);

    /* if first char after protocol is [ check for ] to say it is an ipv6 address */
    if(*service_start == '[') {
        /* go latest until null byte */
        start_ip = service_start + 1;
        while(*service_start != '\0') {
            /* if enclosure bracket ] was found set ipv6 true and increment service_start */
            if(*service_start == ']') {
                end_ipv6 = service_start;
                ipv6 = true;
                service_start++;
                break;
            }
            service_start++;
        }
        /* service_start should point to ':' in format string right before service/port */
    }

    /* ip not ipv6 assume ipv4 or domain name */
    if(ipv6 == false) {
        /* go latest until null byte */
        while(*service_start != '\0') {
            /* service_start points to seperator ':', increment domain_p first and set to null byte */
            if(*service_start == ':') {
                *domain_p = '\0';
                break;
            }
            /* write a copy to domain_buffer */
            *domain_p++ = *service_start++;
        }
        /* service_start should point to ':' in format string right before service/port too */
    }

    /* if not ':' we should be at the end of the string which leads to incomplete format, return position of error */
    if(*service_start != ':') {
        err = service_start - ipstr;
        goto cleanup_error;
    }

    service_start++; /* increase by one to get the real service start position */
    port = atoi(service_start);   /* try to get port number after ':' */
    /* if port port was set to 0, try get port by name */
    if(port == 0) {
        /* NOTE: maybe use getservent_r for thread safety */
        struct servent *service = getservbyname(service_start, NULL);
        if(service == NULL) {
            err = service_start - ipstr;
            goto cleanup_error;
        }
        port = htons(service->s_port); /* convert to host byte order */
    }

     /*********************/
    /* setup struct Addr */
    addr->proto = proto;
    addr->port = port;

    if(ipv6) {
        ipstr++;
        strncpy_s(addr->addr, sizeof(addr->addr), start_ip, end_ipv6 - start_ip);
    }
    else {
        ///* add check for ipv6 protocol later
        struct hostent *entry = gethostbyname(domain_buffer);
        strncpy(addr->addr, inet_ntoa(*(struct in_addr*)entry->h_addr_list[0]), sizeof(addr->addr));
    }

    if(ipv6 && addr->proto == tcp)
        addr->proto = tcp6;
    else if(ipv6 && addr->proto == udp)
        addr->proto = udp6;

     /****************************/
    /* setup struct sockaddr_in */
    switch(addr->proto) {
        case tcp6:
        case udp6:
            ((struct sockaddr_in6*)_sockaddr_storage)->sin6_family = AF_INET6;
            break;
        case tcp:   /* NOTE: add later should only be usable for listener if calls like 'tcp::port' */
        case udp:   /* NOTE: same as tcp */ 
        case tcp4:
        case udp4:
            ((struct sockaddr_in*)_sockaddr_storage)->sin_family = AF_INET;
            break;
        default:
            err = -3; /* if this happens and internal error occured because this function should check strict and returns if something not parsed correctly*/
            goto cleanup_error;
            break;
    }
    

    /* set port */
    ((struct sockaddr_in*)_sockaddr_storage)->sin_port = htons(addr->port);
    
    if(ipv6) {
        if(inet_pton(((struct sockaddr_in6*)_sockaddr_storage)->sin6_family, addr->addr, &((struct sockaddr_in6*)_sockaddr_storage)->sin6_addr) != 1) {
            err = -2;
            goto cleanup_error;
        }
    }
    else { /* ipv4 */
        if(inet_pton(((struct sockaddr_in*)_sockaddr_storage)->sin_family, addr->addr, &((struct sockaddr_in*)_sockaddr_storage)->sin_addr) != 1) {
            err = -2;
            goto cleanup_error;
        }
    }

    /* set correct socket type */
    if(addr->proto >= tcp && addr->proto <= tcp6)
        *socktype = SOCK_STREAM; /* socket is tcp socket */
    else if(addr->proto >= udp && addr->proto <= udp6)
        *socktype = SOCK_DGRAM;  /* socket is udp socket */

    return err; /* error is 0 (success) */

    /* if some error occure you land here handle the struct Addr properly */
    cleanup_error: {
        *addr->addr = '\0';
        addr->port = -1;
        addr->proto = -1;
        return err;
    }
}

int main(void)
{
    Addr test;
    struct sockaddr_storage addr;
    int socktype;

    clock_t clock_start, clock_end;

    clock_start = clock();
    printf("%d\n", ipstr_to_addr("tcp:[2001::124:2144:153]:http", &test, &addr, &socktype));
    clock_end = clock();
    printf("%d:%s:%d - ", test.proto, test.addr, test.port);
    printf("%f ms\n", (double) (clock_end - clock_start) / CLOCKS_PER_SEC);
    
    clock_start = clock();
    ipstr_to_addr("udp:www.google.de:http", &test, &addr, &socktype);
    clock_end = clock();
    printf("%d:%s:%d - ", test.proto, test.addr, test.port);
    printf("%f ms\n", (double) (clock_end - clock_start) / CLOCKS_PER_SEC);

    clock_start = clock();
    ipstr_to_addr("tcp:localhost:8080", &test, &addr, &socktype);
    clock_end = clock();
    printf("%d:%s:%d - ", test.proto, test.addr, test.port);
    printf("%f ms\n", (double) (clock_end - clock_start) / CLOCKS_PER_SEC);


    clock_start = clock();
    ipstr_to_addr("tcp:127.129.41.24:463", &test, &addr, &socktype);
    clock_end = clock();
    printf("%d:%s:%d - ", test.proto, test.addr, test.port);
    printf("%f ms\n", (double) (clock_end - clock_start) / CLOCKS_PER_SEC);

    /* open listener with netcat and run this */

    int sock = socket(addr.ss_family, SOCK_STREAM, 0);
    if(sock == -1) {
        perror("socket error");
        exit(1);
    }

    if(connect(sock, (struct sockaddr*) &addr, sizeof addr)) {
        perror("connection failed");
        exit(1);
    }

    send(sock, "Hallo Meister\n", 15, 0);

    close(sock);

}
```
代码语言:javascript
运行
复制
EN

回答 2

Code Review用户

发布于 2023-02-24 14:07:03

使用getaddrinfo()

与其使用inet_ntoa()getservbyent()和类似的低级函数,不如使用getaddrinfo() .它将解析地址和端口,无论是数字指定的还是名称指定的。剩下的唯一需要做的就是拆分冒号上的输入,并解析协议名。然后,可以通过getaddrinfo()参数将后者提供给hint,在该参数中,您可以指定是否需要IPv4、IPv6,以及您是否需要TCP或UDP。

一个名称可以导致多个地址

如果您传递的是主机名而不是数字地址,主机名查找会导致返回多个地址。它们也可能是IPv4和IPv6的混合体。当然,您可以禁止这样做(将AI_NUMERICHOST作为标志传递给getaddrinfo()),或者允许它,但在后一种情况下,您可能希望返回一个struct addrinfo*而不是一个struct sockaddr

票数 10
EN

Code Review用户

发布于 2023-02-25 14:53:57

候选compare_until_delim()简化

代码语言:javascript
运行
复制
/* Compare function that compares strings until delimiter 
   is found either in str1 or in str2 */
bool compare_until_delim(const char *str1, const char *str2, char delim)
  while (*str1 == *str2 && *str1 != delim && *str1) {
    str1++; 
    str2++; 
  }
  return *str1 == *str2;
}

避免不同的签名

我建议代码准确地使用C 1或者VS 1。

代码语言:javascript
运行
复制
// OP
int strncpy_s(char *, size_t, const char *, size_t)`

// C spec
errno_t strncpy_s(char * restrict, rsize_t, const char * restrict, rsize_t);

// VS 2022
errno_t strncpy_s(char *, size_t, const char *, size_t);

顺便说一句:使用可移植代码编写一个完全兼容的strncpy_s()是很困难的,因为“在重叠的对象之间不应该进行复制”。要求。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/283530

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档