tomcat请求处理分析(三) 绑定本地端口监听请求
2017-08-19 00:00
507 查看
1.1.1.1 bind方法
注意:这个bind可能在load的过程就已经加载,这里只是验证NioEndpoint就是使用Java中的NIO技术,来实行对Socket的处理。它主要包含两个部业务处理部分:Poller线程组和Acceptor线程组。
1.1.1.1.1 解析过程
首先我们应该知道其bind方法做了一些什么操作,代码如下:
public void bind()
throws Exception {
// 打开监听信道
serverSock =ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr= (getAddress()!=null?new
InetSocketAddress(getAddress(),getPort()):new
InetSocketAddress(getPort()));
serverSock.socket().bind(addr,getBacklog());
serverSock.configureBlocking(true);
//mimic APRbehavior
serverSock.socket().setSoTimeout(getSocketProperties().getSoTimeout());
if (acceptorThreadCount==0) {
// FIXME:Doesn't seem to work that well with multiple accept threads
acceptorThreadCount =
1;
}
if (pollerThreadCount<=0) {
//minimum one pollerthread
pollerThreadCount =
1;
}
stopLatch =
new CountDownLatch(pollerThreadCount);
// Initialize SSL ifneeded
if (isSSLEnabled()){
SSLUtil sslUtil = handler.getSslImplementation().getSSLUtil(this);
sslContext =sslUtil.createSSLContext();
sslContext.init(wrap(sslUtil.getKeyManagers()),
sslUtil.getTrustManagers(), null);
SSLSessionContextsessionContext =
sslContext.getServerSessionContext();
if (sessionContext != null) {
sslUtil.configureSessionContext(sessionContext);
}
// Determine which ciphersuites and protocols to enable
enabledCiphers =sslUtil.getEnableableCiphers(sslContext);
enabledProtocols =sslUtil.getEnableableProtocols(sslContext);
}
if (oomParachute>0)reclaimParachute(true);
selectorPool.open();
}
1.1.1.1.1.1实例化ServerSocketChannelImpl
serverSock
=ServerSocketChannel.open();
其方法具体实现:
public static ServerSocketChannel open()
throws IOException{
return SelectorProvider.provider().openServerSocketChannel();
}
在这个方法中进行了两步操作,第一步调用SelectorProvider的provider方法
public static SelectorProvider
provider() {
synchronized (lock) {
if (provider
!= null)
return provider;
//在与当前线程相同访问控制权限的环境中,加载SelectorProvider实例 return AccessController.doPrivileged( new PrivilegedAction<SelectorProvider>() { public SelectorProvider run() { if (loadProviderFromProperty()) return provider; //获取系统配置的SelectorProvider if (loadProviderAsService()) return provider; //获取类加载路径下的SelectorProvider
//加载默认的SelectorProvider provider = sun.nio.ch.DefaultSelectorProvider.create(); return provider; } }); }
}
判断provider在当前进程是否已经被实例化过了,如果已经被实例化过了,那么就直接返回当前provider,不再执行后面的代码;否者就执行后面的代码实例化provider,
AccessController.doPrivileged()在与当前线程相同访问控制权限的环境中,加载SelectorProvider实例
loadProviderFromProperty()这个函数判断如果系统属性java.nio.channels.spi.SelectorProvider
已经被定义了,则该属性名看作具体提供者类的完全限定名。加载并实例化该类;如果此进程失败,则抛出未指定的错误。
loadProviderAsService()这个函数判断:如果在对系统类加载器可见的 jar
文件中安装了提供者类,并且该 jar 文件包含资源目录 META-INF/services 中名为java.nio.channels.spi.SelectorProvider 的提供者配置文件,则采用在该文件中指定的第一个类名称。加载并实例化该类;如果此进程失败,则抛出未指定的错误。
最后,如果未通过上述的方式制定任何provider,则实例化系统默认的provider并返回该结果(一般情况下,都是这种情况。)
这个地方需要注意的是:这里系统默认的provider在不同系统上是不一样的,下面用一个表格来表示:
系统 | provider |
MacOSX | KQueueSelectorProvider |
Linux | |
Windows | WindowsSelectorProvider |
进入
sun.nio.ch.DefaultSelectorProvider.create();这里系统会根据不同的操作系统返回不同的provider;具体信息在上面的表格
总结:该方法的作用完成建立Pipe,并把pipe的读写文件描述符放入pollArray中,这个pollArray是Selector的枢纽
====================方法分界线=======================
上述是调用provider方法的具体过程,下面讲解一下调用其之后继续调用openServerSocketChannel的过程
以osx系统为例其返回了KQueueSelectorProvider,所以调用的方法是KQueueSelectorProvider.openServerSocketChannel
注意:其实这个方法不在KQueueSelectorProvider这个类中,而在其父类SelectorProviderImpl中,方法如下:
publicServerSocketChannelopenServerSocketChannel()
throws IOException {
return new ServerSocketChannelImpl(this);
}
即ServerSocketChannel.open()方法实际上是产生了一个子类ServerSocketChannelImpl的对象实例。其构造器如下:
ServerSocketChannelImpl(SelectorProvider var1) throws IOException { super(sp); this.fd = Net.serverSocket(true); //获取ServerSocket的文件描述符 this.fdVal = IOUtil.fdVal(this.fd); //获取文件描述的id this.state = ST_INUSE; //类变量 private static final int ST_INUSE = 0;
}
所以在这里,serverSock = ServerSocketChannel.open();这个方法的作用是实例化ServerSocketChannelImpl,其成员变量具体实现代码如下:
//获取ServerSocket的文件描述符
class Net
{
private static volatile boolean checkedIPv6 = false;
private static volatile boolean isIPv6Available;
public static final int SHUT_RD = 0;//关闭读操作
public static final int SHUT_WR = 1;//关闭写操作
public static final int SHUT_RDWR = 2;//关闭读写操作
static
{
//加载nio和net资源库
Util.load();
initIDs();
}
private static native void initIDs();
//默认协议
staticfinal ProtocolFamily UNSPEC = new ProtocolFamily() {
public String name()
{
return "UNSPEC";
}
};
//获取ServerSocket文件描述
staticFileDescriptor serverSocket(boolean flag)
{
return IOUtil.newFD(socket0(isIPv6Available(), flag, true));
}
private static native int socket0(boolean flag, boolean flag1, boolean flag2);
}
=============================================================
class IOUtil
{
staticfinal int IOV_MAX = iovMax();
staticfinal boolean $assertionsDisabled = !sun/nio/ch/IOUtil.desiredAssertionStatus();
static
{
Util.load();
}
创建文件描述符
staticFileDescriptor newFD(int i)
{
FileDescriptor filedescriptor = new FileDescriptor();
setfdVal(filedescriptor, i);
return filedescriptor;
}
}
//获取文件描述的id
static native int fdVal(FileDescriptor filedescriptor);
1.1.1.1.1.2 构建socket并设置相关属性
socketProperties.setProperties(serverSock.socket());
serverSock.socket()的具体实现
public
ServerSocket socket() {
synchronized
(stateLock) {//
stateLock
是一个
new Object()
加载进行
if
(socket == null)
socket =ServerSocketAdaptor.create(this);
return
socket;
}
}
============================create
方法
==============================
publicstatic ServerSocket create(ServerSocketChannelImpl ssc) {
try {
return new ServerSocketAdaptor(ssc);
} catch (IOException x) {
throw new Error(x);
}
}
==============================
构造器
=============================
private(ServerSocketChannelImpl ssc)
throws IOException
{
this.ssc = ssc;
}
====================ServerSocketChannelImpl
类属性
===============
private final ServerSocketChannelImpl ssc;
private volatile int timeout = 0;
===============================================================
此方法返回的是一个ServerSocket对象,其中利用同步保证了socket是一个单例
到了这里socketProperties.setProperties(serverSock.socket());这个方法就等价于socketProperties.setProperties(ServerSocket),其代码如下:
public void setProperties(ServerSocket socket)
throws SocketException{
if (rxBufSize
!= null)
socket.setReceiveBufferSize(rxBufSize.intValue()); //设置输入流缓冲大小
if (performanceConnectionTime!=null&&performanceLatency!=null&&
performanceBandwidth !=
null)
socket.setPerformancePreferences(//设置网络传输指标相对重要性
performanceConnectionTime.intValue(),
performanceLatency.intValue(),
performanceBandwidth.intValue());
if (soReuseAddress!=null)
socket.setReuseAddress(soReuseAddress.booleanValue());
if (soTimeout
!= null
&& soTimeout.intValue()>=
0)
socket.setSoTimeout(soTimeout.intValue());
}
总结:这段代码的作用是创建socket实例并给当前socket设置一些属性,包括输入流缓冲区、网络传输三项指标的相对重要性、端口是否可复用、设置读取超时时间,其实在启动过程中这些都是null,所以并没有进行什么设置
public int getReceiveBufferSize() throws SocketException
public void setReceiveBufferSize(int size) throwsSocketException
在默认情况下,输入流的接收缓冲区是8096个字节(8K)。这个值是Java所建议的输入缓冲区的大小。如果这个默认值不能满足要求,可以用setReceiveBufferSize方法来重新设置缓冲区的大小。但最好不要将输入缓冲区设得太小,否则会导致传输数据过于频繁,从而降低网络传输的效率。
如果底层的Socket实现不支持SO_RCVBUF选项,这两个方法将会抛出SocketException例外。必须将size设为正整数,否则setReceiveBufferSize方法将抛出IllegalArgumentException例外
===================================================================
public void setPerformancePreferences(int connectionTime,intlatency,int bandwidth)
以上方法的三个参数表示网络传输数据的三项指标:
参数connectionTime:表示用最少时间建立连接。
参数latency:表示最小延迟。
参数bandwidth:表示最高带宽。
setPerformancePreferences()方法用来设定这三项指标之间的相对重要性。可以为这些参数赋予任意的整数,这些整数之间的相对大小就决定了相应参数的相对重要性。例如,如果参数connectionTime为2,参数latency为1,而参数bandwidth为3,就表示最高带宽最重要,其次是最少连接时间,最后是最小延迟。
public boolean getReuseAddress() throws SocketException
public void setReuseAddress(boolean on) throws SocketException
错误的说法:
通过这个选项,可以使多个Socket对象绑定在同一个端口上。
正确的说明是:
如果端口忙,但TCP状态位于 TIME_WAIT
,可以重用端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息,抛出“Addressalready
in use: JVM_Bind”。如果你的服务程序停止后想立即重启,不等60秒,而新套接字依旧使用同一端口,此时SO_REUSEADDR
选项非常有用。必须意识到,此时任何非期望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不可能。
这个参数在Windows平台与Linux平台表现的特点不一样。在Windows平台表现的特点是不正确的,在Linux平台表现的特点是正确的。
在Windows平台,多个Socket新建立对象可以绑定在同一个端口上,这些新连接是非TIME_WAIT状态的。这样做并没有多大意义。
在Linux平台,只有TCP状态位于 TIME_WAIT
,才可以重用端口。这才是正确的行为。
使用SO_REUSEADDR选项时有两点需要注意:
1. 必须在调用bind方法之前使用setReuseAddress方法来打开SO_REUSEADDR选项。因此,要想使用SO_REUSEADDR选项,就不能通过Socket类的构造方法来绑定端口。
2. 必须将绑定同一个端口的所有的Socket对象的SO_REUSEADDR选项都打开才能起作用。如在例程4-12中,socket1和socket2都使用了setReuseAddress方法打开了各自的SO_REUSEADDR选项。
在Windows操作系统上运行上面的代码的运行结果如下:
这种结果是不正确的。
socket1.getReuseAddress():true
socket2.getReuseAddress():true
在Linux操作系统上运行上面的代码的运行结果如下:
这种结果是正确的。因为第一个连接不是TIME_WAIT状态的,第二个连接就不能使用8899端口;
只有第一个连接是TIME_WAIT状态的,第二个连接就才能使用8899端口;
public int getSoTimeout() throws SocketException
public void setSoTimeout(int timeout) throws SocketException
这个Socket选项在前面已经讨论过。可以通过这个选项来设置读取数据超时。当输入流的read方法被阻塞时,如果设置timeout(timeout的单位是毫秒),那么系统在等待了timeout毫秒后会抛出一个InterruptedIOException例外。在抛出例外后,输入流并未关闭,你可以继续通过read方法读取数据。
如果将timeout设为0,就意味着read将会无限等待下去,直到服务端程序关闭这个Socket.这也是timeout的默认值。如下面的语句将读取数据超时设为30秒:
1.1.1.1.1.3 创建套接字地址
InetSocketAddress addr = (getAddress()!=null?new
InetSocketAddress(getAddress(),getPort()):new
InetSocketAddress(getPort()));
创建套接字地址,并设置其端口
1.1.1.1.1.4 绑定地址和端口
serverSock.socket().bind(addr,getBacklog());
socket()是一个单例模式创建其实例,所以在这里还是上面的ServerSocketChannelImpl实例,然后调用其bind方法,方法代码如下: public ServerSocketChannel bind(SocketAddress socketaddress, int i)
throws IOException
{
synchronized(lock)
{
if(!isOpen())
//如果socket关闭,则抛出ClosedChannelException
throw new ClosedChannelException();
if(isBound())
//如果已绑定,则抛出AlreadyBoundException
throw new AlreadyBoundException();
//确定inetsocketaddress
InetSocketAddress inetsocketaddress = socketaddress != null ? Net.checkAddress(socketaddress) : new InetSocketAddress(0);
SecurityManager securitymanager = System.getSecurityManager();
if(securitymanager != null)
//检查地址端口监听权限
securitymanager.checkListen(inetsocketaddress.getPort());
//绑定前工作
NetHooks.beforeTcpBind(fd, inetsocketaddress.getAddress(), inetsocketaddress.getPort());
//实际地址绑定
Net.bind(fd, inetsocketaddress.getAddress(), inetsocketaddress.getPort());
//开启监听,如果参数i小于1,默认接受50个连接
Net.listen(fd, i >= 1 ? i : 50);
synchronized(stateLock)
{
//更新ocalAddress
localAddress = Net.localAddress(fd);
}
}
return this;
}
从上面可以看出,bind首先检查ServerSocket是否关闭,是否绑定地址,如果既没有绑定也没关闭,则检查绑定的socketaddress是否正确或合法;然后通过Net工具类的bind(native)和listen(native),完成实际的
ServerSocket地址绑定和开启监听,如果绑定是开启的参数小于1,则默认接受50个连接。
1.1.1.1.1.5 serverSock设置成阻塞IO
serverSock.configureBlocking(true);
代码如下:
public finalSelectableChannelconfigureBlocking(boolean
block)
throws IOException
{
synchronized (regLock) {
if (!isOpen())
throw new ClosedChannelException();
if (blocking
== block)
return this;
if (block && haveValidKeys())
throw new IllegalBlockingModeException();
implConfigureBlocking(block);
blocking = block;
}
return this;
}
1.1.1.1.1.6 设置读取超时时间
serverSock.socket().setSoTimeout(getSocketProperties().getSoTimeout());
1.1.1.1.1.7 初始化线程数
//初始化acceptor和poller线程数
if (acceptorThreadCount == 0) { // FIXME: Doesn't seem to work that well with multiple accept threads acceptorThreadCount = 1; } if (pollerThreadCount <= 0) { //minimum one poller thread pollerThreadCount = 1; }
1.1.1.1.1.8 实例化线程同步辅助类
ountDownLatch类是一个同步计数器,构造时传入int参数,该参数就是计数器的初始值,每调用一次countDown()方法,计数器减1,计数器大于0 时,await()方法会阻塞程序继续执行
stopLatch = new CountDownLatch(pollerThreadCount); 参考链接:http://www.cnblogs.com/yezhenhan/archive/2012/01/07/2315652.html
这个属性的作用是为了在关闭的时候确定所有的pollers关闭才继续向后执行
public void stopInternal() {
releaseConnectionLatch();
if (!paused) {
pause();
}
if (running) {
running =
false;
unlockAccept();
for (int
i=0;
pollers!=null
&&i<pollers.length;
i++) {
if (pollers[i]==null)
continue;
pollers[i].destroy();
pollers[i] =
null;
}
try {
stopLatch.await(selectorTimeout+100,
TimeUnit.MILLISECONDS);
} catch
(InterruptedExceptionignore) {
}
shutdownExecutor();
eventCache.clear();
nioChannels.clear();
processorCache.clear();
}
}
1.1.1.1.1.9 NioSelectorPool实例设置属性
selectorPool.open();
其中selectorPool是成员变量
private NioSelectorPool
selectorPool =
new NioSelectorPool();
在分析selectorPool.open();这段代码之前,我们必须了解Selector open()这个方法是干嘛,这个方法也在NioSelectorPool中
代码如下:
public staticSelector open() throws IOException {
returnSelectorProvider.provider().openSelector();
}
通过调用系统默认的SelectorProvider(这里不同的系统会有不同的SelectorProvider实现类)的openSelector()方法来创建新的selector
SelectorProvider.provider()这个方法我们已经在上文分析过,这里获取的就是同一个KQueueSelectorProvider实例
后面调用的也就是KQueueSelectorProvider.openSelector();源码如下:
public AbstractSelector openSelector()throws IOException {
returnnew KQueueSelectorImpl(this);
}
根据代码可以看出其实例化了一个KQueueSelectorImpl,这是一个选择器,看一下选择器的作用,Selector选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。当这么做的时候,可以选择将被激发的线程挂起,直到有就绪的的通道。
所以下面代码的则用是构建blockingSelector实例,并将KQueueSelectorImpl给注入sharedSelector,这两个变量都是NioSelectorPool的属性
public void open()
throws IOException{
enabled =
true;
getSharedSelector();
if (SHARED) {
blockingSelector =
new NioBlockingSelector();
blockingSelector.open(getSharedSelector());
}
}
protected Selector
getSharedSelector()
throws IOException{
if (SHARED
&& SHARED_SELECTOR==null) {
synchronized ( NioSelectorPool.class
) {
if (
SHARED_SELECTOR==null) {
synchronized (Selector.class) {
SHARED_SELECTOR=Selector.open();
}
log.info("Usinga shared selector for servlet write/read");
}
}
}
return SHARED_SELECTOR;
}
public static Selector
open()
throws IOException{
return SelectorProvider.provider().openSelector();
}
下面这个方法是创建一个轮询线程,然后将选择器赋值给这个公司,并设置起为守护线程
public void open(Selector selector) {
sharedSelector = selector;
poller =
new BlockPoller();
poller.selector
= sharedSelector;
poller.setDaemon(true);
poller.setName("NioBlockingSelector.BlockPoller-"+(++threadCounter));
poller.start();
}
相关文章推荐
- Tomcat请求处理(一) -- 服务器端口监听
- tomcat请求处理分析(四) 监听请求轮询处理
- tomcat4 请求的处理——初步分析
- tomcat6源码分析三(请求处理过程)
- tomcat6源码分析三(请求处理过程)
- Tomcat 启动与处理请求分析(二)
- linux C++ 同时监听多个端口 同时处理TCP和UDP服务请求的回射服务器
- Tomcat 启动与处理请求分析(一)
- tomcat请求处理分析(五) 请求到响应流
- tomcat请求处理分析(一) 启动container实例
- tomcat请求处理分析(二) 启动mapperListener
- springMVC源码分析--DispatcherServlet请求获取及处理
- Tomcat处理HTTP请求源码分析(下)
- tomcat 请求处理模型
- android(20)(从网络抓取html显示在本地,把流转换成String,请求网络,简单的消息处理机制,编码问题)
- tomcat部署项目到本地以及端口相关
- 连接本地Oracle 11g时 ORA-12514:TNS:监听程序当前无法识别连接描述符中请求的服务
- 一个简单的html测试tomcat本地服务器的问题处理一
- Spring MVC请求处理流程及源码分析
- 连接本地Oracle 11g时 ORA-12514:TNS:监听程序当前无法识别连接描述符中请求的服务