strace命令_【IT新手之路】strace命令监听Java Nio Server内核调用过程.md

15c24216466a154bbd56d87f0717f4cc.png

cff4c1c1f6169b95564ae6a6726569c2.png

Java Nio Server

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.Date;import java.util.Iterator;public class NioServer implements Runnable {    private Selector selector;    private ServerSocketChannel serverSocketChannel;    public static void main(String[] args) {        new Thread(new NioServer(8765), "NioServer-001").start();    }    public NioServer(int port) {        try {            selector = Selector.open();            serverSocketChannel = ServerSocketChannel.open();            serverSocketChannel.configureBlocking(false);            serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);            serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);            System.out.println("Server start, port :" + port);        } catch (IOException e) {            e.printStackTrace();            System.exit(1);        }    }    @Override    public void run() {        while(true){            try {                selector.select();                Iterator iterator = selector.selectedKeys().iterator();                SelectionKey key = null;                while(iterator.hasNext()) {                    key = iterator.next();                    iterator.remove();                    try {                        handleEvent(key);                    } catch (IOException e) {                        if (key != null) {                            key.cancel();                            if (key.channel() != null) {                                key.channel().close();                            }                        }                    }                }            } catch (IOException e) {                e.printStackTrace();            }        }    }    private void handleEvent(SelectionKey key) throws IOException {        if (key.isValid()) {            if (key.isAcceptable()) {                handleAccept(key);            }            if (key.isReadable()) {                handleRead(key);            }        }    }    private void handleAccept(SelectionKey key) throws IOException {        System.out.println("connect accept,time" + System.currentTimeMillis());        ServerSocketChannel ssc =  (ServerSocketChannel) key.channel();        SocketChannel sc = ssc.accept();        sc.configureBlocking(false);        sc.register(this.selector, SelectionKey.OP_READ);    }    private void handleRead(SelectionKey key) throws IOException {        SocketChannel sc = (SocketChannel) key.channel();        ByteBuffer readBuffer = ByteBuffer.allocate(1024);        int readBytes = sc.read(readBuffer);        if (readBytes > 0) {            readBuffer.flip();            byte[] bytes = new byte[readBuffer.remaining()];            readBuffer.get(bytes);            String body = new String(bytes).trim();            System.out.println("server recive body:" + body);            String response = "currentTime=" + new Date().toString();            doWrite(sc, response);        } else if (readBytes < 0) {            key.cancel();            sc.close();        }    }    private void doWrite(SocketChannel socketChannel, Strin response) throws IOException {        byte[] bytes = response.getBytes();        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);        writeBuffer.put(bytes);        writeBuffer.flip();        socketChannel.write(writeBuffer);        if (!writeBuffer.hasRemaining()) {            System.out.println("response 2 client succeed:" + response);        }    }
让我们使用strace命令,来看看Nio Server端的代码工作中内核的系统调用过程:

服务器版本: CentOS Linux release 7.6.1810

strace: 跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间;

strace命令参数:

-ff 如果提供-o filename,则所有进程的跟踪结果输出到相应的filename.pid中,pid是各进程的进程号.

-o filename 将strace的输出写入文件filename

推荐使用man命令自行查阅;

1. 执行strace命令,启动NioServer

# ~/kernelout/ss是系统调用过程输出的文件路径~$ strace -ff -o kernelout/ss java NioServerServer start, port :8765

可以看到输出了Server start, port :8765,说NioServer启动成功;

2. 打开新的终端,进入~/kernelout/,查看输出文件内容

~$ cd kernelout~/kernelout$ ll-rw-r--r-- 1 user00 user00   9472 Jun 29 11:15 ss.23922-rw-r--r-- 1 user00 user00 219262 Jun 29 11:15 ss.23923-rw-r--r-- 1 user00 user00    863 Jun 29 11:15 ss.23924-rw-r--r-- 1 user00 user00    961 Jun 29 11:15 ss.23925-rw-r--r-- 1 user00 user00  25170 Jun 29 11:17 ss.23926-rw-r--r-- 1 user00 user00   1095 Jun 29 11:15 ss.23927-rw-r--r-- 1 user00 user00   1081 Jun 29 11:15 ss.23928-rw-r--r-- 1 user00 user00    942 Jun 29 11:15 ss.23929-rw-r--r-- 1 user00 user00  12646 Jun 29 11:17 ss.23930-rw-r--r-- 1 user00 user00  11210 Jun 29 11:17 ss.23931-rw-r--r-- 1 user00 user00    942 Jun 29 11:15 ss.23932-rw-r--r-- 1 user00 user00 483374 Jun 29 11:17 ss.23933-rw-r--r-- 1 user00 user00   1562 Jun 29 11:15 ss.23934

