/*
需求: adsl拨号上网,路由器上连了几台电脑,夜里睡觉之前看看有没有其他的电脑还连在局域网上,没有的话就关猫。都是限时上网惹的货
原理:ping最简单的,但是其他电脑有可能有防火墙会屏蔽icmp消息的,所以就不行了。需要高层的tcp或者udp。现在用tcp,连接一个端口,根据返回的错误(0成功电脑开着, 111失败端口未开电脑还是开着, 113失败无法路由电脑关了)进行判断就ok了。
关键:需要设置connect超时,电脑关了(不存在的ip)情况下,局域网可能还好会比较块的返回错误113.但是外网地址一般都要花75秒左右才能返回超时。原因我不说了
作者:林汉杰2008-7-15
环境:g++
*/
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/time.h"
#include "sys/socket.h"
#include "arpa/inet.h"
#include "netinet/in.h"
#include "netdb.h"
#include "fcntl.h"
#include "errno.h"
int main() {
struct timeval timeout;
fd_set wset;
char straddr[100];
for(int i=1; i<255; i++) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)perror("socket()");
sprintf(straddr, "192.168.1.%d\0", i);
fprintf(stderr, "scan %s\t", straddr);
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
if(inet_pton(AF_INET, straddr, &(server_addr.sin_addr)) != 1)perror("inet_pton()");
server_addr.sin_port = htons(22);
/*
int result = connect(sockfd, (struct sockaddr*)(&server_addr), sizeof(server_addr));
if(result == -1)perror("connect()");
else {
printf("connect success %d \n", result);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
}
sleep(2);
*/
int flags;
if((flags = fcntl(sockfd, F_GETFL, 0)) < 0)perror("fcntl()");
if(fcntl(sockfd, F_SETFL, flags | O_NONBLOCK)< 0)perror("fcntl()");
int result = connect(sockfd, (struct sockaddr*)(&server_addr), sizeof(server_addr));
if(result == -1) {
if(errno == EINPROGRESS) {
// do {
timeout.tv_sec = 10;
timeout.tv_usec = 100000;
FD_ZERO(&wset);
FD_SET(sockfd, &wset);
int n = select(sockfd+1, 0, &wset, 0, &timeout);
if(n == -1 && errno!= EINTR) {
perror("select() error");
} else if(n > 0) {
int optval;
int optlen = 4;
if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void*)&optval, (socklen_t*)&optlen) < 0)perror("getsockopt()");
if(optval == 0) {
fprintf(stderr, "success select\n");
} else {
fprintf(stderr, "failed select %d:%s\n", optval, strerror(optval));
}
} else if(n == 0) {
fprintf(stderr, "connect timeout\n");
}
// } while(1);
} else {
close(sockfd);
perror("connect()");
}
} else {
printf("connect() success\n");
close(sockfd);
continue;
}
close(sockfd);
}
return 0;
}
ps:关于connect的select超时问题
select的返回值
0: 已经到了自己设置的timeout超时了
>0:一个或者多个描述符准备好读写或者有异常带外数据到来
可读情况:1.tcp接受缓冲区大于getsockopt SO_RCVLOWAT的值 2.有新的已经完成3次握手的连接放入接收队列 3.对方关闭写端读到0字节 4.非阻塞connect失败通过getsockopt获得错误 5.有错误比如rst,通过read返回错误
可写情况:1.tcp发送缓冲区数据大于sndlowat的值 2.本端主动关闭写端,write返回错误 3.非阻塞connect失败通过getsockopt获得错误 4.非阻塞connect成功,套接字可写 5.有错误rst,通过write返回错误
异常情况:收到带外数据
-1:比如被信号中断等等
以上所见:非阻塞connect的sockfd, 连接成功的时候sockfd只能写。但是连接失败的时候sockfd既可以读也可以写。所以总结起来,select非阻塞的connect的方案有两种:
1. 将sockfd加入rdset wrset,select返回时如果只有wrset被设置则连接成功。如果wrset rdset同时被设置则连接失败。通过getsockopt获得错误码判断具体是什么错误。
2.将sockfd加入wrset,连接成功失败都会返回。通过getsockopt获得错误码判断是否成功。
3.将sockfd加入rdset,连接成功不会返回,无效!!!
强烈注意:一定通过getsockopt获得错误码判断具体是什么错误。否则将会有很大的错误。我的理解,select返回-1就会设置errno,但是connect失败的时候select已经成功的返回了>0,所以就不应该设置errno了,但是又接收到了错误,所以内核就setsockopt SO_ERROR了,然后等待应用程序自己获取这个错误。
阅读(9755) | 评论(0) | 转发(0) |