Java网络编程的那些事儿
我在这里用Java写一个非常简单的网络传输程序。
回答得比较清楚:TCP流不是可缓冲的,所以不需要实现这个flush。这里还提到了nagle算法和setTcpNoDelay方法,有兴趣的可以google一下。Nagle算法其实就是通过一些算法把一些小包串起来发送以尽可能减少发送包的数量,优化网络。
第二个问题其实就是关于那个循环write。我们之所以把代码写成这个样子,实际上是因为受到了c Socket的影响,因为在C,我们一般都会把数据尽可能的分成小段发送,例如4096。
但如果我们深入socketWrite0这个native这个方法就会法相一些不一样的东西了。在正常情况下我们是看不到这个方法的源代码,还好我们有OpenJDK。这里我参考的是openjdk-7-ea-src-b133-10_mar_2011,各位可以根据自己喜欢选择。
在OpenJDK有很多Native的代码,这里我选择Windows平台的,大家熟悉嘛。打开openjdk\jdk\src\windows\native\java\net\SocketOutputStream.c,这里的Java_java_net_SocketOutputStream_socketWrite0就是对应的socketWrite0方法。
JNIEXPORT void JNICALL Java_java_io_FileOutputStream_writeBytes(JNIEnv *env, jobject this, jbyteArray bytes, jint off, jint len, jboolean append){ writeBytes(env, this, bytes, off, len, append, fos_fd);}void writeBytes(JNIEnv *env, jobject this, jbyteArray bytes, jint off, jint len, jboolean append, jfieldID fid){ jint n; char stackBuf[BUF_SIZE]; char *buf = NULL; FD fd; if (IS_NULL(bytes)) { JNU_ThrowNullPointerException(env, NULL); return; } if (outOfBounds(env, off, len, bytes)) { JNU_ThrowByName(env, "java/lang/IndexOutOfBoundsException", NULL); return; } if (len == 0) { return; } else if (len > BUF_SIZE) { buf = malloc(len); if (buf == NULL) { JNU_ThrowOutOfMemoryError(env, NULL); return; } } else { buf = stackBuf; } (*env)->GetByteArrayRegion(env, bytes, off, len, (jbyte *)buf); if (!(*env)->ExceptionOccurred(env)) { off = 0; while (len > 0) { fd = GET_FD(this, fid); if (fd == -1) { JNU_ThrowIOException(env, "Stream Closed"); break; } if (append == JNI_TRUE) { n = (jint)IO_Append(fd, buf+off, len); } else { n = (jint)IO_Write(fd, buf+off, len); } if (n == JVM_IO_ERR) { JNU_ThrowIOExceptionWithLastError(env, "Write error"); break; } else if (n == JVM_IO_INTR) { JNU_ThrowByName(env, "java/io/InterruptedIOException", NULL); break; } off += n; len -= n; } } if (buf != stackBuf) { free(buf); }}#define IO_Append handleAppend#define IO_Write handleWriteJNIEXPORT size_t handleWrite(jlong fd, const void *buf, jint len) { return writeInternal(fd, buf, len, JNI_FALSE);}static size_t writeInternal(jlong fd, const void *buf, jint len, jboolean append){ BOOL result = 0; DWORD written = 0; HANDLE h = (HANDLE)fd; if (h != INVALID_HANDLE_VALUE) { OVERLAPPED ov; LPOVERLAPPED lpOv; if (append == JNI_TRUE) { ov.Offset = (DWORD)0xFFFFFFFF; ov.OffsetHigh = (DWORD)0xFFFFFFFF; ov.hEvent = NULL; lpOv = &ov; } else { lpOv = NULL; } result = WriteFile(h, /* File handle to write */ buf, /* pointers to the buffers */ len, /* number of bytes to write */ &written, /* receives number of bytes written */ lpOv); /* overlapped struct */ } if ((h == INVALID_HANDLE_VALUE) || (result == 0)) { return -1; } return (size_t)written;}本来最后想来几句结束语的,不过后来想了一下还是算了,毕竟这里牛人太多了,还是别乱说废话,以免被喷。 1 楼 biAji 2011-04-19 android用的实现木有这个机制。 写程序还是不能依赖jvm实现啊。 2 楼 tracyhuyan 2011-04-19 lz很有钻研精神,赞下 3 楼 yangyi 2011-04-19 是,即使是buffered stream也没有必要每次flush,因为buffer满了后就会flush,只有最后的一次write需要flush 4 楼 i2534 2011-04-19 文件读写的话NIO中的FileChanel似乎可以工作的更好.
当然了,大家习惯上还是用循环并且flush的.
毕竟这样用之四海而皆准,不用考虑每个实现是不是优化过了. 5 楼 AlwenS 2011-04-19 tcp-stream is non-buffered ?
nagale算法的前提就是必须buffered住小块的tcp data,这两个观点不是冲突了?
莫非:
此buffered指的是应用层/库的buffer而非os内核里从属于该tcp端点的buffer?
6 楼 dwbin 2011-04-19 流操作习惯于关闭以前flush。 7 楼 nitianyi 2011-04-19 深入钻研的精神值得学习! 8 楼 sunqy 2011-04-19 yangyi 写道是,即使是buffered stream也没有必要每次flush,因为buffer满了后就会flush,只有最后的一次write需要flush
对,没有必要每次都flush,最后可以显示的flush一次。 9 楼 janeky 2011-04-19 写得很详细,支持。
其实可以接着写其他方面的内容
比如多线程的socket
NIO …… 10 楼 alosin 2011-04-20 谢谢lz,写代码就是要一减再减,不要做那些脱裤子打屁的事 11 楼 pan_java 2011-04-20 精神值的学习 12 楼 sunqitang 2011-04-20 楼主, 我现在对javaNIO的缓存机制 跟 普通IO中的字节数组之间的区别不是太明白。 能帮忙解释一下吗?