乐趣区

关于后端:NIO源码JavaNIO源码-JNI分析一linux-API介绍

最近重温网络的时候,忽然发现,底层就那么些接口,java 必定也是封装了底层接口,看过我后面 Nio 相干的小伙伴必定晓得对这些类有点影响(Buffer,Channel,Selector,SelectionKey),可是跟底层对应不起来啊,这一篇就透过源码看一下,大略能帮忙你更好的理解这几个类,及底层的实现。并重个人兴趣向整顿,如有不适,欢送吐槽

Linux 网络编程

查阅材料的时候,发现 wiki 百科讲的曾经非常好了,我先贴下原文 Berkeley 套接字,相当完满的形容了 Socket 相干 Api 介绍及 demo 演示,因为,大学学的 c 语言都快还给老师了,写个 demo 未然不太事实,这里就臭不要脸的套用 wiki 百科的 demo,c 语言解说局部,如果有谬误欢送指出~~。

上面是精简版本的 linux 网络编程,具体版能够参考链接。

LinuxAPI

socket()

  • socket() 创立一个新的确定类型的套接字,返回套接字。

    • api: int socket(int domain, int type, int protocol);
    • 参数:

      • domain: 为创立的套接字指定协定集 eg. IPV4
      • type: socket 类型 eg. 流,数据报文
      • protocol: 理论传输协定 eg. TCP,UDP

        bind()

  • bind() 为一个套接字调配地址。当应用 socket() 创立套接字后,只赋予其所应用的协定,并未调配地址。在承受其它主机的连贯前,必须先调用 bind()为套接字调配一个地址。

    • api: int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
    • 参数:

      • sockfd: 套接字描述符,下面返回的套接字
      • my_addr: 指向 sockaddr 构造(用于示意所调配地址)的指针
      • addrlen: 用 socklen_t 字段指定了 sockaddr 构造的长度

        listen()

  • listen() 当 socket 和一个地址绑定之后,listen()函数会开始监听可能的连贯申请。然而,这只能在有牢靠数据流保障的时候应用,例如:数据类型(SOCK_STREAMSOCK_SEQPACKET)。

    • api:int listen(int sockfd, int backlog);
    • 参数:

      • sockfd: 套接字描述符,下面返回的套接字
      • backlog: 实现三次握手、期待 accept 的全连贯的队列的最大长度下限。

accept()

  • accept()当应用程序监听来自其余主机的面对数据流的连贯时,通过事件(比方 Unix select()零碎调用)告诉它。必须用 accept()函数初始化连贯。Accept() 为每个连贯创建新的套接字并从监听队列中移除这个连贯。它应用如下参数:

    • api:int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
    • 参数:

      • sockfd: 监听的套接字描述符
      • cliaddr: 指向 sockaddr 构造体的指针,客户机地址信息。
      • addrlen: 指向 socklen_t的指针,确定客户机地址构造体的大小。

connect()

  • connect() 零碎调用为一个套接字设置连贯,参数有文件描述符和主机地址。链接到指定地址

    • api:int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
    • 参数:

      • sockfd: 监听的套接字描述符
      • serv_addr: 指向 sockaddr 构造体的指针,服务器地址信息。
      • addrlen: 指向 socklen_t的指针,确定服务器地址构造体的大小。

select()

  • select(): 在一段指定的工夫内,监听用户感兴趣的文件描述符上可读、可写和异样等事件

    • api:int select (int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout);
    • 参数:

      • nfds:没有用,仅仅为与伯克利 Socket 兼容而提供。
      • readfds:指定一个 Socket 数组,select 查看该数组中的所有 Socket。如果胜利返回,则 readfds 中寄存的是合乎‘可读性’条件的数组成员(如缓冲区中有可读的数据)。
      • writefds:指定一个 Socket 数组,select 查看该数组中的所有 Socket。如果胜利返回,则 writefds 中寄存的是合乎‘可写性’条件的数组成员(包含连贯胜利)。
      • exceptfds:指定一个 Socket 数组,select 查看该数组中的所有 Socket。如果胜利返回,则 cxceptfds 中寄存的是合乎‘有异样’条件的数组成员(包含连贯接失败)。
      • timeout:指定 select 执行的最长工夫,如果在 timeout 限定的工夫内,readfds、writefds、exceptfds 中指定的 Socket 没有一个符合要求,就返回 0。

