C: время ожидания подключения сокета
У меня есть простая программа, чтобы проверить, открыт ли порт, но я хочу сократить длину таймаута в соединении сокетов, потому что значение по умолчанию слишком длинное. Я не уверен, как это сделать. Здесь код:
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv) {
u_short port; /* user specified port number */
char addr[1023]; /* will be a copy of the address entered by u */
struct sockaddr_in address; /* the libc network address data structure */
short int sock = -1; /* file descriptor for the network socket */
if (argc != 3) {
fprintf(stderr, "Usage %s <port_num> <address>", argv[0]);
return EXIT_FAILURE;
}
address.sin_addr.s_addr = inet_addr(argv[2]); /* assign the address */
address.sin_port = htons(atoi(argv[2])); /* translate int2port num */
sock = socket(AF_INET, SOCK_STREAM, 0);
if (connect(sock,(struct sockaddr *)&address,sizeof(address)) == 0) {
printf("%i is open\n", port);
}
close(sock);
return 0;
}
Ответы
Ответ 1
Эта статья может помочь:
Соединитесь с тайм-аутом (или другим использованием для выбора())
Похоже, вы переводите сокет в неблокирующий режим до тех пор, пока не подключитесь, а затем снова установите его в режим блокировки после установления соединения.
void connect_w_to(void) {
int res;
struct sockaddr_in addr;
long arg;
fd_set myset;
struct timeval tv;
int valopt;
socklen_t lon;
// Create socket
soc = socket(AF_INET, SOCK_STREAM, 0);
if (soc < 0) {
fprintf(stderr, "Error creating socket (%d %s)\n", errno, strerror(errno));
exit(0);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(2000);
addr.sin_addr.s_addr = inet_addr("192.168.0.1");
// Set non-blocking
if( (arg = fcntl(soc, F_GETFL, NULL)) < 0) {
fprintf(stderr, "Error fcntl(..., F_GETFL) (%s)\n", strerror(errno));
exit(0);
}
arg |= O_NONBLOCK;
if( fcntl(soc, F_SETFL, arg) < 0) {
fprintf(stderr, "Error fcntl(..., F_SETFL) (%s)\n", strerror(errno));
exit(0);
}
// Trying to connect with timeout
res = connect(soc, (struct sockaddr *)&addr, sizeof(addr));
if (res < 0) {
if (errno == EINPROGRESS) {
fprintf(stderr, "EINPROGRESS in connect() - selecting\n");
do {
tv.tv_sec = 15;
tv.tv_usec = 0;
FD_ZERO(&myset);
FD_SET(soc, &myset);
res = select(soc+1, NULL, &myset, NULL, &tv);
if (res < 0 && errno != EINTR) {
fprintf(stderr, "Error connecting %d - %s\n", errno, strerror(errno));
exit(0);
}
else if (res > 0) {
// Socket selected for write
lon = sizeof(int);
if (getsockopt(soc, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &lon) < 0) {
fprintf(stderr, "Error in getsockopt() %d - %s\n", errno, strerror(errno));
exit(0);
}
// Check the value returned...
if (valopt) {
fprintf(stderr, "Error in delayed connection() %d - %s\n", valopt, strerror(valopt)
);
exit(0);
}
break;
}
else {
fprintf(stderr, "Timeout in select() - Cancelling!\n");
exit(0);
}
} while (1);
}
else {
fprintf(stderr, "Error connecting %d - %s\n", errno, strerror(errno));
exit(0);
}
}
// Set to blocking mode again...
if( (arg = fcntl(soc, F_GETFL, NULL)) < 0) {
fprintf(stderr, "Error fcntl(..., F_GETFL) (%s)\n", strerror(errno));
exit(0);
}
arg &= (~O_NONBLOCK);
if( fcntl(soc, F_SETFL, arg) < 0) {
fprintf(stderr, "Error fcntl(..., F_SETFL) (%s)\n", strerror(errno));
exit(0);
}
// I hope that is all
}
Ответ 2
Установите неблокирующий сокет и используйте select()
(который принимает параметр тайм-аута). Если неблокирующий сокет пытается подключиться, то select()
будет указывать, что сокет записывается, когда connect()
заканчивается (успешно или безуспешно). Затем вы используете getsockopt()
для определения результата connect()
:
int main(int argc, char **argv) {
u_short port; /* user specified port number */
char *addr; /* will be a pointer to the address */
struct sockaddr_in address; /* the libc network address data structure */
short int sock = -1; /* file descriptor for the network socket */
fd_set fdset;
struct timeval tv;
if (argc != 3) {
fprintf(stderr, "Usage %s <port_num> <address>\n", argv[0]);
return EXIT_FAILURE;
}
port = atoi(argv[1]);
addr = argv[2];
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr(addr); /* assign the address */
address.sin_port = htons(port); /* translate int2port num */
sock = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sock, F_SETFL, O_NONBLOCK);
connect(sock, (struct sockaddr *)&address, sizeof(address));
FD_ZERO(&fdset);
FD_SET(sock, &fdset);
tv.tv_sec = 10; /* 10 second timeout */
tv.tv_usec = 0;
if (select(sock + 1, NULL, &fdset, NULL, &tv) == 1)
{
int so_error;
socklen_t len = sizeof so_error;
getsockopt(sock, SOL_SOCKET, SO_ERROR, &so_error, &len);
if (so_error == 0) {
printf("%s:%d is open\n", addr, port);
}
}
close(sock);
return 0;
}
Ответ 3
Ответы об использовании select()
/poll()
верны, и код должен быть написан таким образом, чтобы быть переносимым.
Однако, поскольку вы находитесь в Linux, вы можете сделать это:
int synRetries = 2; // Send a total of 3 SYN packets => Timeout ~7s
setsockopt(fd, IPPROTO_TCP, TCP_SYNCNT, &synRetries, sizeof(synRetries));
См. man 7 tcp
и man setsockopt
.
Я использовал это для ускорения тайм-аута соединения в программе, которую мне нужно было быстро исправлять. Невозможно было взломать его до таймаута через select()
/poll()
.
Ответ 4
Этот параметр имеет параметризованный ip, порт, таймаут в секундах, обрабатывает ошибки соединения и дает вам время соединения в миллисекундах:
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
int main(int argc, char **argv) {
struct sockaddr_in addr_s;
char *addr;
short int fd=-1;
int port;
fd_set fdset;
struct timeval tv;
int rc;
int so_error;
socklen_t len;
struct timespec tstart={0,0}, tend={0,0};
int seconds;
if (argc != 4) {
fprintf(stderr, "Usage: %s <ip> <port> <timeout_seconds>\n", argv[0]);
return 1;
}
addr = argv[1];
port = atoi(argv[2]);
seconds = atoi(argv[3]);
addr_s.sin_family = AF_INET; // utilizzo IPv4
addr_s.sin_addr.s_addr = inet_addr(addr);
addr_s.sin_port = htons(port);
clock_gettime(CLOCK_MONOTONIC, &tstart);
fd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(fd, F_SETFL, O_NONBLOCK); // setup non blocking socket
// make the connection
rc = connect(fd, (struct sockaddr *)&addr_s, sizeof(addr_s));
if ((rc == -1) && (errno != EINPROGRESS)) {
fprintf(stderr, "Error: %s\n", strerror(errno));
close(fd);
return 1;
}
if (rc == 0) {
// connection has succeeded immediately
clock_gettime(CLOCK_MONOTONIC, &tend);
printf("socket %s:%d connected. It took %.5f seconds\n",
addr, port, (((double)tend.tv_sec + 1.0e-9*tend.tv_nsec) - ((double)tstart.tv_sec + 1.0e-9*tstart.tv_nsec)));
close(fd);
return 0;
} /*else {
// connection attempt is in progress
} */
FD_ZERO(&fdset);
FD_SET(fd, &fdset);
tv.tv_sec = seconds;
tv.tv_usec = 0;
rc = select(fd + 1, NULL, &fdset, NULL, &tv);
switch(rc) {
case 1: // data to read
len = sizeof(so_error);
getsockopt(fd, SOL_SOCKET, SO_ERROR, &so_error, &len);
if (so_error == 0) {
clock_gettime(CLOCK_MONOTONIC, &tend);
printf("socket %s:%d connected. It took %.5f seconds\n",
addr, port, (((double)tend.tv_sec + 1.0e-9*tend.tv_nsec) - ((double)tstart.tv_sec + 1.0e-9*tstart.tv_nsec)));
close(fd);
return 0;
} else { // error
printf("socket %s:%d NOT connected: %s\n", addr, port, strerror(so_error));
}
break;
case 0: //timeout
fprintf(stderr, "connection timeout trying to connect to %s:%d\n", addr, port);
break;
}
close(fd);
return 0;
}
Ответ 5
Что-то не так с решением Науэля Греко, кроме ошибки компиляции?
Если я изменю одну строку
// Compilation error
setsockopt(fd, SO_SNDTIMEO, &timeout, sizeof(timeout));
в
// Fixed?
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
тогда кажется, что он работает как объявлено - socket()
возвращает ошибку тайм-аута.
Результирующий код:
struct timeval timeout;
timeout.tv_sec = 7; // after 7 seconds connect() will timeout
timeout.tv_usec = 0;
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
connect(...)
Я не достаточно сведущ, чтобы знать, что компромисс между тайм-аутом отправки и неблокирующим сокетом, но мне интересно узнать.
Ответ 6
В Linux вы также можете использовать:
struct timeval timeout;
timeout.tv_sec = 7; // after 7 seconds connect() will timeout
timeout.tv_usec = 0;
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
connect(...)
Не забудьте очистить SO_SNDTIMEO
после connect()
, если вам это не нужно.