SocketノートのRead timed outの深い分析
36667 ワード
文書ディレクトリ ReadTimedOut深い分析 PlainSocketImpl.c Java_java_net_PlainSocketImpl_socketSetOption read()時 Java_java_net_SocketInputStream_socketRead0 solaris/native/java/net/bsd_close.c まとめ ReadTimedOut分析 javaプログラムシミュレーション シミュレーションタイムアウト 実行フロー PlainSocketImpl.c
Java_java_net_PlainSocketImpl_socketSetOption以下のコード解析から分かるように、
read()の場合 javaコードの読み出しデータ を参照ここで
SocketInputStream SocketInputStream.c ① ② ③ ④ SocketInputStream SocketInputStream.c
Java_java_net_SocketInputStream_socketRead0次のコード解析から、javaにおける である.
solaris/native/java/net/bsd_close.c を超えるか否かを判断する.
まとめ javaのlinuxにおける である. となる.
Read timed out
は接続に成功したが、サーバがデータをタイムリーに返さず、リードタイムアウトを招いた.Read timed out
public class SimpleServer {
public static void main(String[] args) throws IOException, InterruptedException {
ServerSocket serverSocket = new ServerSocket(8888,200);
Thread.sleep(6666666);
}
}
//
@Test
public void testReadTimeOut() {
Socket socket = new Socket();
long startTime = 0;
try {
socket.connect(new InetSocketAddress("127.0.0.1", 8888), 10000);
System.out.println("socket ....");
socket.setSoTimeout(2000);
startTime = System.currentTimeMillis();
int read = socket.getInputStream().read();
} catch (IOException e) {
e.printStackTrace();
} finally {
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
}
}
実行結果socket ....
:2008
java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:170)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at java.net.SocketInputStream.read(SocketInputStream.java:223)
at cn.jannal.net.port.SimpleClient.testReadTimeOut(SimpleClient.java:29)
Read timed out
PSH
は、制御情報が正常に転送されることを意味します.つまり、握手は正常に成功し、データを転送する際に、サーバがクライアントにデータコンテンツを転送できないことを制限しています.上記javaプログラムシミュレーションの原理と同様に、接続を確立することができますが、データを返さないことができます. -A OUTPUT -p tcp -m tcp --tcp-flags PSH PSH --sport 8888 -j DROP
client socket
socket.setSoTimeout(int timeout)
--> socketOptions.setOption(int optID, Object value)
--> AbstractPlainSocketImpl.setOption(int opt, Object val)
--> PlainSocketImpl.socketSetOption(int cmd, boolean on, Object value)
--> PlainSocketImpl.c Java_java_net_PlainSocketImpl_socketSetOption
Java_java_net_PlainSocketImpl_socketSetOption
socket.setSoTimeout(int timeout)
オプションはSolaris/linux
の下でCライブラリ関数のsetSockOpt(SO_RCVTIMEO)
を呼び出すことはありません.javaではread timed out
をどのように実現しているのでしょうか. /*
* Class: java_net_PlainSocketImpl
* Method: socketSetOption
* Signature: (IZLjava/lang/Object;)V
*/
JNIEXPORT void JNICALL
Java_java_net_PlainSocketImpl_socketSetOption(JNIEnv *env, jobject this,
jint cmd, jboolean on,
jobject value) {
int fd;
int level, optname, optlen;
union {
int i;
struct linger ling;
} optval;
/*
* Check that socket hasn't been closed
*/
fd = getFD(env, this);
if (fd < 0) {
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
"Socket closed");
return;
}
/*
* SO_TIMEOUT is a NOOP on Solaris/Linux
* , Solaris/linux C setSockOpt(SO_RCVTIMEO)
*/
if (cmd == java_net_SocketOptions_SO_TIMEOUT) {
return;
}
/*
* Map the Java level socket option to the platform specific
* level and option name.
*/
if (NET_MapSocketOption(cmd, &level, &optname)) {
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Invalid option");
return;
}
switch (cmd) {
case java_net_SocketOptions_SO_SNDBUF :
case java_net_SocketOptions_SO_RCVBUF :
case java_net_SocketOptions_SO_LINGER :
case java_net_SocketOptions_IP_TOS :
{
jclass cls;
jfieldID fid;
cls = (*env)->FindClass(env, "java/lang/Integer");
CHECK_NULL(cls);
fid = (*env)->GetFieldID(env, cls, "value", "I");
CHECK_NULL(fid);
if (cmd == java_net_SocketOptions_SO_LINGER) {
if (on) {
optval.ling.l_onoff = 1;
optval.ling.l_linger = (*env)->GetIntField(env, value, fid);
} else {
optval.ling.l_onoff = 0;
optval.ling.l_linger = 0;
}
optlen = sizeof(optval.ling);
} else {
optval.i = (*env)->GetIntField(env, value, fid);
optlen = sizeof(optval.i);
}
break;
}
/* Boolean -> int */
default :
optval.i = (on ? 1 : 0);
optlen = sizeof(optval.i);
}
if (NET_SetSockOpt(fd, level, optname, (const void *)&optval, optlen) < 0) {
#ifdef __solaris__
if (errno == EINVAL) {
// On Solaris setsockopt will set errno to EINVAL if the socket
// is closed. The default error message is then confusing
char fullMsg[128];
jio_snprintf(fullMsg, sizeof(fullMsg), "Invalid option or socket reset by remote peer");
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", fullMsg);
return;
}
#endif /* __solaris__ */
NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
"Error setting socket option");
}
}
read()の場合
InputStream inputStream = socket.getInputStream();
inputStream.read()
inputStream
の実装クラスはsocketInputStream
である.実行フローは、タイムアウト設定の値がread()
メソッドが呼び出されたときに入力される-->read() ①
--> read(b, off, length, impl.getTimeout());// , SO_TIMEOUT ②
-->native int socketRead0(FileDescriptor fd,
byte b[], int off, int len,
int timeout) ③
--> SocketInputStream.c Java_java_net_SocketInputStream_socketRead0 ④
である.SocketInputStream SocketInputStream.c ① ② ③ ④ SocketInputStream SocketInputStream.c
Java_java_net_SocketInputStream_socketRead0
setSoTimeout()
の設定値は最終的にlinuxのselect()
関数に渡され、C言語のsocket
によって設定されていないことがわかる.read()
より前にタイムアウト時間を設定すればよく、計算タイムアウトのコアコードはNET_Timeout
のうち /*
* Class: java_net_SocketInputStream
* Method: socketRead0
* Signature: (Ljava/io/FileDescriptor;[BIII)I
*/
JNIEXPORT jint JNICALL
Java_java_net_SocketInputStream_socketRead0(JNIEnv *env, jobject this,
jobject fdObj, jbyteArray data,
jint off, jint len, jint timeout)
{
char BUF[MAX_BUFFER_LEN];
char *bufP;
jint fd, nread;
if (IS_NULL(fdObj)) {
/* shouldn't this be a NullPointerException? -br */
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
"Socket closed");
return -1;
} else {
fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
/* Bug 4086704 - If the Socket associated with this file descriptor
* was closed (sysCloseFD), then the file descriptor is set to -1.
*/
if (fd == -1) {
JNU_ThrowByName(env, "java/net/SocketException", "Socket closed");
return -1;
}
}
/*
* If the read is greater than our stack allocated buffer then
* we allocate from the heap (up to a limit)
*/
if (len > MAX_BUFFER_LEN) {
if (len > MAX_HEAP_BUFFER_LEN) {
len = MAX_HEAP_BUFFER_LEN;
}
bufP = (char *)malloc((size_t)len);
if (bufP == NULL) {
bufP = BUF;
len = MAX_BUFFER_LEN;
}
} else {
bufP = BUF;
}
if (timeout) {
nread = NET_Timeout(fd, timeout);
if (nread <= 0) {
if (nread == 0) {
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
"Read timed out");
} else if (nread == JVM_IO_ERR) {
if (errno == EBADF) {
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
} else {
NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
"select/poll failed");
}
} else if (nread == JVM_IO_INTR) {
JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
"Operation interrupted");
}
if (bufP != BUF) {
free(bufP);
}
return -1;
}
}
nread = NET_Read(fd, bufP, len);
if (nread <= 0) {
if (nread < 0) {
switch (errno) {
case ECONNRESET:
case EPIPE:
JNU_ThrowByName(env, "sun/net/ConnectionResetException",
"Connection reset");
break;
case EBADF:
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
"Socket closed");
break;
case EINTR:
JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
"Operation interrupted");
break;
default:
NET_ThrowByNameWithLastError(env,
JNU_JAVANETPKG "SocketException", "Read failed");
}
}
} else {
(*env)->SetByteArrayRegion(env, data, off, nread, (jbyte *)bufP);
}
if (bufP != BUF) {
free(bufP);
}
return nread;
}
solaris/native/java/net/bsd_close.c
NET_timeout
コードは、select(s, timeout)
に対するパッケージである、select
関数を用いてタイマを実現し、設定時間(setSoTimeout()
) /*
* Wrapper for select(s, timeout). We are using select() on Mac OS due to Bug 7131399.
* Auto restarts with adjusted timeout if interrupted by
* signal other than our wakeup signal.
*/
int NET_Timeout(int s, long timeout) {
long prevtime = 0, newtime;
struct timeval t, *tp = &t;
fd_set fds;
fd_set* fdsp = NULL;
int allocated = 0;
threadEntry_t self;
fdEntry_t *fdEntry = getFdEntry(s);
/*
* Check that fd hasn't been closed.
*/
if (fdEntry == NULL) {
errno = EBADF;
return -1;
}
/*
* Pick up current time as may need to adjust timeout
*/
if (timeout > 0) {
/* Timed */
struct timeval now;
gettimeofday(&now, NULL);
prevtime = now.tv_sec * 1000 + now.tv_usec / 1000;
t.tv_sec = timeout / 1000;
t.tv_usec = (timeout % 1000) * 1000;
} else if (timeout < 0) {
/* Blocking */
tp = 0;
} else {
/* Poll */
t.tv_sec = 0;
t.tv_usec = 0;
}
if (s < FD_SETSIZE) {
fdsp = &fds;
FD_ZERO(fdsp);
} else {
int length = (howmany(s+1, NFDBITS)) * sizeof(int);
fdsp = (fd_set *) calloc(1, length);
if (fdsp == NULL) {
return -1; // errno will be set to ENOMEM
}
allocated = 1;
}
FD_SET(s, fdsp);
for(;;) {
int rv;
/*
* call select on the fd. If interrupted by our wakeup signal
* errno will be set to EBADF.
*/
startOp(fdEntry, &self);
rv = select(s+1, fdsp, 0, 0, tp);
endOp(fdEntry, &self);
/*
* If interrupted then adjust timeout. If timeout
* has expired return 0 (indicating timeout expired).
*/
if (rv < 0 && errno == EINTR) {
if (timeout > 0) {
struct timeval now;
gettimeofday(&now, NULL);
newtime = now.tv_sec * 1000 + now.tv_usec / 1000;
timeout -= newtime - prevtime;
if (timeout <= 0) {
if (allocated != 0)
free(fdsp);
// 0
return 0;
}
prevtime = newtime;
t.tv_sec = timeout / 1000;
t.tv_usec = (timeout % 1000) * 1000;
}
} else {
if (allocated != 0)
free(fdsp);
return rv;
}
}
}
まとめ
Read timed out
は、接続に成功した(すなわち、3回の握手が完了した)ことを示すが、サーバがデータをタイムリーに返さず(設定された時間内にデータを返さなかった)、リードタイムアウトを招く.Read timed out
は、C関数setSockOpt(SO_RCVTIMEO)
によって設けるのではなく、select(s, timeout).
によってタイマーが実現され、JNI
の異常を投げ出すことによって制御されるjava socket
のリードタイムアウトの設定はread()
メソッドが呼び出されたときに入るので、read()
呼び出しの前に設定すれば