我正在用C编写一个网络功能包装库,希望让用户用一个格式字符串初始化一个结构sockaddr_storage。
格式列表:"proto:host:port/servicename“示例:"tcp:localhost:8080","udp:8.8.8.8:dns”
可能的协议串: tcp,tcp4,tcp6,udp,udp4,udp6
我现在有一些问题。
我得到了以下时间:-查看我的主要功能
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
为什么时间过得很快?
代码:
#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);
}
```
发布于 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
。
发布于 2023-02-25 14:53:57
compare_until_delim()
简化/* 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。
// 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()
是很困难的,因为“在重叠的对象之间不应该进行复制”。要求。
https://codereview.stackexchange.com/questions/283530
复制相似问题