SocketOutputStream和SocketChannel write方法的區別和底層實現

Java直接內存原理提到了SocketChannel#write的實現原理。
經過IOUtil#write將java堆內存拷貝到了直接內存,而後再把地址傳給了I/O函數。
那麼 BIO 是怎麼實現往socket裏面寫數據的呢?java

BIO

Socket#getOutputStream()得到SocketOutputStream
三個write方法最後都會調用native方法SocketOutputStream#socketWrite0 SocketOutputStream.c#Java_java_net_SocketOutputStream_socketWrite0方法c#

/* * Class: java_net_SocketOutputStream * Method: socketWrite * Signature: (Ljava/io/FileDescriptor;[BII)V */
JNIEXPORT void JNICALL Java_java_net_SocketOutputStream_socketWrite0(JNIEnv *env, jobject this, jobject fdObj, jbyteArray data, jint off, jint len) {
    char *bufP; //中間臨時緩衝區
    char BUF[MAX_BUFFER_LEN]; //若是堆棧能夠存下,直接使用堆棧內存
    int buflen; //緩衝區大小,就是須要發送的數據大小
    int fd;

    /* 省略,入參校驗*/

    /* * 儘量使用堆棧分配緩衝區。 * 對於large sizes,咱們從堆中分配一箇中間緩衝區(達到最大值)。 * 若是堆不可用,咱們只使用的堆棧緩衝區。 */
    if (len <= MAX_BUFFER_LEN) {
        bufP = BUF;
        buflen = MAX_BUFFER_LEN;
    } else {
        buflen = min(MAX_HEAP_BUFFER_LEN, len);
        bufP = (char *)malloc((size_t)buflen);
        if (bufP == NULL) {
            bufP = BUF;
            buflen = MAX_BUFFER_LEN;
        }
    }

    while(len > 0) {
        int loff = 0;
        int chunkLen = min(buflen, len);
        int llen = chunkLen;
        int retry = 0;
        /* * 這個方法複製了java數組到native堆中!!! */
        (*env)->GetByteArrayRegion(env, data, off, chunkLen, (jbyte *)bufP);
		/* * 因爲Windows套接字中的錯誤(在NT和Windows 2000上觀察到),可能須要重試發送。 */
        while(llen > 0) {
        	/* 循環調用發送*/
            int n = send(fd, bufP + loff, llen, 0);
            if (n > 0) {
                llen -= n;
                loff += n;
                continue;
            }

            /* * 因爲Windows套接字中的錯誤(在NT和Windows 2000上觀察到),可能須要重試發送。 */
            if (WSAGetLastError() == WSAENOBUFS) {
                /* 省略,失敗重試機制*/
            }

            /* * 發送失敗 - 可能由關閉或寫入錯誤引發。 */
            if (WSAGetLastError() == WSAENOTSOCK) {
                JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
            } else {
                NET_ThrowCurrent(env, "socket write error");
            }
            /* 釋放臨時緩衝區內存*/
            if (bufP != BUF) {
                free(bufP);
            }
            return;
        }
        len -= chunkLen;
        off += chunkLen;
    }
	/* 釋放臨時緩衝區內存*/
    if (bufP != BUF) {
        free(bufP);
    }
}
複製代碼

jni.cpp宏定義數組

#define DEFINE_GETSCALARARRAYREGION(ElementTag,ElementType,Result, Tag) \ DT_VOID_RETURN_MARK_DECL(Get##Result##ArrayRegion);\ \ JNI_ENTRY(void, \ jni_Get##Result##ArrayRegion(JNIEnv *env, ElementType##Array array, jsize start, \ jsize len, ElementType *buf)) \ /* 省略,動態判斷應該去調用byte、int等哪一個方法;還有一些動態追蹤的邏輯?*/
  typeArrayOop src = typeArrayOop(JNIHandles::resolve_non_null(array)); \
  if (start < 0 || len < 0 || ((unsigned int)start + (unsigned int)len > (unsigned int)src->length())) { \
    THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException()); \
  } else { \
    if (len > 0) { \
      int sc = TypeArrayKlass::cast(src->klass())->log2_element_size(); \
      /* 內存拷貝*/
      memcpy((u_char*) buf, \
             (u_char*) src->Tag##_at_addr(start), \
             len << sc);                          \
    } \
  } \
JNI_END
複製代碼

因此除了直接使用ByteBuffer#allocateDirect分配堆外內存以外,無論是BIO和NIO都須要將java堆內存拷貝到native堆(堆外內存)。
固然都不能避免從native堆拷貝到socket buffer(SO_RCVBUF和SO_SNDBUF)。socket

相關文章
相關標籤/搜索