![4c29ba70862bd928483d8cb81ef1cbe2.png](https://i-blog.csdnimg.cn/blog_migrate/ee5a4d185a47722873cf9f168e34f5af.jpeg)
本次测试用的是center os7/64,jdk版本jdk1.8.0_261。
测试系统调用,最简单的代码就是IO。
![2bb8ce63a6175cedc91ea872ba4a6996.png](https://i-blog.csdnimg.cn/blog_migrate/38fb1f1cb971e6df1d654d7cd45e5715.jpeg)
代码:
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](https://i-blog.csdnimg.cn/blog_migrate/46ccce5354503d0e8283d37a64771efe.png)
使用vi打开68397,这儿我是猜的,因为这个文件比较大
通过/9990搜索
![1f3a0f0b8bc1a331ae17f558ef1c68bf.png](https://i-blog.csdnimg.cn/blog_migrate/100c9df2b4fd1337e1850a4a7cf63643.jpeg)
我们以可以通过查看网络来看当前程序监听状态
查看java进程:jps
查看网络链接状态:netstat -natp
![6e62a42a80c6fc5c50b89fa131831cd1.png](https://i-blog.csdnimg.cn/blog_migrate/4fcf2cac4411c724e09fc43bf58a4193.png)
我们使用命令:nc localhost 9990链接服务端
然后再查看日志文件和网络监听
![c34fa7b89d057a2cda7b33c8ce7c59d5.png](https://i-blog.csdnimg.cn/blog_migrate/1d2aa966e39748c7de6e88b7f31733e1.jpeg)
很明显多了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](https://i-blog.csdnimg.cn/blog_migrate/f6adb679a7af672c5e316e53cb224532.jpeg)
代码:
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](https://i-blog.csdnimg.cn/blog_migrate/f442291129a4e9133351bf45304c5fd9.jpeg)
查看说明:
[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](https://i-blog.csdnimg.cn/blog_migrate/10f1e63a665dc098003d39d75d7483eb.jpeg)
![b53419cc827224151825ab054ef4447a.png](https://i-blog.csdnimg.cn/blog_migrate/321bad535e8120c46ca7935bc2c1b9da.jpeg)
![328e61477bfdc7b9fde8a16538330ad6.png](https://i-blog.csdnimg.cn/blog_migrate/952d70410c0a632447763e0e02a0913b.jpeg)
c10k问题
http://www.kegel.com/c10k.html