暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

干货分享:Java AIO 网络编程真相(终结篇)

马士兵 2021-07-16
424

前言

本篇文章详细描述了Java AIO的实现原理,读者看完之后就会明白这样一句话:AIO就是选择器+线程池,也即Epoll + ThreadPoolExecutor。

读者可根据需要选择不同的小节进行阅读。

本文所有文字全是手打,没有任何复制,本人写出的文章遵循本心,从不以增加阅读量和人气,在网上进行拷贝拼接,耗费了大量心力,希望有收获的读者,可以帮忙进行转载和点赞,作者争取每一次的发文都能够做到精致。
同时,读者可以将想了解的知识发到评论区,作者会逐步将他们都逐行解释,采取最简单易懂的语言进行描述,希望能帮助到读者理清思路,树立计算机思维。

前几篇内容可以点击下方链接阅读

干货分享:Java AIO 网络编程真相(一)

干货分享:Java AIO 网络编程真相(二)

干货分享:Java AIO 网络编程真相(三)

干货分享:Java AIO 网络编程真相(四)

干货分享:Java AIO 网络编程真相(五)


附录一 Linux 服务端网络编程


    #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h>#define PORT_NUM 8888
    int main(int argc, char *argv[])
    {
    int sockfd,new_fd;
    struct sockaddr_in server_addr; // 服务器地址
    struct sockaddr_in client_addr; // 客户端地址
    int sin_size;
    char hello[]="Hello\n";
    /* 服务器端开始建立sockfd描述符 */
    if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) // 使用AF_INET协议栈代表TCP/IP,SOCK_STREAM表明使用协议栈中的TCP协议
    {
    fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
    exit(1);
    }
    /* 构建服务端的sockaddr结构 */
    bzero(&server_addr,sizeof(struct sockaddr_in)); // 初始化server_addr结构体
    server_addr.sin_family=AF_INET; // 设置协议栈为TCP/IP
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY); // 网络上传输数据使用大端序进行描述,我们需要通过htonl函数将字节序转为大端序,INADDR_ANY 表示可以接收任意IP地址的数据,即绑定到所有的IP
    //server_addr.sin_addr.s_addr=inet_addr("192.168.1.1"); 用于绑定到一个固定IP,inet_addr用于把数字加格式的ip转化为整形ip
    server_addr.sin_port=htons(PORT_NUM); // 设置端口号
    /* 绑定端口信息 */
    if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
    {
    fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
    exit(1);
    }
    /* 设置允许连接的最大客户端数 */
    if(listen(sockfd,5)==-1)
    {
    fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
    exit(1);
    }
    // 循环处理接收到的客户端请求
    while(1)
    {
    /* 服务器阻塞,直到客户程序建立连接 */
    sin_size=sizeof(struct sockaddr_in);
    if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size))==-1)
    {
    fprintf(stderr,"Accept error:%s\n\a",strerror(errno));
    exit(1);
    }
    fprintf(stderr,"Server get connection from %s\n",inet_ntoa(client_addr.sin_addr)); // 将网络地址转换成.字符串,并打印到输出终端


    //向客户端程序写入hello数组里的字符
    if(write(new_fd,hello,strlen(hello))==-1)
    {
    fprintf(stderr,"Write Error:%s\n",strerror(errno));
    exit(1);
    }
    /* 这个通讯已经结束 */
    close(new_fd);
    /* 循环下一个 */
    }
    /* 结束通讯 */
    close(sockfd);
    exit(0);
    }

    附录二 Linux 客户端网络编程


      #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h>#define PORT_NUM 8888  服务端端口号
      int main(int argc, char *argv[])
      {
      int sockfd;
      char buffer[1024];
      struct sockaddr_in server_addr; //描述服务器的地址
      struct hostent *host;
      int nbytes;
      /* 使用hostname查询host名字 */
      if(argc!=2)
      {
      fprintf(stderr,"Usage:%s hostname \a\n",argv[0]);
      exit(1);
      }
      if((host=gethostbyname(argv[1]))==NULL)
      {
      fprintf(stderr,"Gethostname error\n");
      exit(1);
      }
      /* 客户程序开始建立 sockfd描述符 */
      if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) // AF_INET:Internet;SOCK_STREAM:TCP
      {
      fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
      exit(1);
      }
      /* 初始化并设置服务端链接地址 */
      bzero(&server_addr,sizeof(server_addr)); // 初始化,置0
      server_addr.sin_family=AF_INET; // 设置协议栈
      server_addr.sin_port=htons(PORT_NUM); // 端口号
      server_addr.sin_addr=*((struct in_addr *)host->h_addr); // 服务端IP
      /* 链接服务端 */
      if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
      {
      fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
      exit(1);
      }
      /* 读取服务端响应数据 */
      if((nbytes=read(sockfd,buffer,1024))==-1)
      {
      fprintf(stderr,"Read Error:%s\n",strerror(errno));
      exit(1);
      }
      buffer[nbytes]='\0';
      printf("client received:%s\n",buffer);
      /* 结束通讯 */
      close(sockfd);
      exit(0);
      }

      附录三  Java AIO涉及到的Native JNI实现


      1、socket0


        JNIEXPORT int JNICALL
        Java_sun_nio_ch_Net_socket0(JNIEnv *env, jclass cl, jboolean preferIPv6,
        jboolean stream, jboolean reuse)
        {
        int fd;
        int type = (stream ? SOCK_STREAM : SOCK_DGRAM);
        int domain = AF_INET;
        fd = socket(domain, type, 0);
        if (fd < 0) {
        return handleSocketError(env, errno);
        }
        ...
        return fd;
        }

        2、bind0


          JNIEXPORT void JNICALL
          Java_sun_nio_ch_Net_bind0(JNIEnv *env, jclass clazz, jobject fdo, jboolean preferIPv6,
          jboolean useExclBind, jobject iao, int port)
          {
          SOCKADDR sa;
          int sa_len = SOCKADDR_LEN;
          int rv = 0;
          if (NET_InetAddressToSockaddr(env, iao, port, (struct sockaddr *)&sa, &sa_len, preferIPv6) != 0) {
          return;
          }
          rv = NET_Bind(fdval(env, fdo), (struct sockaddr *)&sa, sa_len);
          if (rv != 0) {
          handleSocketError(env, errno);
          }
          }
          JNIEXPORT int JNICALL
          NET_Bind(int s, struct sockaddr *him, int len)
          {
          int rv;
          rv = bind(s, him, len);
          if (rv == SOCKET_ERROR) {
          if (WSAGetLastError() == WSAEACCES) {
          WSASetLastError(WSAEADDRINUSE);
          }
          }
          return rv;
          }

          3、listen


            JNIEXPORT void JNICALL
            Java_sun_nio_ch_Net_listen(JNIEnv *env, jclass cl, jobject fdo, jint backlog)
            {
            if (listen(fdval(env, fdo), backlog) < 0)
            handleSocketError(env, errno);
            }

            4、accept0


              JNIEXPORT jint JNICALL
              Java_sun_nio_ch_ServerSocketChannelImpl_accept0(JNIEnv *env, jobject this,
              jobject ssfdo, jobject newfdo,
              jobjectArray isaa)
              {
              jint ssfd = (*env)->GetIntField(env, ssfdo, fd_fdID);
              jint newfd;
              struct sockaddr *sa;
              int alloc_len;
              jobject remote_ia = 0;
              jobject isa;
              jint remote_port;
              NET_AllocSockaddr(&sa, &alloc_len); // 初始化sockaddr
              for (;;) {
              socklen_t sa_len = alloc_len;
              newfd = accept(ssfd, sa, &sa_len);
              if (newfd >= 0) {
              break;
              }
              if (errno != ECONNABORTED) {
              break;
              }
              }
              // 没有可用TCP三次握手的socket信息
              if (newfd < 0) {
              free((void *)sa);
              if (errno == EAGAIN)
              return IOS_UNAVAILABLE;
              if (errno == EINTR)
              return IOS_INTERRUPTED;
              JNU_ThrowIOExceptionWithLastError(env, "Accept failed");
              return IOS_THROWN;
              }
              // 设置Java对象newfdo的fd_fdID
              (*env)->SetIntField(env, newfdo, fd_fdID, newfd);
              // 获取远程client信息
              remote_ia = NET_SockaddrToInetAddress(env, sa, (int *)&remote_port);
              free((void *)sa); // 释放用于接收的sockaddr
              // 创建isa_class指定的Java对象,这里对应于InetSocketAddress,可以参考前面的源码描述
              isa = (*env)->NewObject(env, isa_class, isa_ctorID,
              remote_ia, remote_port);
              // 设置数组元素
              (*env)->SetObjectArrayElement(env, isaa, 0, isa);
              return 1;
              }

              关于作者:

              ——————
              进行业交流群
              👇推荐关注👇
              有趣的行业资讯
              干货技术分享
              程序员的日常生活
              ......
              干就完了

              文章转载自马士兵,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

              评论