poll()

  • poll()用于查看套接字的状态。套接字能够被测试,看是否能够写入、读取或是有谬误。

    • api:int poll(struct pollfd * fds , nfds_t nfds , int timeout);
    • 参数:

      • fds是 pollfd 构造体指针
      • nfdsnfds 是描述符个数,构造体 pollfd 数组元素的个数
      • timeout: 参数设置为 - 1 时,示意永远阻塞期待。0 示意立刻返回,不阻塞。大于 0 时,示意期待指定数目的毫秒数。

fcntl()

  • fcntl()关上文件描述符,具体操作由 cmd 决定

    • api:int fcntl(int fd , int cmd , ... /* arg */);
    • 参数

      • fd:文件描述符
      • cmd:操作指令

epoll_create()

  • epoll_create()在内核中创立 epoll 实例并返回一个 epoll 文件描述符。

    • api:int epoll_create(int size);
    • 参数:

      • size: 而当初 size 曾经没有这种语义了,然而调用者调用时 size 仍然必须大于 0,以保障后向兼容性。

epoll_ctl()

  • epoll_ctl()向 epfd 对应的内核epoll 实例增加、批改或删除对 fd 上事件 event 的监听。

    • api:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    • 参数

      • epfdepoll 构造体
      • opcud 对应的事件枚举
      • fd文件描述符
      • events程度触发 or 边缘触发

epoll_wait()

  • epoll_wait()期待其治理的连贯上的 IO 事件

    • api:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    • 参数

      • epfdepoll 构造体
      • eventsepoll_event 构造体指针
      • maxevents最多返回多少事件
      • timeout当 timeout 为 0 时,epoll_wait 永远会立刻返回。而 timeout 为 -1 时,epoll_wait 会始终阻塞直到任一已注册的事件变为就绪。当 timeout 为一正整数时,epoll 会阻塞直到计时 timeout 毫秒完毕或已注册的事件变为就绪。

send()和 recv(), 或者 write()和 read(), 或者 recvfrom()和 sendto()等

  • 用于往 / 从近程套接字发送和承受数据

相干 api 大抵如上,如有更精密的能够自行搜寻。

demo

BIO

  /* Server code in C */
     
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  
  int main(void)
  {
    struct sockaddr_in stSockAddr;
    int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  
    if(-1 == SocketFD)
    {perror("can not create socket");
      exit(EXIT_FAILURE);
    }
  
    memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
  
    stSockAddr.sin_family = AF_INET;
    stSockAddr.sin_port = htons(1100);
    stSockAddr.sin_addr.s_addr = INADDR_ANY;
  
    if(-1 == bind(SocketFD,(const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in)))
    {perror("error bind failed");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }
  
    if(-1 == listen(SocketFD, 10))
    {perror("error listen failed");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }
  
    for(;;)
    {int ConnectFD = accept(SocketFD, NULL, NULL);
  
      if(0 > ConnectFD)
      {perror("error accept failed");
        close(SocketFD);
        exit(EXIT_FAILURE);
      }
  
     /* perform read write operations ... */
  
      shutdown(ConnectFD, SHUT_RDWR);
  
      close(ConnectFD);
    }

    close(SocketFD);
    return 0;
  }
 /* Client code in C */

  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  
  int main(void)
  {
    struct sockaddr_in stSockAddr;
    int Res;
    int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  
    if (-1 == SocketFD)
    {perror("cannot create socket");
      exit(EXIT_FAILURE);
    }
  
    memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
  
    stSockAddr.sin_family = AF_INET;
    stSockAddr.sin_port = htons(1100);
    Res = inet_pton(AF_INET, "192.168.1.3", &stSockAddr.sin_addr);
  
    if (0 > Res)
    {perror("error: first parameter is not a valid address family");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }
    else if (0 == Res)
    {perror("char string (second parameter does not contain valid ipaddress");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }

    if (-1 == connect(SocketFD, (const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in)))
    {perror("connect failed");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }
  
    /* perform read write operations ... */
  
    shutdown(SocketFD, SHUT_RDWR);
  
    close(SocketFD);
    return 0;
  }