可以看到有很多ss文件,那首先看哪个文件呢?

3. 找到java NioServer 主进程的PID

~/kernelout$ ps -ef | grep javauser00   23920  4120  0 11:15 pts/0    00:00:01 strace -ff -o kernel/ss java NioServeruser00   23922 23920  0 11:15 pts/0    00:00:00 java NioServer
可以看到 java NioServer 命令启动的java进程PID是23922,所以,我们首先看下ss.23922这个文件。

根据man strace: -o filename Write the trace output to the file filename rather than to stderr. Use filename.pid if -ff is used.

4. ss.23922文件内容

clone(child_stack=0x7f5f40316fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f5f403179d0, tls=0x7f5f40317700, child_tidptr=0x7f5f403179d0) = 23923

还记得我们的java代码里,启动了一个新线程NioServer-001么,这里:

new Thread(new NioServer(8765), "NioServer-001").start();

其实就对应着上面ss.23922文件中的clone()函数,并返回线程id23923;

5. 接下来看ss.23923文件

java代码里,有一个绑定ip,port的方法

serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);

我们用端口号8765去文件ss.23923中搜索,可以发现:

bind(9, {sa_family=AF_INET, sin_port=htons(8765), sin_addr=inet_addr("0.0.0.0")}, 16) = 0listen(9, 1024)

bind函数的第一个参数是socket file description,那这个socket是什么时候创建的呢,我们继续在ss.23923查找后发现:

# ipv6socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 6# ipv4socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 9

很明显,因为我们绑定的是ipv4的ip,所以bind()函数里的socketfd就是9;

并且listen()函数说明开启里对fd=9的socket的监听;

我们也能看到epoll系列函数的出现:

epoll_create(256)                       = 8

它其实就对应这行Java代码,表示创建一个多路复用器

selector = Selector.open();

接下来,ss.23923文件里,似乎除了创建线程,也没做其他什么值得我们关注的事情了,我们将目光转向到其他ss.pid文件;

6. ss.23934

我们在ss.23934文件中,又发现了epoll系列函数 ;

为什么到目前为止,除了ss.23923外,只有这一个文件出现了epoll系列,这个任务委派关系是如何建立的,其他线程又是用来做什么的;这些问题,我还在学习中;

epoll_ctl(8, EPOLL_CTL_ADD, 9, {EPOLLIN, {u32=9, u64=4538492564453457929}}) = 0epoll_wait(8,

这里epoll_ctl函数表示将fd为9的socket的读事件注册到fd为8的多路复用器上,而epoll_wait函数用来阻塞等待事件发生;就对应下面两行Java代码

serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);selector.select();

7. 打开新的终端,使用nc命令,与NioServer建立连接

~$ nc 127.0.0.1 8765

有打印内容connect accept,time1593401891111,表示client与Server已建立好了连接;

~$ strace -ff -o kernelout/ss java NioServerServer start, port :8765connect accept,time1593401891111

这时,我们再去看ss.23934文件

epoll_wait(8, [{EPOLLIN, {u32=9, u64=4538492564453457929}}], 8192, -1) = 1accept(9, {sa_family=AF_INET, sin_port=htons(35744), sin_addr=inet_addr("127.0.0.1")}, [16]) = 11epoll_ctl(8, EPOLL_CTL_ADD, 11, {EPOLLIN, {u32=11, u64=4521727116663848971}}) = 0epoll_wait(8,

发现之前的epoll_wait函数有返回了,就是因为有客户端连接进来了,然后accept函数接收这个连接,生成了另一个fd为11的socket;然后就将fd为11的socket的读事件注册到fd为8的多路复用器上,然后继续阻塞等待事件发生

对应Java代码

SocketChannel sc = ssc.accept();sc.register(this.selector, SelectionKey.OP_READ);

8. client发送消息

在client端随便输入些内容,传输给NIOServer;我们能在ss.23934文件看到,通过read函数读取fd为11的scoket的内容,即client发送的内容;并写回一些内容;

read(11, "dfsdfsf\n", 1024)             = 8write(1, "server recive body:sdfsdf", 25) = 25write(1, "\n", 1)                       = 1epoll_wait(8,

这里的read、write函数,对应我们的Java代码

int readBytes = sc.read(readBuffer); socketChannel.write(writeBuffer);

从上面的操作步骤,我们完成了一次client与server端的网络通信,大致了解了整个通信的过程中涉及到一些内核系统调用;有兴趣到同学可以自行操作下。

a93424520e066e6111e2b1d66567f2aa.png

bd1e15b3eaff943c01df8943ed429fdb.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值