|
1 阻塞socket 和非阻塞socket 设置一个套接字为非阻塞模式,相当于通知内核当一个套接字相关的I/O 操作不能立刻 完成的时候,不要将调用者设为休眠状态,而是应该返回一个错误代码。其区别如下图所示: |
内核 |
应用 |
read, recvfrom(write,sendto,accept,connect) |
wait for data come in |
copy data |
copy completely |
return |
continue to process with data |
内核 |
应用 |
read |
return EWOULDBLOCK immediately |
read |
return EWOULDBLOCK immediately |
read |
拷贝数据到用户空间 |
return success |
处理数据 |
1.1 读操作 对于阻塞的socket,当socket 的接收缓冲区中没有数据时,read 调用会一直阻塞住,直 到有数据到来才返回。当socket 缓冲区中的数据量小于期望读取的数据量时,返回实际读 |
节数,返回实际读取的长度。 对于非阻塞socket 而言,socket 的接收缓冲区中有没有数据,read 调用都会立刻返回。 接收缓冲区中有数据时,与阻塞socket 有数据的情况是一样的,如果接收缓冲区中没有数 据,则返回错误号为EWOULDBLOCK, 表示该操作本来应该阻塞的,但是由于本socket 为非阻塞的socket,因此立刻返回,遇 到这样的情况,可以在下次接着去尝试读取。如果返回值是其它负值,则表明读取错误。 因此,非阻塞的read 调用一般这样写: if ((nread = read(sock_fd, buffer, len)) < 0) { if (errno == EWOULDBLOCK) { return 0; //表示没有读到数据 }else return -1; //表示读取失败 }else return nread;读到数据长度 |
1.2 写操作 对于写操作write,原理是类似的,非阻塞socket 在发送缓冲区没有空间时会直接返回错 误号EWOULDBLOCK,表示没有空间可写数据,如果错误号是别的值,则表明发送失败。 如果发送缓冲区中有足够空间或者是不足以拷贝所有待发送数据的空间的话,则拷贝前面N 个能够容纳的数据,返回实际拷贝的字节数。 而对于阻塞Socket 而言,如果发送缓冲区没有空间或者空间不足的话,write 操作会直 接阻塞住,如果有足够空间,则拷贝所有数据到发送缓冲区,然后返回。 非阻塞的write 操作一般写法是: int write_pos = 0; int nLeft = nLen; while (nLeft > 0) { int nWrite = 0; if ((nWrite = write(sock_fd, data + write_pos, nLeft)) <= 0) { if (errno == EWOULDBLOCK) nWrite = 0; else return -1; //表示写失败 } nLeft -= nWrite; write_pos += nWrite; } return nWrite |
1.3 建立连接 阻塞方式下,connect 首先发送SYN 请求道服务器,当客户端收到服务器返回的SYN 的 确认时,则connect 返回。否则的话一直阻塞。 非阻塞方式,connect 将启用TCP 协议的三次握手,但是connect 函数并不等待连接建立 好才返回,而是立即返回。返回的错误码为EINPROGRESS,表示正在进行某种过程。接收 |
用的连接,才返回。 非阻塞倾听socket, 在有没有连接时都立即返回,没有连接时,返回的错误码为 EWOULDBLOCK,表示本来应该阻塞。 |
2 无阻塞的设置方法 方法一:fcntl int flag=fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flag|O_NONBLOCK) ; 方法二:ioctl int b_on = 1; ioctl (fd, FIONBIO, &b_on); |
3 程序分析: 下面的程序利用非阻塞模式下的conncet 函数,进行端口扫描。 #include <stdio.h> #include <sys/time.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <errno.h> #include <sys/select.h> #include <fcntl.h> |
//使用方法:scan 192.168.1.1 192.168.2.10 89 500 int main(int argc, char* argv[]) { |
long startip,endip,startport,endport; if(!(argc==4||argc==5||argc==1)) {printf("参数错误\r\n");exit(-1);}; |
if(argc==1) { startip=inet_addr("127.0.0.1"); endip=startip; startport=1; endport=65535; } |
{ startip=inet_addr(argv[1]); endip=startip; startport=atoi(argv[2]); endport=atoi(argv[3]); } |
if(argc==5) { startip=inet_addr(argv[1]); endip=inet_addr(argv[2]); startport=atoi(argv[3]); endport=atoi(argv[4]); } if(startip==INADDR_NONE||endip==INADDR_NONE) {printf("error ip address\r\n");exit(-1);}; |
if(startport==0||endport==0) {printf("error port!\r\n");exit(-1);}; long i,j; for(i=ntohl(startip);i<=ntohl(endip);i++) { //i 表示IP 地址的变量(PC 机顺序) for(j=startport;j<=endport;j++) { //j 表示端口 int fd=socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in s; s.sin_addr.s_addr=htonl(i);//将i 转换为网络字节序的IP 地址 s.sin_port=htons(j); s.sin_family=AF_INET; int flag; fcntl(fd,F_GETFL,&flag); fcntl(fd,F_SETFL,flag|O_NONBLOCK);//设置为非阻塞 int cret=connect(fd,(struct sockaddr*)&s,sizeof s); if(cret==0) { printf("%s:%d is open\r\n",inet_ntoa(s.sin_addr),j); close(fd); continue; } /*由于是非阻塞工作模式,因此对于来不及返回0 的connect 函数, errno 应该为EINPROGRESS,表示正在建立这个连接*/ |
|
fd_set wset; FD_ZERO(&wset); FD_SET(fd,&wset); struct timeval timeout; timeout.tv_sec=0; timeout.tv_usec=10000;//10 毫秒 int rs=select(fd+1,NULL,&wset,NULL,&timeout);//等待10 毫秒,等待 这个fd 是否可写,可写表示已经建立了连接 |
int error=0; int len=sizeof error; |
if((rs==1)&&(getsockopt(fd,SOL_SOCKET,SO_ERROR,&error,&len)==0)) { //error 如果不为零,则证明对方的端口是开放的 if(error==0) printf("%s:%d is open\r\n",inet_ntoa(s.sin_addr),j); } close(fd); continue; } } |
} |