NIO

#include<stdio.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<ctype.h>
#define MAXLEN 1024
#define SERV_PORT 8000
#define MAX_OPEN_FD 1024

int main(int argc,char *argv[])
{
    int  listenfd,connfd,efd,ret;
    char buf[MAXLEN];
    struct sockaddr_in cliaddr,servaddr;
    socklen_t clilen = sizeof(cliaddr);
    struct epoll_event tep,ep[MAX_OPEN_FD];

    listenfd = socket(AF_INET,SOCK_STREAM,0);

    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);
    bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    listen(listenfd,20);
    // 创立一个 epoll fd
    efd = epoll_create(MAX_OPEN_FD);
    tep.events = EPOLLIN;tep.data.fd = listenfd;
    // 把监听 socket 先增加到 efd 中
    ret = epoll_ctl(efd,EPOLL_CTL_ADD,listenfd,&tep);
    // 循环期待
    for (;;)
    {
        // 返回已就绪的 epoll_event,- 1 示意阻塞, 没有就绪的 epoll_event, 将始终期待
        size_t nready = epoll_wait(efd,ep,MAX_OPEN_FD,-1);
        for (int i = 0; i < nready; ++i)
        {
            // 如果是新的连贯, 须要把新的 socket 增加到 efd 中
            if (ep[i].data.fd == listenfd )
            {connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen);
                tep.events = EPOLLIN;
                tep.data.fd = connfd;
                ret = epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&tep);
            }
            // 否则, 读取数据
            else
            {connfd = ep[i].data.fd;
                int bytes = read(connfd,buf,MAXLEN);
                // 客户端敞开连贯
                if (bytes == 0){ret =epoll_ctl(efd,EPOLL_CTL_DEL,connfd,NULL);
                    close(connfd);
                    printf("client[%d] closed\n", i);
                }
                else
                {for (int j = 0; j < bytes; ++j)
                    {buf[j] = toupper(buf[j]);
                    }
                    // 向客户端发送数据
                    write(connfd,buf,bytes);
                }
            }
        }
    }
    return 0;
}

极度精简版本

nio 曾经很精简了就不多废话了

server

 int main(void)
  {
    struct sockaddr_in stSockAddr;
    // 创立套接字
    int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    // 构造体初始化
    memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
    stSockAddr.sin_family = AF_INET;
    stSockAddr.sin_port = htons(1100);
    stSockAddr.sin_addr.s_addr = INADDR_ANY;
    // 绑定地址
    bind(SocketFD,(const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in));
    // 监听申请
    listen(SocketFD, 10);
    // 承受申请
    int ConnectFD = accept(SocketFD, NULL, NULL);

    //do something
    // 敞开
    close(ConnectFD);
    // 敞开
    close(SocketFD);
    return 0;
  }

client

  int main(void)
  {
    struct sockaddr_in stSockAddr;
    int Res;
    int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
  
    stSockAddr.sin_family = AF_INET;
    stSockAddr.sin_port = htons(1100);
    //IP 地址转换
    Res = inet_pton(AF_INET, "192.168.1.3", &stSockAddr.sin_addr);
    // 连贯地址
    connect(SocketFD, (const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in))
    shutdown(SocketFD, SHUT_RDWR);
  
    close(SocketFD);
    return 0;
  }

参考文章

Berkeley 套接字:https://zh.wikipedia.org/wiki…

epoll:https://zh.wikipedia.org/wiki…

linux 文档:https://man7.org/linux/man-pa…

退出移动版