`
Donald_Draper
  • 浏览: 990408 次
社区版块
存档分类
最新评论

SocketChannelImpl 解析一(通道连接,发送数据)

    博客分类:
  • NIO
nio 
阅读更多
ThreadLocal解析 :http://donald-draper.iteye.com/blog/2368159
Java NIO ByteBuffer详解:http://donald-draper.iteye.com/blog/2357084
DirectByteBuffer简介:http://donald-draper.iteye.com/blog/2372351
SelectorProvider定义:http://donald-draper.iteye.com/blog/2369615
ServerSocketChannelImpl解析:http://donald-draper.iteye.com/blog/2370912
SocketChannel接口定义:http://donald-draper.iteye.com/blog/2371218

引言:
在SocketChannel接口定义这篇文章中,我们看了socket的连接,完成连接,是否正在建立连接,读缓冲区写到通道,聚集写,读通道写缓冲区,分散读等方法。在NIO包中TCP发送接受字节序列通过SocketChannel。今天我们来看一下SocketChannel的具体
实现。我们从SocketChannel的open方法开始。
//SocketChannel
 public static SocketChannel open() throws IOException {
        return SelectorProvider.provider().openSocketChannel();
    }

SelectorProvider.provider()这个过程我们就不详说了实际是加载系统默认的SelectorProvider
实例,则个我们在SelectorProvider定义有提过,简单看一下:
//SelectorProviderImpl
public abstract class SelectorProviderImpl extends SelectorProvider
{
  public SocketChannel openSocketChannel()
        throws IOException
    {
        return new SocketChannelImpl(this);
    }
}

从上面可以看出,SocketChannel的默认实现为SocketChannelImpl。再来看SocketChannelImpl的变量
声明和相关方法的实现。
class SocketChannelImpl extends SocketChannel
    implements SelChImpl
{  
    private static NativeDispatcher nd = new SocketDispatcher();//socket的分发器
    private final FileDescriptor fd;//文件描述
    private final int fdVal;//文件描述id
    private volatile long readerThread;//读线程
    private volatile long writerThread;//写线程
    private final Object readLock;//读锁
    private final Object writeLock;//写锁
    private final Object stateLock;//状态锁
    private static final int ST_UNINITIALIZED = -1;//未初始化
    private static final int ST_UNCONNECTED = 0;//未连接
    private static final int ST_PENDING = 1;//正在连接
    private static final int ST_CONNECTED = 2;//已连接
    private static final int ST_KILLPENDING = 3;//正在关闭
    private static final int ST_KILLED = 4;//关闭
    private int state;//通道状态
    private SocketAddress localAddress;//socket本地地址
    private SocketAddress remoteAddress;//socket远端地址
    private boolean isInputOpen;//输入流是否打开
    private boolean isOutputOpen;//输出流是否打开
    private boolean readyToConnect;//是否正在准备连接
    private Socket socket;//通道套接字
    static final boolean $assertionsDisabled = !sun/nio/ch/SocketChannelImpl.desiredAssertionStatus();
    static 
    {
       //加载nio,net资源库
        Util.load();
    }
  }

  SocketChannelImpl的构造方法有三种分别如下
  1.
  
 SocketChannelImpl(SelectorProvider selectorprovider)
        throws IOException
    {
        super(selectorprovider);
        readerThread = 0L;
        writerThread = 0L;
	//初始化读写及状态锁
        readLock = new Object();
        writeLock = new Object();
        stateLock = new Object();
        state = -1;//状态默认为未初始化
        isInputOpen = true;
        isOutputOpen = true;
        readyToConnect = false;
        fd = Net.socket(true);//初始化文件描述符
        fdVal = IOUtil.fdVal(fd);//获取文件描述的值
        state = 0;//已初始化,未连接
    }

2.
 
  SocketChannelImpl(SelectorProvider selectorprovider, FileDescriptor filedescriptor, boolean flag)
        throws IOException
    {
        super(selectorprovider);
        readerThread = 0L;
        writerThread = 0L;
        readLock = new Object();
        writeLock = new Object();
        stateLock = new Object();
        state = -1;
        isInputOpen = true;
        isOutputOpen = true;
        readyToConnect = false;
        fd = filedescriptor;
        fdVal = IOUtil.fdVal(filedescriptor);
        state = 0;//已初始化,未连接
        if(flag)
	    //初始化本地地址
            localAddress = Net.localAddress(filedescriptor);
    }

3.
 
  SocketChannelImpl(SelectorProvider selectorprovider, FileDescriptor filedescriptor, InetSocketAddress inetsocketaddress)
        throws IOException
    {
        super(selectorprovider);
        readerThread = 0L;
        writerThread = 0L;
        readLock = new Object();
        writeLock = new Object();
        stateLock = new Object();
        state = -1;
        isInputOpen = true;
        isOutputOpen = true;
        readyToConnect = false;
        fd = filedescriptor;
        fdVal = IOUtil.fdVal(filedescriptor);
        state = 2;//已连接
        localAddress = Net.localAddress(filedescriptor);
        remoteAddress = inetsocketaddress;
    }

我们需要关注的是这两点,
a.fd = Net.socket(true);//初始化文件描述符

//Net
 static FileDescriptor socket(boolean flag)
        throws IOException
    {
        return socket(UNSPEC, flag);
    }
    static FileDescriptor socket(ProtocolFamily protocolfamily, boolean flag)
        throws IOException
    {
        boolean flag1 = isIPv6Available() && protocolfamily != StandardProtocolFamily.INET;
        return IOUtil.newFD(socket0(flag1, flag, false));
    }
    private static native int socket0(boolean flag, boolean flag1, boolean flag2);

//IOUtil
 static FileDescriptor newFD(int i)
    {
        FileDescriptor filedescriptor = new FileDescriptor();
        setfdVal(filedescriptor, i);
        return filedescriptor;
    }

这个我们在ServerSocketChannelImpl解析这篇文章接触过Net和IOUtil,这里不具体的解释了
,看一下即可,很容易理解。
b.localAddress = Net.localAddress(filedescriptor);

//Net
 static InetSocketAddress localAddress(FileDescriptor filedescriptor)
        throws IOException
    {
        return new InetSocketAddress(localInetAddress(filedescriptor), localPort(filedescriptor));
    }
      private static native int localPort(FileDescriptor filedescriptor)
        throws IOException;

    private static native InetAddress localInetAddress(FileDescriptor filedescriptor)
        throws IOException;

从上面可以看出SocketChannelImpl构造主要是初始化读写及状态锁和通道socket文件描述。
来看SocketChannelImpl的其他方法
//连接socket地址
 public boolean connect(SocketAddress socketaddress)
        throws IOException
    {
        boolean flag = false;
        Object obj = readLock;//同步读锁
        JVM INSTR monitorenter ;//try
        Object obj1 = writeLock;//同步写锁
        JVM INSTR monitorenter ;
        InetSocketAddress inetsocketaddress;
	//确保socket通道处于打开状态,没有连接
        ensureOpenAndUnconnected();
	//检查socketAddress正确与合法性
        inetsocketaddress = Net.checkAddress(socketaddress);
        SecurityManager securitymanager = System.getSecurityManager();
        if(securitymanager != null)
	    //检查当前线程是否有Connect方法的访问控制权限
            securitymanager.checkConnect(inetsocketaddress.getAddress().getHostAddress(), inetsocketaddress.getPort());
        //同步regLock锁,Lock for registration and configureBlocking operations
	//这个在AbstractSelectableChannel中定义
	Object obj2 = blockingLock();
        JVM INSTR monitorenter ;
        int i = 0;
	//Marks the begin/end of an I/O operation that might block indefinitely.
        begin();//与end协调使用,用于可能阻塞IO操作
        boolean flag1;
	//同步状态锁
        synchronized(stateLock)
        {
            if(isOpen())
                break MISSING_BLOCK_LABEL_149;
            flag1 = false;
        }
	//清除Reader线程
        readerCleanup();
        end(i > 0 || i == -2);
	//断言连接结果大于-2,则连接失败,抛出断言异常
        if(!$assertionsDisabled && !IOStatus.check(i))
            throw new AssertionError();
        return flag1;
        if(localAddress == null)
	    //beforeTcpConnect为静态空方法体,这个我们在ServerSocketChannelImpl中有说
            NetHooks.beforeTcpConnect(fd, inetsocketaddress.getAddress(), inetsocketaddress.getPort());
        //初始化读线程
	readerThread = NativeThread.current();
        obj3;
        JVM INSTR monitorexit ;
        do
        {
            InetAddress inetaddress = inetsocketaddress.getAddress();
            if(inetaddress.isAnyLocalAddress())
                inetaddress = InetAddress.getLocalHost();
            //尝试连接socket地址
            i = Net.connect(fd, inetaddress, inetsocketaddress.getPort());
        } while(i == -3 && isOpen());
        readerCleanup();
        end(i > 0 || i == -2);
        if(!$assertionsDisabled && !IOStatus.check(i))
            throw new AssertionError();
        break MISSING_BLOCK_LABEL_358;
        Exception exception1;
        exception1;
        readerCleanup();
        end(i > 0 || i == -2);
        if(!$assertionsDisabled && !IOStatus.check(i))
            throw new AssertionError();
        else
            throw exception1;
        IOException ioexception;
        ioexception;
	//出现IO异常,则关闭通道
        close();
        throw ioexception;
        Object obj4 = stateLock;
        JVM INSTR monitorenter ;
        remoteAddress = inetsocketaddress;
        if(i <= 0) goto _L2; else goto _L1
_L1:
        state = 2;
        if(isOpen())
            localAddress = Net.localAddress(fd);
        true;
        obj2;
        JVM INSTR monitorexit ;//退出同步
        obj1;
        JVM INSTR monitorexit ;
        obj;
        JVM INSTR monitorexit ;
        return;
_L2:
        if(!isBlocking())
            state = 1;
        else
        if(!$assertionsDisabled)
            throw new AssertionError();
        obj4;
        JVM INSTR monitorexit ;
          goto _L3
        Exception exception2;
        exception2;
        obj4;
        JVM INSTR monitorexit ;
        throw exception2;
_L3:
        obj2;
        JVM INSTR monitorexit ;
          goto _L4
        Exception exception3;
        exception3;
        obj2;
        JVM INSTR monitorexit ;
        throw exception3;
_L4:
        false;
        obj1;
        JVM INSTR monitorexit ;
        obj;
        JVM INSTR monitorexit ;
        return;
        Exception exception4;
        exception4;
        throw exception4;
        Exception exception5;
        exception5;
        throw exception5;
    }

connect连接方法有几点要看:
1.
//确保socket通道处于打开状态,没有连接
ensureOpenAndUnconnected();

2.
//清除Reader线程
readerCleanup();

3.尝试连接socket地址
do
{
    InetAddress inetaddress = inetsocketaddress.getAddress();
    if(inetaddress.isAnyLocalAddress())
        inetaddress = InetAddress.getLocalHost();
    //尝试连接socket地址
    i = Net.connect(fd, inetaddress, inetsocketaddress.getPort());
} while(i == -3 && isOpen());

4.检查连接结果
if(!$assertionsDisabled && !IOStatus.check(i))
       throw new AssertionError();
   else
       throw exception1;
   IOException ioexception;
   ioexception;
   //出现IO异常,则关闭通道
   close();

下面分别来看这四点:
1.
//确保socket通道处于打开状态,没有连接
ensureOpenAndUnconnected();

 void ensureOpenAndUnconnected()
        throws IOException
    {
        synchronized(stateLock)
        {
            if(!isOpen())//通道关闭
                throw new ClosedChannelException();
            if(state == 2)//已经连接
                throw new AlreadyConnectedException();
            if(state == 1)//正在来接
                throw new ConnectionPendingException();
        }
    }

2.
//清除Reader线程
readerCleanup();

 private void readerCleanup()
        throws IOException
    {
        synchronized(stateLock)
        {
            readerThread = 0L;
	    //连接正在关闭,则调用kill完成实际关闭工作
            if(state == 3)
                kill();
        }
    }

3.尝试连接socket地址
do
{
    InetAddress inetaddress = inetsocketaddress.getAddress();
    if(inetaddress.isAnyLocalAddress())
        inetaddress = InetAddress.getLocalHost();
    //尝试连接socket地址,这里为什么是循序,因为连接操作有可能被中断,及i为-3,
    //当中断位消除时,继续尝试连接
    i = Net.connect(fd, inetaddress, inetsocketaddress.getPort());
} while(i == -3 && isOpen());

//Net
  
static int connect(FileDescriptor filedescriptor, InetAddress inetaddress, int i)
        throws IOException
    {
        return connect(UNSPEC, filedescriptor, inetaddress, i);
    }

    static int connect(ProtocolFamily protocolfamily, FileDescriptor filedescriptor, InetAddress inetaddress, int i)
        throws IOException
    {
        boolean flag = isIPv6Available() && protocolfamily != StandardProtocolFamily.INET;
        return connect0(flag, filedescriptor, inetaddress, i);
    }

    private static native int connect0(boolean flag, FileDescriptor filedescriptor, InetAddress inetaddress, int i)
        throws IOException;

4.检查连接结果
if(!$assertionsDisabled && !IOStatus.check(i))
       throw new AssertionError();
   else
       throw exception1;
   IOException ioexception;
   ioexception;
   //出现IO异常,则关闭通道
   close();

这一点我们需要关注的是IOStatus.check(i)这句:
//IOStatus
package sun.nio.ch;
final class IOStatus
{
    static final int EOF = -1;//结束
    static final int UNAVAILABLE = -2;//不可用
    static final int INTERRUPTED = -3;//操作中断
    static final int UNSUPPORTED = -4;//不支持
    static final int THROWN = -5;//异常
    static final int UNSUPPORTED_CASE = -6;
    private IOStatus()
    {
    }
    static int normalize(int i)
    {
        if(i == -2)
            return 0;
        else
            return i;
    }
    //连接结果i大于等于-2,即连接失败
    static boolean check(int i)
    {
        return i >= -2;
    }
    static long normalize(long l)
    {
        if(l == -2L)
            return 0L;
        else
            return l;
    }
    static boolean check(long l)
    {
        return l >= -2L;
    }
    static boolean checkAll(long l)
    {
        return l > -1L || l < -6L;
    }
}

从上面可以看出,connect连接方法首先同步读锁和写锁,确保socket通道打开,并没有连接;
然后检查socket地址的正确性与合法性,然后检查当前线程是否有Connect方法的访问控制权限,
最后尝试连接socket地址。
再来看地址绑定方法bind
 public SocketChannel bind(SocketAddress socketaddress)
        throws IOException
    {
       //同步读锁,写锁,状态锁
        synchronized(readLock)
        {
            synchronized(writeLock)
            {
                synchronized(stateLock)
                {
                    if(!isOpen())//通道关闭
                        throw new ClosedChannelException();
                    if(state == 1)//正在连接
                        throw new ConnectionPendingException();
                    if(localAddress != null)
                        throw new AlreadyBoundException();
		    //检查地址
                    InetSocketAddress inetsocketaddress = socketaddress != null ? Net.checkAddress(socketaddress) : new InetSocketAddress(0);
                    NetHooks.beforeTcpBind(fd, inetsocketaddress.getAddress(), inetsocketaddress.getPort());
                    //绑定地址,这个在ServerSocketChannelImpl篇,一看过不在重复。
		    Net.bind(fd, inetsocketaddress.getAddress(), inetsocketaddress.getPort());
                    //初始化localAddress
		    localAddress = Net.localAddress(fd);
                }
            }
        }
        return this;
    }

下面来看SocketChannelImpl的几个读写方法
先来看从缓冲区读取数据,写到通道
public int write(ByteBuffer bytebuffer)
        throws IOException
    {
        if(bytebuffer == null)
            throw new NullPointerException();
        Object obj = writeLock;//同步写锁
        JVM INSTR monitorenter ;//进入同步
        int i;
	//确保没有关闭输出流
        ensureWriteOpen();
        i = 0;
        begin();//end,
        int k;
        synchronized(stateLock)
        {
            if(isOpen())
                break MISSING_BLOCK_LABEL_140;
            k = 0;
        }
	//清除写线程
        writerCleanup();
        end(i > 0 || i == -2);
	//同步状态锁,如果通道输出流关闭或写异常,则抛出AsynchronousCloseException
        synchronized(stateLock)
        {
            if(i <= 0 && !isOutputOpen)
                throw new AsynchronousCloseException();
        }
	//断言,检查写结果
        if(!$assertionsDisabled && !IOStatus.check(i))
            throw new AssertionError();
        return k;
	//初始化线程
        writerThread = NativeThread.current();
        obj1;
        JVM INSTR monitorexit ;
        int j;
        do
	    //写字节流,为什么是循环写,如果字节序列太多,发送缓冲区一次写不完,需要分多次写
            i = IOUtil.write(fd, bytebuffer, -1L, nd, writeLock);
        while(i == -3 && isOpen());
        j = IOStatus.normalize(i);
        writerCleanup();
        end(i > 0 || i == -2);
        synchronized(stateLock)
        {
            if(i <= 0 && !isOutputOpen)
                throw new AsynchronousCloseException();
        }
        if(!$assertionsDisabled && !IOStatus.check(i))
            throw new AssertionError();
        obj;
        JVM INSTR monitorexit ;
        return j;
        Exception exception3;
        exception3;
        writerCleanup();
        end(i > 0 || i == -2);
        synchronized(stateLock)
        {
            if(i <= 0 && !isOutputOpen)
                throw new AsynchronousCloseException();
        }
        if(!$assertionsDisabled && !IOStatus.check(i))
            throw new AssertionError();
        else
            throw exception3;
        Exception exception5;
        exception5;
        throw exception5;
    }

写操作需要关注一下几点,
1.
//确保没有关闭输出流
 ensureWriteOpen();

2.
 //写字节流
 do
    //写字节流
     i = IOUtil.write(fd, bytebuffer, -1L, nd, writeLock);
 while(i == -3 && isOpen())

3.
//清除写线程
writerCleanup();

下面分别来看这三点
1.
//确保没有关闭输出流
 ensureWriteOpen();
  private void ensureWriteOpen()
     throws ClosedChannelException
 {
     synchronized(stateLock)
     {
         if(!isOpen())//通道关闭
             throw new ClosedChannelException();
         if(!isOutputOpen)//输出流关闭
             throw new ClosedChannelException();
         if(!isConnected())//还没连接
             throw new NotYetConnectedException();
     }
 }

2.
//写字节流
 do
    //写字节流,为什么是循环写,如果字节序列太多,发送缓冲区一次写不完,需要分多次写
     i = IOUtil.write(fd, bytebuffer, -1L, nd, writeLock);
 while(i == -3 && isOpen())

//IOUtil
static int write(FileDescriptor filedescriptor, ByteBuffer bytebuffer, long l, NativeDispatcher nativedispatcher, Object obj)
        throws IOException
    {
        int i;
        ByteBuffer bytebuffer1;
	//如果ByteBffer为DirectBuffer,则调用writeFromNativeBuffer
        if(bytebuffer instanceof DirectBuffer)
            return writeFromNativeBuffer(filedescriptor, bytebuffer, l, nativedispatcher, obj);
        //获取缓冲区的当前位置
	i = bytebuffer.position();
	//获取缓冲区limit位置
        int j = bytebuffer.limit();
	//断言position是否大于limit,是抛出AssertionError
        if(!$assertionsDisabled && i > j)
            throw new AssertionError();
        int k = i > j ? 0 : j - i;//需要些的字节数
	//获取k个字节的临时DirectBuffer
        bytebuffer1 = Util.getTemporaryDirectBuffer(k);
        int j1;
	写缓冲区到临时内存缓冲区DirectBuffer-bytebuffer1
        bytebuffer1.put(bytebuffer);
	//转换bytebuffer1写模式,为读模式
        bytebuffer1.flip();
        bytebuffer.position(i);//重新定位bytebuffer的position位置
	//从本地缓冲空间写字节流,i1为已写的字节数
        int i1 = writeFromNativeBuffer(filedescriptor, bytebuffer1, l, nativedispatcher, obj);
        if(i1 > 0)
	    //重新定位bytebuffer的position位置
	    //为什么重新定位bytebuffer的position位,
	    //如果字节序列太多,发送缓冲区一次写不完,需要分多次写
	    //将position向前移动i1位置,避免重复写即已写过的字节序列。
            bytebuffer.position(i + i1);
        j1 = i1;
	//将byteBuffer内存写到当前线程的缓存区
        Util.offerFirstTemporaryDirectBuffer(bytebuffer1);
        return j1;
        Exception exception;
        exception;
        Util.offerFirstTemporaryDirectBuffer(bytebuffer1);
        throw exception;
    }

这一步我们有几点要关注:
a.
//获取k个字节的临时DirectBuffer
bytebuffer1 = Util.getTemporaryDirectBuffer(k);

想要理解这点,先看一下Util的定义
//Util
class Util
{
    private static final int TEMP_BUF_POOL_SIZE;//临时缓冲区大小
    private static ThreadLocal localSelector = new ThreadLocal();
    private static ThreadLocal localSelectorWrapper = new ThreadLocal();
    private static Unsafe unsafe = Unsafe.getUnsafe();
    private static int pageSize = -1;
    private static volatile Constructor directByteBufferConstructor = null;
    private static volatile Constructor directByteBufferRConstructor = null;
    private static volatile String bugLevel = null;
    private static boolean loaded = false;
    static final boolean $assertionsDisabled = !sun/nio/ch/Util.desiredAssertionStatus();
    static 
    {
        //初始化临时缓冲区大小,为IOUtil的IOV_MAX,及系统默认最大IO缓冲区大小
	//static final int IOV_MAX = iovMax();
	//static native int iovMax();
        TEMP_BUF_POOL_SIZE = IOUtil.IOV_MAX;
    }
    //线程本地缓存区
    private static ThreadLocal bufferCache = new ThreadLocal() {

        protected BufferCache initialValue()
        {
            return new BufferCache();
        }

        protected volatile Object initialValue()
        {
            return initialValue();
        }

    };
}

//IOUtil,变量IOV_MAX
static native int iovMax();
static final int IOV_MAX = iovMax();

再来看Util的缓冲区的定义BufferCache
//Util
 private static class BufferCache
    {
        //存放字节序列的缓存数组,可以这么理解buffers为
	//当前缓冲区存放的字节序列ByteBuffer
	//buffers的size,即为当前缓冲区可以接受写多少个字节序列ByteBuffer
        private ByteBuffer buffers[];
        private int count;//当前缓冲区中,有数据的字节序列ByteBuffer的个数,即buffers计数器
        private int start;//缓冲区buffers的开始索引,即头部
        static final boolean $assertionsDisabled = !sun/nio/ch/Util.desiredAssertionStatus();
        BufferCache()
        {
	    //初始化缓冲区
            buffers = new ByteBuffer[Util.TEMP_BUF_POOL_SIZE];
        }
	//向缓冲区的头部添加一个字节序列bytebuffer,即写字节序列到缓存区
	 boolean offerFirst(ByteBuffer bytebuffer)
        {
            if(count >= Util.TEMP_BUF_POOL_SIZE)
            {
	        //如果当前缓冲区已满,则返回false,即当前不能写字节序列到缓存区
                return false;
            } else
            {
	        //获取缓冲区byteBuffers的当前头部索引start的前一个索引
                start = ((start + Util.TEMP_BUF_POOL_SIZE) - 1) % Util.TEMP_BUF_POOL_SIZE;
                //写字节序列到缓存区的索引start对应的ByteBuffer
		buffers[start] = bytebuffer;
                count++;//缓冲区bytebuffer计数器+1
                return true;//写字节序列到缓存区成功
            }
        }
	//这个与offerFirst恰好相反,写字节序列到缓冲区的尾部(索引start + count)
        boolean offerLast(ByteBuffer bytebuffer)
        {
            if(count >= Util.TEMP_BUF_POOL_SIZE)
            {
                return false;
            } else
            {
                int i = (start + count) % Util.TEMP_BUF_POOL_SIZE;
                buffers[i] = bytebuffer;
                count++;
                return true;
            }
        }
	//缓冲区buffers,索引向后移动
        private int next(int i)
        {
            return (i + 1) % Util.TEMP_BUF_POOL_SIZE;
        }
	//注意这个i不是索引的意思,是需要写的字节序列的字节个数,
	//这个在IOUtil的write方法中调用,如下面两行代码
	//获取k个字节的临时DirectBuffer
        //bytebuffer1 = Util.getTemporaryDirectBuffer(k);
        ByteBuffer get(int i)
        {
	    //如果缓存区当前可用的可用的ByteBuffer,返回null
            if(count == 0)
                return null;
            ByteBuffer abytebuffer[] = buffers;
            ByteBuffer bytebuffer = abytebuffer[start];
	    //如果当前缓冲区start索引对应的bytebuffer,不够用,即容量不够存放要写的字节序列
	    //则遍历当前buffers,找到可以存放的bytebuffer
            if(bytebuffer.capacity() < i)
            {
                bytebuffer = null;
                int j = start;
                do
                {
                    if((j = next(j)) == start)
		        //只有一个bytebuffer,break
                        break;
                    ByteBuffer bytebuffer1 = abytebuffer[j];
                    if(bytebuffer1 == null)
		        //下一个bytebuffer为null,break
                        break;
                    if(bytebuffer1.capacity() < i)
		         //容量不够用,continue
                        continue;
		    //找到可以存放i个字节序列的bytebuffer
                    bytebuffer = bytebuffer1;
                    break;
                } while(true);
                if(bytebuffer == null)
                    return null;
                abytebuffer[j] = abytebuffer[start];
            }
	    //清空
            abytebuffer[start] = null;
            start = next(start);
            count--;//缓冲区bytebuffer计数器-1
	    //调用rewind,为了从开始位置写字节流
            bytebuffer.rewind();
            bytebuffer.limit(i);//限制bytebuffer的可用空间limit
            return bytebuffer;
        }
	//缓冲区是否为空
        boolean isEmpty()
        {
            return count == 0;
        }
	//移除缓冲区头部的bytebuffer
        ByteBuffer removeFirst()
        {
	   //如果断言开启, 缓冲区为空,抛出断言异常
            if(!$assertionsDisabled && count <= 0)
            {
                throw new AssertionError();
            } else
            {
	       //有了上面几个方法,下面应该很好理解,就不说了
                ByteBuffer bytebuffer = buffers[start];
                buffers[start] = null;
                start = next(start);
                count--;
                return bytebuffer;
            }
        }
    }

从上面可以看出BufferCache用一个ByteBuffer数组buffers存放写到缓冲区的字节流序列,每次写字节流对应一个ByteBuffer,用count记录当前缓冲区中的有数据或可用的ByteBuffer数量,start记录当前缓冲区buffers的头部;offerFirst方法向缓冲区的头部添加一个字节序列bytebuffer,即写字节序列到缓存区;offerLast与offerFirst恰好相反,写字节序列到缓冲区的尾部(索引start + count);next方法为向后移动缓冲区buffers索引;get(int i)方法为从缓冲区获取可以存放i个字节序列的ByteBuffer,并rewind字节缓冲区ByteBuffer,
限制孔勇空间为ByteBuffer。removeFirst为移除缓冲区头部的bytebuffer,并返回。

看过Util的BufferCache的定义,我们再回到
//获取k个字节的临时DirectBuffer
bytebuffer1 = Util.getTemporaryDirectBuffer(k);

//Util
static ByteBuffer getTemporaryDirectBuffer(int i)
{
    //获取当前线程的缓冲区(ThreadLocal-bufferCache)
    BufferCache buffercache = (BufferCache)bufferCache.get();
    //从缓冲区获取容量第一个大于i的ByteBuffer
    ByteBuffer bytebuffer = buffercache.get(i);
    //如果缓冲区存在容量大于i个字节的bytebuffer,直接返回
    if(bytebuffer != null)
        return bytebuffer;
    //如果缓冲区中不存在容量大于i的bytebuffer,且不为空;
    //则移除缓冲区头部的bytebuffer
    if(!buffercache.isEmpty())
    {
        ByteBuffer bytebuffer1 = buffercache.removeFirst();
	//释放bytebuffer1
        free(bytebuffer1);
    }
    //ByteBuffer直接分配一个DirectByteBuffer,存放字节序列
    return ByteBuffer.allocateDirect(i);
}

获取临时DirectByteBuffer有两点要看
a.1
//释放bytebuffer1
free(bytebuffer1);

//Util
 private static void free(ByteBuffer bytebuffer)
    {
        //实际委托给DirectBuffer的clean,这个我们在DirectByteBuffer有说,
	//即释放分配的实际物理内存
        ((DirectBuffer)bytebuffer).cleaner().clean();
    }

//DirectBuffer
package sun.nio.ch;
import sun.misc.Cleaner;
public interface DirectBuffer
{
    public abstract long address();
    public abstract Object attachment();
    public abstract Cleaner cleaner();
}

a.2
 
public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }

b.
//从本地缓冲空间写字节流,i1为已写的字节数
int i1 = writeFromNativeBuffer(filedescriptor, bytebuffer1, l, nativedispatcher, obj);

//nativedispatcher参数实际为SocketDispatcher
private static int writeFromNativeBuffer(FileDescriptor filedescriptor, ByteBuffer bytebuffer, long l, NativeDispatcher nativedispatcher, Object obj)
        throws IOException
    {
        int i = bytebuffer.position();
        int j = bytebuffer.limit();
        if(!$assertionsDisabled && i > j)
            throw new AssertionError();
        int k = i > j ? 0 : j - i;
        int i1 = 0;
        if(k == 0)
            return 0;
        if(l != -1L)
	    //这个方法在Nativedispatcher定义,在SocketDispatcher并没有实现,obj为writeLock
            i1 = nativedispatcher.pwrite(filedescriptor, ((DirectBuffer)bytebuffer).address() + (long)i, k, l, obj);
        else
	    //默认的写操作
            i1 = nativedispatcher.write(filedescriptor, ((DirectBuffer)bytebuffer).address() + (long)i, k);
        if(i1 > 0)
	    //将position向前移动i1位置,避免重复写即已写过的字节序列
            bytebuffer.position(i + i1);
        return i1;
    }

来看两种方式的写
b.1
 if(l != -1L)
    //这个在Nativedispatcher,在SocketDispatcher并没有实现
    i1 = nativedispatcher.pwrite(filedescriptor, ((DirectBuffer)bytebuffer).address() + (long)i, k, l, obj);

//Nativedispatcher
 int pwrite(FileDescriptor filedescriptor, long l, int i, long l1, Object obj)
        throws IOException
    {
       //操作当前JDK,不支持,留待以后扩展用吧,我的JDK为1.7.0.17
        throw new IOException("Operation Unsupported");
    }

b.2
else
    //默认的写操作
    i1 = nativedispatcher.write(filedescriptor, ((DirectBuffer)bytebuffer).address() + (long)i, k);

//SocketDispatcher
int write(FileDescriptor filedescriptor, long l, int i)
        throws IOException
    {
        return write0(filedescriptor, l, i);
    }
  static native int write0(FileDescriptor filedescriptor, long l, int i)
        throws IOException;

从缓冲读取字节序列,写到通道中,实际是通过SocketDispatcher完成实际的写工作,当前默认的写方法为write(FileDescriptor filedescriptor, long l, int i)。
c.
//添加bytebuffer到线程当前缓冲区
Util.offerFirstTemporaryDirectBuffer(bytebuffer1);

static void offerFirstTemporaryDirectBuffer(ByteBuffer bytebuffer)
   {
       if(!$assertionsDisabled && bytebuffer == null)
           throw new AssertionError();
       //获取当前线程缓冲区
       BufferCache buffercache = (BufferCache)bufferCache.get();
       //将bytebuffer添加到缓冲区
       if(!buffercache.offerFirst(bytebuffer))
           free(bytebuffer);
   }

3.
//清除写线程
writerCleanup();
 private void writerCleanup()
        throws IOException
    {
        synchronized(stateLock)
        {
            writerThread = 0L;
            if(state == 3)
	        //这个kill操作,我们会在后面再讲
                kill();
        }
    }

从以上分析可以看出,从缓冲区读取字节序列写到通道,首先确保通道打开,且输出流没有关闭,然后委托给IOUtil写字节序列;IOUtil写字节流过程为首先通过Util从当前线程的缓冲区获取可以容下字节序列的临时缓冲区(DirectByteBuffer),如果没有则创建一个DirectByteBuffer,将字节序列写到临时的DirectByteBuffer中,然后将写操作委托给nativedispatcher(SocketDispatcher),将DirectByteBuffer添加到当前线程的缓冲区,
以便重用,因为DirectByteBuffer实际上是存在物理内存中,频繁的分配将会消耗更多的资源。

总结:

SocketChannelImpl构造主要是初始化读写及状态锁和通道socket文件描述。
connect连接方法首先同步读锁和写锁,确保socket通道打开,并没有连接;然后检查socket地址的正确性与合法性,然后检查当前线程是否有Connect方法的访问控制权限,最后尝试连接socket地址。从缓冲区读取字节序列写到通道write(ByteBuffer),首先确保通道打开,且输出流没有关闭,然后委托给IOUtil写字节序列;IOUtil写字节流过程为首先通过Util从当前线程的缓冲区获取可以容下字节序列的临时缓冲区(DirectByteBuffer),如果没有则创建一个DirectByteBuffer,将字节序列写到临时的DirectByteBuffer中,然后将写操作委托给nativedispatcher(SocketDispatcher),将DirectByteBuffer添加到当前线程的缓冲区,
以便重用,因为DirectByteBuffer实际上是存在物理内存中,频繁的分配将会消耗更多的资源。

SocketChannelImpl 解析二(发送数据后续):http://donald-draper.iteye.com/blog/2372548
附:
权限检查:SecurityManager为系统的默认安全检查管理器,主要用于检查当前线程是否拥有
某个权限的访问控制权限,比如socket连接,监听,获取类加载等。
//SecurityManager
//检查socket连接权限
 public void checkConnect(String host, int port) {
        if (host == null) {
            throw new NullPointerException("host can't be null");
        }
        if (!host.startsWith("[") && host.indexOf(':') != -1) {
            host = "[" + host + "]";
        }
        if (port == -1) {
            checkPermission(new SocketPermission(host,
                SecurityConstants.SOCKET_RESOLVE_ACTION));
        } else {
	    //检查是否socket连接访问控制权限
            checkPermission(new SocketPermission(host+":"+port,
                SecurityConstants.SOCKET_CONNECT_ACTION));
        }
    }
     public void checkPermission(Permission perm) {
        //检查是否perm的访问控制权限
        java.security.AccessController.checkPermission(perm);
    }

//SecurityConstants,安全权限常量
public final class SecurityConstants
{   
    //AWT为创建图形界面相关权限
    public static class AWT
    {
        private static PermissionFactory permissionFactory()
        {
            Class class1;
            class1 = (Class)AccessController.doPrivileged(new PrivilegedAction() {

                public Class run()
                {
                    return Class.forName("sun.awt.AWTPermissionFactory", true, null);
                    ClassNotFoundException classnotfoundexception;
                    classnotfoundexception;
                    return null;
                }

                public volatile Object run()
                {
                    return run();
                }

            });
            if(class1 == null)
                break MISSING_BLOCK_LABEL_52;
            return (PermissionFactory)class1.newInstance();
            Object obj;
            obj;
            throw new InternalError(((InstantiationException) (obj)).getMessage());
            obj;
            throw new InternalError(((IllegalAccessException) (obj)).getMessage());
            return new FakeAWTPermissionFactory();
        }
        private static Permission newAWTPermission(String s)
        {
            return factory.newPermission(s);
        }
        private static final String AWTFactory = "sun.awt.AWTPermissionFactory";
        private static final PermissionFactory factory = permissionFactory();
        public static final Permission TOPLEVEL_WINDOW_PERMISSION = newAWTPermission("showWindowWithoutWarningBanner");
        public static final Permission ACCESS_CLIPBOARD_PERMISSION = newAWTPermission("accessClipboard");//访问粘贴板
        public static final Permission CHECK_AWT_EVENTQUEUE_PERMISSION = newAWTPermission("accessEventQueue");
        public static final Permission TOOLKIT_MODALITY_PERMISSION = newAWTPermission("toolkitModality");
        public static final Permission READ_DISPLAY_PIXELS_PERMISSION = newAWTPermission("readDisplayPixels");
        public static final Permission CREATE_ROBOT_PERMISSION = newAWTPermission("createRobot");
        public static final Permission WATCH_MOUSE_PERMISSION = newAWTPermission("watchMousePointer");
        public static final Permission SET_WINDOW_ALWAYS_ON_TOP_PERMISSION = newAWTPermission("setWindowAlwaysOnTop");
        public static final Permission ALL_AWT_EVENTS_PERMISSION = newAWTPermission("listenToAllAWTEvents");
        public static final Permission ACCESS_SYSTEM_TRAY_PERMISSION = newAWTPermission("accessSystemTray");


        private AWT()
        {
        }
    }
    private static class FakeAWTPermission extends BasicPermission
    {

        public String toString()
        {
            return (new StringBuilder()).append("(\"java.awt.AWTPermission\" \"").append(getName()).append("\")").toString();
        }

        private static final long serialVersionUID = -1L;

        public FakeAWTPermission(String s)
        {
            super(s);
        }
    }
    private static class FakeAWTPermissionFactory
        implements PermissionFactory
    {

        public FakeAWTPermission newPermission(String s)
        {
            return new FakeAWTPermission(s);
        }

        public volatile Permission newPermission(String s)
        {
            return newPermission(s);
        }

        private FakeAWTPermissionFactory()
        {
        }

    }
    private SecurityConstants()
    {
    }
    public static final String FILE_DELETE_ACTION = "delete";//文件删除
    public static final String FILE_EXECUTE_ACTION = "execute";//文件执行
    public static final String FILE_READ_ACTION = "read";//文件读
    public static final String FILE_WRITE_ACTION = "write";//写文件
    public static final String FILE_READLINK_ACTION = "readlink";
    public static final String SOCKET_RESOLVE_ACTION = "resolve";
    public static final String SOCKET_CONNECT_ACTION = "connect";//socket连接
    public static final String SOCKET_LISTEN_ACTION = "listen";//socket监听
    public static final String SOCKET_ACCEPT_ACTION = "accept";//socket接受连接
    public static final String SOCKET_CONNECT_ACCEPT_ACTION = "connect,accept";//socket连接,接受连接
    public static final String PROPERTY_RW_ACTION = "read,write";//读写属性
    public static final String PROPERTY_READ_ACTION = "read";//读属性
    public static final String PROPERTY_WRITE_ACTION = "write";//写属性
    public static final AllPermission ALL_PERMISSION = new AllPermission();
    public static final NetPermission SPECIFY_HANDLER_PERMISSION = new NetPermission("specifyStreamHandler");
    public static final NetPermission SET_PROXYSELECTOR_PERMISSION = new NetPermission("setProxySelector");
    public static final NetPermission GET_PROXYSELECTOR_PERMISSION = new NetPermission("getProxySelector");
    public static final NetPermission SET_COOKIEHANDLER_PERMISSION = new NetPermission("setCookieHandler");
    public static final NetPermission GET_COOKIEHANDLER_PERMISSION = new NetPermission("getCookieHandler");
    public static final NetPermission SET_RESPONSECACHE_PERMISSION = new NetPermission("setResponseCache");
    public static final NetPermission GET_RESPONSECACHE_PERMISSION = new NetPermission("getResponseCache");
    //创建类加载器
    public static final RuntimePermission CREATE_CLASSLOADER_PERMISSION = new RuntimePermission("createClassLoader");
    public static final RuntimePermission CHECK_MEMBER_ACCESS_PERMISSION = new RuntimePermission("accessDeclaredMembers");
    //修改线程
    public static final RuntimePermission MODIFY_THREAD_PERMISSION = new RuntimePermission("modifyThread");
    //修改线程分组信息
    public static final RuntimePermission MODIFY_THREADGROUP_PERMISSION = new RuntimePermission("modifyThreadGroup");
    public static final RuntimePermission GET_PD_PERMISSION = new RuntimePermission("getProtectionDomain");
    //获取类加载器
    public static final RuntimePermission GET_CLASSLOADER_PERMISSION = new RuntimePermission("getClassLoader");
    public static final RuntimePermission STOP_THREAD_PERMISSION = new RuntimePermission("stopThread");
    public static final RuntimePermission GET_STACK_TRACE_PERMISSION = new RuntimePermission("getStackTrace");
    public static final SecurityPermission CREATE_ACC_PERMISSION = new SecurityPermission("createAccessControlContext");
    public static final SecurityPermission GET_COMBINER_PERMISSION = new SecurityPermission("getDomainCombiner");
    public static final SecurityPermission GET_POLICY_PERMISSION = new SecurityPermission("getPolicy");
    public static final SocketPermission LOCAL_LISTEN_PERMISSION = new SocketPermission("localhost:1024-", "listen");

}
0
2
分享到:
评论

相关推荐

    jdk 1.8源码包,包含sun源码,绝对真实,自己看大小就懂了,靠谱

    此外,Stream API也是1.8的一大亮点,它提供了处理集合的新方式,可以进行数据流的过滤、映射、归约等操作,大大简化了数据处理的代码。 在"jdk-5b86f66575b7"这个压缩包中,很可能包含了JDK 1.8的完整源码,包括...

    Ice-3.7.4.msi for windows版

    7 服务器端没有指定adapter的端口和ip,仅仅随便给了一个名字,并使用该名字从配置文件中读取信息: 启动服务器时没有问题正常,但是客户端无法连接 原因: 对象适配器无效 错误信息: 抛出异常: Ice::...

    TinyYolo2实时视频流物体检测ONNX模型

    TinyYolo2实时视频流物体检测ONNX模型 运行 ONNX 模型,并结合 OpenCV 进行图像处理。具体流程包括: 1. 加载并初始化 ONNX 模型。 2. 从摄像头捕获实时视频流。 3. 对每一帧图像进行模型推理,生成物体检测结果。 4. 在界面上绘制检测结果的边界框和标签。

    chromedriver-linux64-134.0.6998.23(Beta).zip

    chromedriver-linux64-134.0.6998.23(Beta).zip

    Web开发:ABP框架4-DDD四层架构的详解

    Web开发:ABP框架4-DDD四层架构的详解

    chromedriver-linux64-135.0.7029.0(Canary).zip

    chromedriver-linux64-135.0.7029.0(Canary).zip

    (参考项目)MATLAB人脸门禁系统.zip

    实现人脸识别的考勤门禁系统可以分为以下步骤: 1. 采集人脸图像数据集:首先需要采集员工的人脸图像数据集,包括正面、侧面等多个角度的图像。可以使用MATLAB中的图像采集工具或者第三方库进行采集。 2. 预处理人脸图像数据:对采集到的人脸图像数据进行预处理,包括人脸检测、人脸对齐、人脸裁剪等操作。MATLAB提供了相关的图像处理工具箱,可以用于实现这些处理步骤。 3. 特征提取与特征匹配:使用人脸识别算法提取人脸图像的特征,比如使用人脸识别中常用的特征提取算法如Eigenfaces、Fisherfaces或者基于深度学习的算法。然后将员工的人脸数据与数据库中的人脸数据进行匹配,判断是否为注册员工。 4. 考勤记录与门禁控制:如果人脸匹配成功,系统可以记录员工的考勤时间,并且控制门禁系统进行开启。MATLAB可以与外部设备进行通信,实现门禁控制以及考勤记录功能。

    rdtyfv、ijij

    yugy

    企业IT治理体系规划.pptx

    企业IT治理体系规划.pptx

    基于Nutz、SSH、SSM的新闻管理系统.zip(毕设&课设&实训&大作业&竞赛&项目)

    项目工程资源经过严格测试运行并且功能上ok,可实现复现复刻,拿到资料包后可实现复现出一样的项目,本人系统开发经验充足(全栈全领域),有任何使用问题欢迎随时与我联系,我会抽时间努力为您解惑,提供帮助 【资源内容】:包含源码+工程文件+说明等。答辩评审平均分达到96分,放心下载使用!可实现复现;设计报告也可借鉴此项目;该资源内项目代码都经过测试运行,功能ok 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 【提供帮助】:有任何使用上的问题欢迎随时与我联系,抽时间努力解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 下载后请首先打开说明文件(如有);整理时不同项目所包含资源内容不同;项目工程可实现复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用

    基于多目标粒子群算法的冷热电联供综合能源系统优化调度与运行策略分析,基于多目标粒子群算法的冷热电联供综合能源系统优化调度与运行策略分析,MATLAB代码:基于多目标粒子群算法冷热电联供综合能源系统运行

    基于多目标粒子群算法的冷热电联供综合能源系统优化调度与运行策略分析,基于多目标粒子群算法的冷热电联供综合能源系统优化调度与运行策略分析,MATLAB代码:基于多目标粒子群算法冷热电联供综合能源系统运行优化 关键词:综合能源 冷热电三联供 粒子群算法 多目标优化 参考文档:《基于多目标算法的冷热电联供型综合能源系统运行优化》 仿真平台:MATLAB 平台采用粒子群实现求解 优势:代码注释详实,适合参考学习,非目前烂大街的版本,程序非常精品,请仔细辨识 主要内容:代码构建了含冷、热、电负荷的冷热电联供型综合能源系统优化调度模型,考虑了燃气轮机、电制冷机、锅炉以及风光机组等资源,并且考虑与上级电网的购电交易,综合考虑了用户购电购热冷量的成本、CCHP收益以及成本等各种因素,从而实现CCHP系统的经济运行,求解采用的是MOPSO算法(多目标粒子群算法),求解效果极佳,具体可以看图 ,核心关键词: 综合能源系统; 冷热电三联供; 粒子群算法; 多目标优化; MOPSO算法; 优化调度模型; 燃气轮机; 电制冷机; 锅炉; 风光机组; 上级电网购售电交易。,基于多目标粒子群算法的CCHP综合

    DSP28379D串口升级方案:单核双核升级与Boot优化,C#上位机开发串口通信方案,DSP28379D串口升级方案:单核双核升级与Boot优化,C#上位机开发实现串口通信,DSP28379D串口升

    DSP28379D串口升级方案:单核双核升级与Boot优化,C#上位机开发串口通信方案,DSP28379D串口升级方案:单核双核升级与Boot优化,C#上位机开发实现串口通信,DSP28379D串口升级方案 单核双核升级,boot升级,串口方案。 上位机用c#开发。 ,DSP28379D; 串口升级方案; 单核双核升级; boot升级; 上位机C#开发,DSP28379D串口双核升级方案:Boot串口升级技术使用C#上位机开发

    基于ASP.NET MVC+三层架构和EntityFramework的微博门户网站项目.zip(毕设&课设&实训&大作业&竞赛&项目)

    项目工程资源经过严格测试运行并且功能上ok,可实现复现复刻,拿到资料包后可实现复现出一样的项目,本人系统开发经验充足(全栈全领域),有任何使用问题欢迎随时与我联系,我会抽时间努力为您解惑,提供帮助 【资源内容】:包含源码+工程文件+说明等。答辩评审平均分达到96分,放心下载使用!可实现复现;设计报告也可借鉴此项目;该资源内项目代码都经过测试运行,功能ok 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 【提供帮助】:有任何使用上的问题欢迎随时与我联系,抽时间努力解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 下载后请首先打开说明文件(如有);整理时不同项目所包含资源内容不同;项目工程可实现复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用

    基于PLC的双层自动门控制:光电传感触发,有序开关与延时功能实现,附程序、画面及参考文档 ,基于PLC的双层自动门控制系统:精准控制,保障无尘环境;门间联动,智能安防新体验 ,基于plc的双层自动门控

    基于PLC的双层自动门控制:光电传感触发,有序开关与延时功能实现,附程序、画面及参考文档。,基于PLC的双层自动门控制系统:精准控制,保障无尘环境;门间联动,智能安防新体验。,基于plc的双层自动门控制系统,全部采用博途仿真完成,提供程序,画面,参考文档,详情见图。 实现功能(详见上方演示视频): ① 某房间要求尽可能地保持无尘,在通道上设置了两道电动门,门1和门2,可通过光电传感器自动完成门的打开和关闭。 门1和门2 不能同时打开。 ② 第 1 道门(根据出入方向不同,可能是门 1 或门 2),是由在通道外的开门者通过按开门按钮打开的,而第 2 道门(根据出入方向不同,可能是门 1 或门 2 )则是在打开的第 1 道门关闭后自动地打开的(也可以由通道内的人按开门按钮来打开第2 道门)。 这两道门都是在门开后,经过 3s 的延时而自动关闭的。 ③ 在门关闭期间,如果对应的光电传感器的信号被遮断,则门立即自动打开。 如果在门外或者在门内的开门者按对应的开门按钮时,立即打开。 ④ 出于安全方面的考虑,如果在通道内的某个人经过光电传感器时,对应的门已经打开,则通道外的开门者可以不按开门按钮。

    黑马程序员Java品达通用权限项目,基于SpringCloud SpringBoot 的微服务框架的权限管理解决方案.zip

    项目工程资源经过严格测试运行并且功能上ok,可实现复现复刻,拿到资料包后可实现复现出一样的项目,本人系统开发经验充足(全栈全领域),有任何使用问题欢迎随时与我联系,我会抽时间努力为您解惑,提供帮助 【资源内容】:包含源码+工程文件+说明等。答辩评审平均分达到96分,放心下载使用!可实现复现;设计报告也可借鉴此项目;该资源内项目代码都经过测试运行,功能ok 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 【提供帮助】:有任何使用上的问题欢迎随时与我联系,抽时间努力解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 下载后请首先打开说明文件(如有);整理时不同项目所包含资源内容不同;项目工程可实现复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用

    DeepSeek+DeepResearch-让科研像聊天一样简单

    DeepSeek+DeepResearch——让科研像聊天一样简单 (1)DeepSeek如何做数据分析? (2)DeepSeek如何分析文件内容? (3)DeepSeek如何进行数据挖掘? (4)DeepSeek如何进行科学研究? (5)DeepSeek如何写综述? (6)DeepSeek如何进行数据可视化? (7)DeepSeek如何写作润色? (8)DeepSeek如何中英文互译? (9)DeepSeek如何做降重? (10)DeepSeek论文参考文献指令 (11)DeepSeek基础知识。

    基于springboot+uniapp实现的蛋糕商城小程序.zip(毕设&课设&实训&大作业&竞赛&项目)

    项目工程资源经过严格测试运行并且功能上ok,可实现复现复刻,拿到资料包后可实现复现出一样的项目,本人系统开发经验充足(全栈全领域),有任何使用问题欢迎随时与我联系,我会抽时间努力为您解惑,提供帮助 【资源内容】:包含源码+工程文件+说明等。答辩评审平均分达到96分,放心下载使用!可实现复现;设计报告也可借鉴此项目;该资源内项目代码都经过测试运行,功能ok 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 【提供帮助】:有任何使用上的问题欢迎随时与我联系,抽时间努力解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 下载后请首先打开说明文件(如有);整理时不同项目所包含资源内容不同;项目工程可实现复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用

    jdepend-demo-2.9.1-10.el7.x64-86.rpm.tar.gz

    1、文件内容:jdepend-demo-2.9.1-10.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/jdepend-demo-2.9.1-10.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、更多资源/技术支持:公众号禅静编程坊

    关爱儿童公益网站 web 项目.zip

    项目工程资源经过严格测试运行并且功能上ok,可实现复现复刻,拿到资料包后可实现复现出一样的项目,本人系统开发经验充足(全栈全领域),有任何使用问题欢迎随时与我联系,我会抽时间努力为您解惑,提供帮助 【资源内容】:包含源码+工程文件+说明等。答辩评审平均分达到96分,放心下载使用!可实现复现;设计报告也可借鉴此项目;该资源内项目代码都经过测试运行;功能ok 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 【提供帮助】:有任何使用上的问题欢迎随时与我联系,抽时间努力解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 下载后请首先打开说明文件(如有);整理时不同项目所包含资源内容不同;项目工程可实现复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用

    MATLAB实现WOA-LSTM鲸鱼算法优化长短期记忆网络数据分类预测(含模型描述及示例代码)

    内容概要:本文档详细介绍了如何利用 MATLAB 实现鲸鱼优化算法 (WOA) 和长短期记忆网络 (LSTM) 相结合的技术——WOA-LSTM,在数据分类和预测领域的应用。文章首先概述了LSTM在网络训练中超参数依赖的问题以及WOA作为一种新颖的全局优化算法的优势。接着阐述了该项目的研究背景、目的及其重要意义,并深入讨论了项目面临的六大主要挑战,从模型优化到超参数空间管理。文档特别强调WOA-LSTM融合所带来的性能提升、降低计算复杂度的能力及其实现自动化的超参数优化流程。除此之外,文中展示了模型的应用广泛性,覆盖了从金融市场的股票预测到智能制造业的各种实际场景,并提供了具体的模型架构细节和代码实例,以帮助理解模型的工作原理和技术要点。 适合人群:具有一定编程技能的研究人员、工程师和科学家们,尤其是对深度学习技术和机器学习感兴趣的专业人士。 使用场景及目标:该文档的目标是向用户传授使用MATLAB实现WOA-LSTM进行复杂数据分类和预测的方法论,旨在指导读者理解和掌握如何利用WOA进行超参数寻优,从而改善LSTM网络性能。 其他说明:通过阅读这份文档,使用者不仅能够获得有关WOA-LSTM技术的具体实现方式的知识,而且还可以获取关于项目规划和实际部署过程中的宝贵经验。

Global site tag (gtag.js) - Google Analytics