Поскольку Рыба-меч уже ответила, вы пытаетесь sprintf()
к неопределенному указателю.Вместо этого используйте, например,
#define STRINGIFY_(x) #x
#define STRINGIFY(x) STRINGIFY_(x)
service = STRINGIFY(PORT);
.
Рассмотрите следующую вспомогательную функцию:
#define DEFAULT_HOST "*"
#define DEFAULT_PORT "9191"
/* Return a server-side UDP/IP(v4/v6) socket bound
to the specified address. If addr is non-NULL,
and *addrlen is initialized to its length and
is large enough, the bound-to address will be
stored there. Returns -1 with errno set if
an error occurs. */
int server_socket(struct sockaddr *addr, socklen_t *addrlen,
const char *host, const char *port)
{
struct addrinfo hints, *list, *curr;
int err, fd;
/* Set defaults, if NULL or empty. */
if (!host || !*host)
host = DEFAULT_HOST;
if (!port || !*port)
port = DEFAULT_PORT;
/* "" or "*" host refer to wildcard address. */
if (!host[0] || !strcmp(host, "*")
host = NULL;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
hints.ai_flags = 0; /* For wildcard IP address */
hints.ai_protocol = 0; /* Any protocol */
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
err = getaddrinfo(host, port, &hints, &list);
if (err)
switch (err) {
case EAI_ADDRFAMILY:
case EAI_FAMILY:
errno = EAFNOSUPPORT;
return -1;
case EAI_AGAIN:
errno = EAGAIN;
return -1;
case EAI_FAIL:
case EAI_NODATA:
case EAI_NONAME:
case EAI_SERVICE:
errno = EADDRNOTAVAIL;
return -1;
case EAI_MEMORY:
errno = ENOMEM;
return -1;
case EAI_SOCKTYPE:
errno = ESOCKTNOSUPPORT;
return -1;
case EAI_SYSTEM:
/* errno already set */
return -1;
default:
errno = EINVAL;
return -1;
}
/* Find first valid socket we can construct and bind to */
for (curr = list; curr != NULL; curr = curr->ai_next) {
fd = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol);
if (fd == -1)
continue;
if (bind(fd, curr->ai_addr, curr->ai_addrlen) == 0)
break; /* This one works. */
/* Cannot bind. Clean it up, and try the next one. */
close(fd);
}
if (!curr) {
/* No suitable socket found. */
errno = EADDRNOTAVAIL;
return -1;
}
/* Copy bound-to address, if enough room */
if (addrlen) {
if (!addr || *addrlen < curr->ai_addrlen) {
*addrlen = 0;
} else {
memcpy(addr, curr->ai_addr, curr->ai_addrlen);
*addrlen = curr->ai_addrlen;
}
}
/* Discard the linked list getaddrinfo() provided. */
freeaddrinfo(list);
return fd;
}
И числовые порты, и именованные службы (как указано в getent services
) может использоваться как для порта по умолчанию, так и для параметра порта.
Типичная служба считывает их из файла конфигурации (или файлов).
Если вышеприведенный server_socket()
завершается неудачно,он вернет -1 с errno
, приблизительно равным сообщению об ошибке.(Если getaddrinfo()
терпит неудачу, он отображает ошибку на errno
константы как можно лучше, вместо использования gai_strerror()
.) В случае успеха, он вернет номер дескриптора связанного сокета.
Причина, по которой вы захотите записать их как отдельные функции, в основном лень и простота обслуживания .Видите ли, если вы запишите вышеприведенное (с необходимыми #include и т. Д.) В отдельную программу тестирования, вы можете протестировать эту часть отдельно, как единое целое: таким образом, модульное тестирование это с несколькими различнымипараметры, включая указатели NULL и недопустимые значения.Например,
struct sockaddr_storage addr;
socklen_t addrlen;
const char *node, *service;
int fd;
addrlen = sizeof addr;
fd = server_socket(&addr, &addrlen, node, service);
if (fd == -1) {
fprintf(stderr, "Cannot create server socket: %s.\n", strerror(errno));
exit(EXIT_FAILURE);
}
, чтобы сообщить об ошибках.Помните, что тестирование - это не просто проверка работоспособности функции при заданных вменяемых параметрах;он также проверяет, что происходит, если сами параметры неверны / плохи / противоречивы и т. д., и проверяет, что , что тоже приемлемо.
Причина, по которой этот вид кусочного тестирования экономит время и усилияв том, что когда возникают ошибки - и они будут, независимо от того, насколько вы хороши программистом - есть только небольшая часть кода (добавленные части еще не протестированы модульно) и их общее взаимодействиелогика (логика «более высокого уровня»), которую нужно изучить и обдумать, чтобы найти ошибки.Кроме того, вам не нужно тратить силы на то, чтобы держать все части кода в уме одновременно;вы можете просто забыть о проверенных модулем деталях, помня только то, что они делают в общих чертах.
Это также означает, что вы захотите научиться писать комментарии, описывающие, что ваше намерение какпрограммист - это то, что должен делать код, а не то, что делает код.Последнее очевидно из кода, но только программист знает первое;чтобы эффективно находить ошибки, нам нужно знать и то, и другое.(Я все еще изучаю эту часть после более чем двух десятилетий платной работы программистом.)