accept系统调用_如何追踪java系统调用的过程?

4c29ba70862bd928483d8cb81ef1cbe2.png

本次测试用的是center os7/64,jdk版本jdk1.8.0_261。

测试系统调用,最简单的代码就是IO。

2bb8ce63a6175cedc91ea872ba4a6996.png

代码:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Create BY poplar ON 2020/9/17
 */
public class SocketTest {

    public static void main(String[] args) throws IOException {
        ServerSocket socket = new ServerSocket(9990, 19);
        System.out.println("ServerSocket启动.....");
        while (true) {
            Socket client = socket.accept();
            System.out.println("获取到客服端链接,端口:" + client.getPort());
            new Thread(() -> {
                InputStream in = null;
                BufferedReader stream = null;
                try {
                    in = client.getInputStream();
                    stream = new BufferedReader(new InputStreamReader(in));
                    while (true) {
                        String line = stream.readLine();
                        if (line != null) {
                            System.out.println(line);
                        } else {
                            client.close();
                            break;
                        }
                    }
                    System.out.println("客服端已断开...");
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        in.close();
                        stream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

具体命令:

编译:

/usr/local/java/jdk1.8.0_261/bin/javac SocketTest.java

执行的时候使用:

strace -ff -o out /usr/local/java/jdk1.8.0_261/bin/java SocketTest

这样执行完毕之后我们会看到一堆文件

c7a83d1af061eaaef9a7e1c50ae39833.png

使用vi打开68397,这儿我是猜的,因为这个文件比较大

通过/9990搜索

1f3a0f0b8bc1a331ae17f558ef1c68bf.png

我们以可以通过查看网络来看当前程序监听状态

查看java进程:jps

查看网络链接状态:netstat -natp

6e62a42a80c6fc5c50b89fa131831cd1.png

我们使用命令:nc localhost 9990链接服务端

然后再查看日志文件和网络监听

c34fa7b89d057a2cda7b33c8ce7c59d5.png

很明显多了70开头的日志文件

如果想查看Linux里面某个方法代表啥意思,可以如下操作:

[root@localhost test]# man 3 bind
BIND(3P)                                                                               POSIX Programmer's Manual                                                                              BIND(3P)

PROLOG
       This  manual page is part of the POSIX Programmer's Manual.  The Linux implementation of this interface may differ (consult the corresponding Linux manual page for details of Linux behavior),
       or the interface may not be implemented on Linux.

NAME
       bind - bind a name to a socket

SYNOPSIS
       #include <sys/socket.h>

       int bind(int socket, const struct sockaddr *address,
              socklen_t address_len);

DESCRIPTION
       The bind() function shall assign a local socket address address to a socket identified by descriptor socket that has no local socket address assigned. Sockets created with the socket()  func‐
       tion are initially unnamed; they are identified only by their address family.

       The bind() function takes the following arguments:

       socket Specifies the file descriptor of the socket to be bound.

       address
              Points to a sockaddr structure containing the address to be bound to the socket. The length and format of the address depend on the address family of the socket.

       address_len
              Specifies the length of the sockaddr structure pointed to by the address argument.

       The socket specified by socket may require the process to have appropriate privileges to use the bind() function.

RETURN VALUE
       Upon successful completion, bind() shall return 0; otherwise, -1 shall be returned and errno set to indicate the error.

ERRORS
       The bind() function shall fail if:

       EADDRINUSE
              The specified address is already in use.

       EADDRNOTAVAIL
              The specified address is not available from the local machine.

       EAFNOSUPPORT
              The specified address is not a valid address for the address family of the specified socket.

       EBADF  The socket argument is not a valid file descriptor.

       EINVAL The socket is already bound to an address, and the protocol does not support binding to a new address; or the socket has been shut down.

       ENOTSOCK
              The socket argument does not refer to a socket.

       EOPNOTSUPP
              The socket type of the specified socket does not support binding to an address.

       If the address family of the socket is AF_UNIX, then bind() shall fail if:

       EACCES A component of the path prefix denies search permission, or the requested name requires writing in a directory with a mode that denies write permission.

       EDESTADDRREQ or EISDIR
              The address argument is a null pointer.

       EIO    An I/O error occurred.

       ELOOP  A loop exists in symbolic links encountered during resolution of the pathname in address.
 Manual page bind(3p) line 1 (press h for help or q to quit)

也可以监控文件

[root@localhost test]# tail -f out.18847
mprotect(0x7f41d0107000, 4096, PROT_READ|PROT_WRITE) = 0
mprotect(0x7f41d0108000, 4096, PROT_READ|PROT_WRITE) = 0
mmap(NULL, 1052672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7f41c08d4000
clone(child_stack=0x7f41c09d3fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f41c09d49d0, tls=0x7f41c09d4700, child_tidptr=0x7f41c09d49d0) = 18858
futex(0x7f41d000a554, FUTEX_WAIT_PRIVATE, 23, NULL) = 0
futex(0x7f41d000a528, FUTEX_WAIT_PRIVATE, 2, NULL) = 0
futex(0x7f41d000a528, FUTEX_WAKE_PRIVATE, 1) = 0
write(1, "Server Start.....", 17)       = 17
write(1, "n", 1)                       = 1
read(0, 

java NIO系统底层实现

39b4eb77cb1ea6f9ca080cd750e2d9b1.png

代码:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Create BY poplar ON 2020/9/17
 */
public class NioTest {

    public static void main(String[] args) throws IOException, InterruptedException {
        List<SocketChannel> queue = new LinkedList<>();
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.bind(new InetSocketAddress(9990));
        serverSocket.configureBlocking(false);//只让接受客服端,不足赛
        while (true) {
            TimeUnit.SECONDS.sleep(1);
            //接受客服端链接
            SocketChannel client = serverSocket.accept();
            if (client == null) {
                System.out.println("client is null...");
            } else {
                client.configureBlocking(false);//设置客服端为非阻塞
                System.out.println("client: " + client.socket().getPort());
                queue.add(client);
            }
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
            for (SocketChannel c : queue) {
                int num = c.read(buffer);//>0, 0, -1
                if (num > 0) {
                    buffer.flip();
                    byte[] bytes = new byte[buffer.limit()];
                    buffer.get(bytes);
                    String string = new String(bytes);
                    System.out.println("receive form "+c.socket().getPort()+ "data: "+string);
                    buffer.clear();
                }
            }
        }
    }
}

日志文件分析

2830 read(3, "3123762722760000040%n0602610027n030031n0503270337"..., 711) = 711
   //得到socket 4
   2831 socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 4 

   2832 setsockopt(4, SOL_IPV6, IPV6_V6ONLY, [0], 4) = 0
   2833 setsockopt(4, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
   
  //绑定和监听4
   2854 bind(4, {sa_family=AF_INET6, sin6_port=htons(9990), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
   2855 listen(4, 50)     
                      = 0
   2856 getsockname(4, {sa_family=AF_INET6, sin6_port=htons(9990), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 0
   2857 getsockname(4, {sa_family=AF_INET6, sin6_port=htons(9990), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 0
   

  2858 fcntl(4, F_GETFL)                       = 0x2 (flags O_RDWR)
   //设置文件描述符4为O_NONBLOCK
  2859 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK)    = 0

   3010 write(1, "n", 1)                       = 1
   3011 mprotect(0x7fbe980dd000, 4096, PROT_READ|PROT_WRITE) = 0
   3012 futex(0x7fbe9800a354, FUTEX_WAIT_BITSET_PRIVATE, 1, {27934, 805097894}, ffffffff) = -1 ETIMEDOUT (Connection timed out)
   3013 futex(0x7fbe9800a328, FUTEX_WAKE_PRIVATE, 1) = 0

//接受客服端链接,没有就返回 -1
   3014 accept(4, 0x7fbe980d24b0, 0x7fbe9e19f754) = -1 EAGAIN (Resource temporarily unavailable)
  
//得到客服端链接,返回5
   3037 accept(4, {sa_family=AF_INET6, sin6_port=htons(51850), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 5
   3038 fcntl(5, F_GETFL)                       = 0x2 (flags O_RDWR)
  
   3065 lseek(3, 59681650, SEEK_SET)            = 59681650
   3066 read(3, "31237627227600000401770v70f70r105write1033(["..., 324) = 324
   3067 getsockname(5, {sa_family=AF_INET6, sin6_port=htons(9990), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 0
   3068 getsockname(5, {sa_family=AF_INET6, sin6_port=htons(9990), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 0
   3069 fcntl(5, F_GETFL)                       = 0x2 (flags O_RDWR)
//把文件操作符5设为 O_NONBLOCK
   3070 fcntl(5, F_SETFL, O_RDWR|O_NONBLOCK)    = 0
   3071 lseek(3, 49142508, SEEK_SET)            = 49142508

从上面的截图日志我们可以看出,我们大量的系统调用,假设我们有10000个客服端和服务端链接,只有其中几个和服务端在传输数据,但是却产生了大量的系统调用,严重影响性能,就像收费站员工,假设有1000条路,收费站员工来回跑看那条路有车来了,这个人会被累死。

此时就需要另外一个东西来帮助解决这个问题,那就是多路复用器

c729739ad9080b12f535f80c3de01ef5.png

查看说明:

[root@localhost test]# man 2 select
SELECT(2)                                                                              Linux Programmer's Manual                                                                             SELECT(2)

NAME
       select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing

SYNOPSIS
       /* According to POSIX.1-2001 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

       #include <sys/select.h>

       int pselect(int nfds, fd_set *readfds, fd_set *writefds,
                   fd_set *exceptfds, const struct timespec *timeout,
                   const sigset_t *sigmask);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       pselect(): _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600

DESCRIPTION
       select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g., input pos‐
       sible).  A file descriptor is considered ready if it is possible to perform the corresponding I/O operation (e.g., read(2)) without blocking.

       The operation of select() and pselect() is identical, other than these three differences:

       (i)    select() uses a timeout that is a struct timeval (with seconds and microseconds), while pselect() uses a struct timespec (with seconds and nanoseconds).

       (ii)   select() may update the timeout argument to indicate how much time was left.  pselect() does not change this argument.

       (iii)  select() has no sigmask argument, and behaves as pselect() called with NULL sigmask.

       Three independent sets of file descriptors are watched.  Those listed in readfds will be watched to see if characters become available for reading (more precisely, to see if a read  will  not
       block;  in  particular,  a file descriptor is also ready on end-of-file), those in writefds will be watched to see if a write will not block, and those in exceptfds will be watched for excep‐
       tions.  On exit, the sets are modified in place to indicate which file descriptors actually changed status.  Each of the three file descriptor sets  may  be  specified  as  NULL  if  no  file
       descriptors are to be watched for the corresponding class of events.

       Four  macros are provided to manipulate the sets.  FD_ZERO() clears a set.  FD_SET() and FD_CLR() respectively add and remove a given file descriptor from a set.  FD_ISSET() tests to see if a
       file descriptor is part of the set; this is useful after select() returns.

       nfds is the highest-numbered file descriptor in any of the three sets, plus 1.

       The timeout argument specifies the minimum interval that select() should block waiting for a file descriptor to become ready.  (This interval will be rounded up to the system clock  granular‐
       ity,  and kernel scheduling delays mean that the blocking interval may overrun by a small amount.)  If both fields of the timeval structure are zero, then select() returns immediately.  (This
       is useful for polling.)  If timeout is NULL (no timeout), select() can block indefinitely.

       sigmask is a pointer to a signal mask (see sigprocmask(2)); if it is not NULL, then pselect() first replaces the current signal mask by the one pointed to by sigmask, then does  the  "select"
       function, and then restores the original signal mask.

epoll,多路复用器

package com.poplar.test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Create BY poplar ON 2020/9/18
 * 多路复用的多线程版
 */
public class MultipleSelectorTest {
    Selector selector1 = null;
    Selector selector2 = null;
    Selector selector3 = null;
    ServerSocketChannel serverSocketChannel;

    public void initServer() {

        try {
            selector1 = Selector.open();
            selector2 = Selector.open();
            selector3 = Selector.open();
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.bind(new InetSocketAddress(9990));
            serverSocketChannel.register(selector1, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        MultipleSelectorTest service = new MultipleSelectorTest();
        service.initServer();
        HandleTreadTest test1 = new HandleTreadTest(service.selector1, 2);
        HandleTreadTest test2 = new HandleTreadTest(service.selector2);

        HandleTreadTest test3 = new HandleTreadTest(service.selector3);
        test1.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        test2.start();
        test3.start();
        System.out.println("Server Start.....");
        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class HandleTreadTest extends Thread {
    static BlockingQueue<SocketChannel>[] queue;
    static AtomicInteger idx = new AtomicInteger();
    int id = 0;
    Selector selector = null;

    int selectors = 2;

    boolean isBoss = false;

    HandleTreadTest(Selector selector, int n) {
        this.selector = selector;
        this.selectors = n;
        this.isBoss = true;
        queue = new LinkedBlockingQueue[selectors];
        for (int i = 0; i < selectors; i++) {
            queue[i] = new LinkedBlockingQueue();
        }
        System.out.println("Boss start...");
    }

    HandleTreadTest(Selector selector) {
        isBoss = false;
        this.selector = selector;
        id = idx.getAndIncrement() % selectors;
        System.out.println("Worker start..." + id);
    }

    @Override
    public void run() {
        try {
            while (true) {
                while (selector.select(10) > 0) {
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while (iterator.hasNext()) {
                        SelectionKey selectionKey = iterator.next();
                        //避免重复
                        iterator.remove();
                        if (selectionKey.isAcceptable()) {
                            handleAccept(selectionKey);
                        } else if (selectionKey.isReadable()) {
                            handleRead(selectionKey);
                        }
                    }
                }
                if (!isBoss && !queue[id].isEmpty()) {
                    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
                    SocketChannel socketChannel = queue[id].take();
                    socketChannel.register(selector, SelectionKey.OP_READ, buffer);
                    System.out.println("new socketChannel" + socketChannel.getRemoteAddress());
                }
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void handleAccept(SelectionKey selectionKey) {
        try {
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            int num = idx.getAndIncrement() % selectors;
            queue[num].add(socketChannel);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private void handleRead(SelectionKey selectionKey) {
        SocketChannel channel = (SocketChannel) selectionKey.channel();
        ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
        buffer.clear();
        int read = 0;

        try {
            while (true) {
                read = channel.read(buffer);
                if (read > 0) {
                    buffer.flip();
                    while (buffer.hasRemaining()) {
                        channel.write(buffer);
                    }
                    buffer.clear();
                } else if (read == 0) {
                    break;
                } else {
                    //返回-1时候可能客服端已经断开链接了
                    channel.close();
                    break;
                }
                //System.out.println("crazy output...");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

f28dedc94b6ff01ca4697e7abebc5980.png

b53419cc827224151825ab054ef4447a.png

328e61477bfdc7b9fde8a16538330ad6.png

c10k问题

http://www.kegel.com/c10k.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值