以前对windows下jdk的socket的connect函数感到很不理解,终于看一下。这个函数是在PlainSocketImpl.java定义 private native void socketCreate(boolean isServer) throws IOException; private native void socketConnect(InetAddress address, int port, int timeout)实现在TwoStacksPlainSocketImpl.c中,不知道sun的那帮同学起了一个这样的名字。socketCreate函数比较简单,就不多说啦。主要是socketConnect,我把自己的注释,写在代码中/* * inetAddress is the address object passed to the socket connect * call. * * Class: java_net_TwoStacksPlainSocketImpl * Method: socketConnect * Signature: (Ljava/net/InetAddress;I)V */JNIEXPORT void JNICALLJava_java_net_TwoStacksPlainSocketImpl_socketConnect(JNIEnv *env, jobject this, jobject iaObj, jint port, jint timeout){ jint localport = (*env)->GetIntField(env, this, psi_localportID);
/* family and localport are int fields of iaObj */ int family; jint fd, fd1=-1; jint len; int ipv6_supported = ipv6_available();
/* fd initially points to the IPv4 socket and fd1 to the IPv6 socket * If we want to connect to IPv6 then we swap the two sockets/objects * This way, fd is always the connected socket, and fd1 always gets closed. */ jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID); jobject fd1Obj = (*env)->GetObjectField(env, this, psi_fd1ID);
SOCKETADDRESS him;
/* The result of the connection */ int connect_res;
if (!IS_NULL(fdObj)) { /*得到文件描述符,这个存放在PlainSocketImpl的 private FileDescriptor fd1; */ fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); }
if (ipv6_supported && !IS_NULL(fd1Obj)) { fd1 = (*env)->GetIntField(env, fd1Obj, IO_fd_fdID); }
if (IS_NULL(iaObj)) { JNU_ThrowNullPointerException(env, "inet address argument is null."); return; }
if (NET_InetAddressToSockaddr(env, iaObj, port, (struct sockaddr *)&him, &len, JNI_FALSE) != 0) { return; }
family = him.him.sa_family; if (family == AF_INET6) { if (!ipv6_supported) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Protocol family not supported"); return; } else { if (fd1 == -1) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Destination unreachable"); return; } /* close the v4 socket, and set fd to be the v6 socket */ (*env)->SetObjectField(env, this, psi_fdID, fd1Obj); (*env)->SetObjectField(env, this, psi_fd1ID, NULL); NET_SocketClose(fd); fd = fd1; fdObj = fd1Obj; } } else { if (fd1 != -1) { (*env)->SetIntField(env, fd1Obj, IO_fd_fdID, -1); NET_SocketClose(fd1); } if (fd == -1) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Destination unreachable"); return; } } (*env)->SetObjectField(env, this, psi_fd1ID, NULL); /*超时小于等于0,超时值零被解释为无限超时 */ if (timeout <= 0) { connect_res = connect(fd, (struct sockaddr *) &him, SOCKETADDRESS_LEN(&him)); if (connect_res == SOCKET_ERROR) { connect_res = WSAGetLastError(); } } else { int optval; int optlen = sizeof(optval);
/* make socket non-blocking */ optval = 1; /* 改成非阻塞模式 如果不使用ioctlsocket sock 默认为阻塞模式 在使用SELECT的时候要改成 非阻塞模式 ,不改成该模式也可以正常运行,select会自动把套节字置为非阻塞模式 */ ioctlsocket( fd, FIONBIO, &optval );
/* initiate the connect */ connect_res = connect(fd, (struct sockaddr *) &him, SOCKETADDRESS_LEN(&him)); if (connect_res == SOCKET_ERROR) { if (WSAGetLastError() != WSAEWOULDBLOCK) { /*WSAEWOULDBLOCK==10053不是错误 这是出错了 */ connect_res = WSAGetLastError(); } else { fd_set wr, ex; struct timeval t;
FD_ZERO(&wr); FD_ZERO(&ex); FD_SET(fd, &wr); FD_SET(fd, &ex); t.tv_sec = timeout / 1000; t.tv_usec = (timeout % 1000) * 1000;
/* * Wait for timout, connection established or * connection failed. 等待连接 */ connect_res = select(fd+1, 0, &wr, &ex, &t);
/* * Timeout before connection is established/failed so * we throw exception and shutdown input/output to prevent * socket from being used. * The socket should be closed immediately by the caller. 返回socket的个数,成功了肯定不是0
*/ if (connect_res == 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", "connect timed out"); shutdown( fd, SD_BOTH );
/* make socket blocking again - just in case */ optval = 0; ioctlsocket( fd, FIONBIO, &optval ); return; }
/* * We must now determine if the connection has been established * or if it has failed. The logic here is designed to work around * bug on Windows NT whereby using getsockopt to obtain the * last error (SO_ERROR) indicates there is no error. The workaround * on NT is to allow winsock to be scheduled and this is done by * yielding and retrying. As yielding is problematic in heavy * load conditions we attempt up to 3 times to get the error reason. win2k 以后应该不用考虑的 */ if (!FD_ISSET(fd, &ex)) { connect_res = 0; } else { int retry; for (retry=0; retry<3; retry++) { NET_GetSockOpt(fd, SOL_SOCKET, SO_ERROR, (char*)&connect_res, &optlen); if (connect_res) { break; } Sleep(0); }
if (connect_res == 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Unable to establish connection"); return; } } } }
/* make socket blocking again */ optval = 0; ioctlsocket(fd, FIONBIO, &optval); }
if (connect_res) { if (connect_res == WSAEADDRNOTAVAIL) { JNU_ThrowByName(env, JNU_JAVANETPKG "ConnectException", "connect: Address is invalid on local machine, or port is not valid on remote machine"); } else { NET_ThrowNew(env, connect_res, "connect"); } return; }
(*env)->SetIntField(env, fdObj, IO_fd_fdID, (int)fd);
/* set the remote peer address and port */ (*env)->SetObjectField(env, this, psi_addressID, iaObj); (*env)->SetIntField(env, this, psi_portID, port);
/* * we need to initialize the local port field if bind was called * previously to the connect (by the client) then localport field * will already be initialized */ if (localport == 0) { /* Now that we're a connected socket, let's extract the port number * that the system chose for us and store it in the Socket object. */ u_short port; int len = SOCKETADDRESS_LEN(&him); if (getsockname(fd, (struct sockaddr *)&him, &len) == -1) {
if (WSAGetLastError() == WSAENOTSOCK) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); } else { NET_ThrowCurrent(env, "getsockname failed"); } return; } port = ntohs ((u_short)GET_PORT(&him)); (*env)->SetIntField(env, this, psi_localportID, (int) port); }} |