我有一个简单的程序来检查端口是否打开,但我想缩短套接字连接的超时长度,因为默认值太长。我不知道该怎么做。这是代码:
#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;
}
答案 0 :(得分:58)
设置套接字非阻塞,并使用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;
}
答案 1 :(得分:32)
这篇文章可能有所帮助:http://developerweb.net/viewtopic.php?id=3196。看起来你在连接之前将套接字置于非阻塞模式,然后在连接建立后将其重新置于阻塞模式。
答案 2 :(得分:9)
有关使用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()
将其黑客攻击超时不是一种选择。
答案 3 :(得分: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;
}
答案 4 :(得分:1)
这是一个connect_with_timeout
的现代poll
实现,具有正确的错误和信号处理:
#include <sys/socket.h>
#include <fcntl.h>
#include <poll.h>
#include <time.h>
int connect_with_timeout(int sockfd, const struct sockaddr *addr, socklen_t addrlen, unsigned int timeout_ms) {
int rc = 0;
// Set O_NONBLOCK
int sockfd_flags_before;
if((sockfd_flags_before=fcntl(sockfd,F_GETFL,0)<0)) return -1;
if(fcntl(sockfd,F_SETFL,sockfd_flags_before | O_NONBLOCK)<0) return -1;
// Start connecting (asynchronously)
do {
if (connect(sockfd, addr, addrlen)<0) {
// Did connect return an error? If so, we'll fail.
if ((errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {
rc = -1;
}
// Otherwise, we'll wait for it to complete.
else {
// Set a deadline timestamp 'timeout' ms from now (needed b/c poll can be interrupted)
struct timespec now;
if(clock_gettime(CLOCK_MONOTONIC, &now)<0) { rc=-1; break; }
struct timespec deadline = { .tv_sec = now.tv_sec,
.tv_nsec = now.tv_nsec + timeout_ms*1000000l};
// Wait for the connection to complete.
do {
// Calculate how long until the deadline
if(clock_gettime(CLOCK_MONOTONIC, &now)<0) { rc=-1; break; }
int ms_until_deadline = (int)( (deadline.tv_sec - now.tv_sec)*1000l
+ (deadline.tv_nsec - now.tv_nsec)/1000000l);
if(ms_until_deadline<0) { rc=0; break; }
// Wait for connect to complete (or for the timeout deadline)
struct pollfd pfds[] = { { .fd = sockfd, .events = POLLOUT } };
rc = poll(pfds, 1, ms_until_deadline);
// If poll 'succeeded', make sure it *really* succeeded
if(rc>0) {
int error = 0; socklen_t len = sizeof(error);
int retval = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
if(retval==0) errno = error;
if(error!=0) rc=-1;
}
}
// If poll was interrupted, try again.
while(rc==-1 && errno==EINTR);
// Did poll timeout? If so, fail.
if(rc==0) {
errno = ETIMEDOUT;
rc=-1;
}
}
}
} while(0);
// Restore original O_NONBLOCK state
if(fcntl(sockfd,F_SETFL,sockfd_flags_before)<0) return -1;
// Success
return rc;
}
答案 5 :(得分:0)
在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()
。
答案 6 :(得分:0)
两个套接字选项SO_RCVTIMEO
和SO_SNDTIMEO
对connect
无效。以下是屏幕快照的链接,其中包含此说明,在这里我只是对其进行介绍。使用connect
或signal
实现select or poll
超时的合适方法。
connect
, SIGALRM
可以被自身生成的信号alarm
中断。但是,应为同一信号安装信号处理,否则程序将终止。代码像这样...
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<signal.h>
#include<errno.h>
static void signal_handler(int signo)
{
return; // Do nothing just interrupt.
}
int main()
{
/* Register signal handler */
struct sigaction act, oact;
act.sa_handler = signal_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
if(sigaction(SIGALRM, &act, &oact) < 0) // Error registering signal handler.
{
fprintf(stderr, "Error registering signal disposition\n");
exit(1);
}
/* Prepare your socket and sockaddr structures */
int sockfd;
struct sockaddr* servaddr;
/* Implementing timeout connect */
int sec = 30;
if(alarm(sec) != 0)
fprintf(stderr, "Already timer was set\n");
if(connect(sockfd, servaddr, sizeof(struct sockaddr)) < 0)
{
if(errno == EINTR)
fprintf(stderr, "Connect timeout\n");
else
fprintf(stderr, "Connect failed\n");
close(sockfd);
exit(1);
}
alarm(0); /* turn off the alarm */
sigaction(SIGALRM, &oact, NULL); /* Restore the default actions of SIGALRM */
/* Use socket */
/* End program */
close(sockfd);
return 0;
}
由于已经有一些用户提供了有关如何使用select
来实现connect
超时的很好的解释,因此我没有必要重复此说明。 poll
可以以相同的方式使用。但是,在所有答案中很少有常见的错误,我想解决。
即使套接字是非阻塞的,但是如果我们要连接的服务器在同一本地计算机上,则connect
可能会成功返回。因此,建议在调用connect
之前检查select
的返回值。
伯克利派生的实现(和POSIX)对非阻塞套接字和connect
具有以下规则。
1)连接成功完成后,描述符变为可写(TCPv2的第531页)。
2)当连接建立遇到错误时,描述符将变为可读写(TCPv2的第530页)。
所以代码应该处理这些情况,在这里我只是编写必要的修改。
/* All the code stays */
/* Modifications at connect */
int conn_ret = connect(sockfd, servaddr, sizeof(struct sockdaddr));
if(conn_ret == 0)
goto done;
/* Modifications at select */
int sec = 30;
for( ; ; )
{
struct timeval timeo;
timeo.tv_sec = sec;
timeo.tv_usec = 0;
fd_set wr_set, rd_set;
FDZERO(&wr_set);
FD_SET(sockfd, &wr_set);
rd_set = wr_set;
int sl_ret = select(sockfd + 1, &rd_set, &wr_set, NULL, &timeo);
/* All the code stays */
}
done:
/* Use your socket */