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

SocketChannelImpl 解析二(发送数据后续)

    博客分类:
  • NIO
nio 
阅读更多
SocketChannelImpl 解析一(通道连接,发送数据):http://donald-draper.iteye.com/blog/2372364
引言:
上一篇文章我们看了一下SocketChannelImpl的初始化,通道连接(Socket),写操作(write-ByteBuffer)。先回顾一下:
SocketChannelImpl构造主要是初始化读写及状态锁和通道socket文件描述。
connect连接方法首先同步读锁和写锁,确保socket通道打开,并没有连接;然后检查socket地址的正确性与合法性,然后检查当前线程是否有Connect方法的访问控制权限,最后尝试连接socket地址。从缓冲区读取字节序列写到通道write(ByteBuffer),首先确保通道打开,且输出流没有关闭,然后委托给IOUtil写字节序列;IOUtil写字节流过程为首先通过Util从当前线程的缓冲区获取可以容下字节序列的临时缓冲区(DirectByteBuffer),如果没有则创建一个DirectByteBuffer,将字节序列写到临时的DirectByteBuffer中,然后将写操作委托给nativedispatcher(SocketDispatcher),将DirectByteBuffer添加到当前线程的缓冲区,
以便重用,因为DirectByteBuffer实际上是存在物理内存中,频繁的分配将会消耗更多的资源。
上一篇文章我们看了写一个ByteBuffer,现在来看一下写多个ByteBuffer
 public long write(ByteBuffer abytebuffer[], int i, int j)
     throws IOException
 {
     //检查offset(i),length(j)的合法性
     if(i < 0 || j < 0 || i > abytebuffer.length - j)
         throw new IndexOutOfBoundsException();
     Object obj = writeLock;//获取写锁
     JVM INSTR monitorenter ;//进入同步,try
     long l;
     //确保通道,输出流打开,连接建立
     ensureWriteOpen();
     l = 0L;
     begin();//与end方法配合,记录中断器,处理中断
     long l2;
     synchronized(stateLock)
     {
         if(isOpen())
             break MISSING_BLOCK_LABEL_165;
         l2 = 0L;
     }
     writerCleanup();//清除写线程
     end(l > 0L || l == -2L);
     synchronized(stateLock)
     {
         if(l <= 0L && !isOutputOpen)
             throw new AsynchronousCloseException();
     }
     if(!$assertionsDisabled && !IOStatus.check(l))
         throw new AssertionError();
     return l2;
     //初始化本地写线程
     writerThread = NativeThread.current();
     obj1;
     JVM INSTR monitorexit ;
     long l1;
     do
         //委托IOUtil写字节数组序列
         l = IOUtil.write(fd, abytebuffer, i, j, nd);
     while(l == -3L && isOpen());
     l1 = IOStatus.normalize(l);
     writerCleanup();
     end(l > 0L || l == -2L);
     synchronized(stateLock)
     {
         if(l <= 0L && !isOutputOpen)
             throw new AsynchronousCloseException();
     }
     if(!$assertionsDisabled && !IOStatus.check(l))
         throw new AssertionError();
     obj;
     JVM INSTR monitorexit ;//退出同步
     return l1;
     Exception exception3;//有异常则抛出
     exception3;
     writerCleanup();
     end(l > 0L || l == -2L);
     synchronized(stateLock)
     {
         if(l <= 0L && !isOutputOpen)
             throw new AsynchronousCloseException();
     }
     if(!$assertionsDisabled && !IOStatus.check(l))
         throw new AssertionError();
     else
         throw exception3;
     Exception exception5;
     exception5;
     throw exception5;
 }

由于我们在前面已经讲过写单个ByteBuffer的方法,此方与write(ByteBuffer)
基本相似,我们只需要关注下面这点几个:
 do
     //委托IOUtil写字节数组序列
     l = IOUtil.write(fd, abytebuffer, i, j, nd);
 while(l == -3L && isOpen());

在看上面这句之前我们先看一下IOVecWrapper
//字节序列数组包装类
class IOVecWrapper
{
    private static final int BASE_OFFSET = 0;
    private static final int LEN_OFFSET;
    private static final int SIZE_IOVEC;//
    private final AllocatedNativeObject vecArray;//存放字节数组的地址
    private final int size;//字节数据大小
    private final ByteBuffer buf[];//存放字节数组
    private final int position[];//存放每个字节数组的position
    private final int remaining[];//存放每个字节数组的字节数量remaining
    private final ByteBuffer shadow[];//存放字节数组副本
    final long address;//字节序列数组包装类的起始地址
    static int addressSize;//操作系统物理地址所占的字节数
    private static final ThreadLocal cached = new ThreadLocal();//线程本地缓存
    static 
    {
        addressSize = Util.unsafe().addressSize();
        LEN_OFFSET = addressSize;
	//为什么要地址长度的2倍,一个存放字节缓冲的地址,一个存字节缓冲区的实际长度。
        SIZE_IOVEC = (short)(addressSize * 2);//存放字节数组的实际地址
    }
}

来看IOVecWrapper的构造
 private IOVecWrapper(int i)
    {
        size = i;
        buf = new ByteBuffer[i];
        position = new int[i];
        remaining = new int[i];
        shadow = new ByteBuffer[i];
	//创建存储字节数组起始地址的内存空间
        vecArray = new AllocatedNativeObject(i * SIZE_IOVEC, false);
	//获取字节序列数组包装类起始地址
        address = vecArray.address();
    }

构造中我们需要关注以下节点:
1.创建存储字节数组起始地址的内存空间
vecArray = new AllocatedNativeObject(i * SIZE_IOVEC, false);

//AllocatedNativeObject
class AllocatedNativeObject extends NativeObject
{
    AllocatedNativeObject(int i, boolean flag)
    {
        super(i, flag);
    }
}

//NativeObject
protected NativeObject(int i, boolean flag)
    {
        if(!flag)
        {
	    //分配可以存i个字节的物理内存
            allocationAddress = unsafe.allocateMemory(i);
	    //初始化起始地址
            address = allocationAddress;
        } else
        {
	   //在分配i个字节的基础上,多分配一页内存,这个我们在前面以说,这里不再说
            int j = pageSize();
            long l = unsafe.allocateMemory(i + j);
            allocationAddress = l;
            address = (l + (long)j) - (l & (long)(j - 1));
        }
    }

2.获取字节序列数组包装类起始地址
address = vecArray.address();

//NativeObject
long address()
    {
        return address;
    }

从构造可以看出,主要是初始化字节缓冲区包装类的容量,存放字节缓冲区数组,
存放字节缓冲区position数组,存放字节缓冲区容量数组,字节缓冲区副本数组,
创建存储字节数组起始地址的内存空间,初始化字节缓冲区包装类起始地址。
再来看其他方法
//获取存放i个字节缓冲区的缓冲区包装类
static IOVecWrapper get(int i)
    {
        //获取线程本地的iovecwrapper
        IOVecWrapper iovecwrapper = (IOVecWrapper)cached.get();
        if(iovecwrapper != null && iovecwrapper.size < i)
        {
	    //iovecwrapper不为null,且容量小于i,则释放iovecwrapper内存
            iovecwrapper.vecArray.free();
            iovecwrapper = null;
        }
        if(iovecwrapper == null)
        {
	    //创建存放i个字节缓冲区的缓冲区包装类
            iovecwrapper = new IOVecWrapper(i);
	    //添加iovecwrapper到引用对象Cleaner
            Cleaner.create(iovecwrapper, new Deallocator(iovecwrapper.vecArray));
	    //添加iovecwrapper到线程本地缓存
            cached.set(iovecwrapper);
        }
        return iovecwrapper;
    }

上面方法有两点要关注:
1.
//iovecwrapper不为null,且容量小于i,则释放iovecwrapper内存
 iovecwrapper.vecArray.free();

//AllocatedNativeObject
synchronized void free()
    {
        if(allocationAddress != 0L)
        {
	    //释放物理内存
            unsafe.freeMemory(allocationAddress);
            allocationAddress = 0L;
        }
    }

2.
//添加iovecwrapper到引用对象清除器Cleaner
 Cleaner.create(iovecwrapper, new Deallocator(iovecwrapper.vecArray));

这一点我们在前面相关文章中有讲,我们简单看一下Deallocator
//Deallocator,引用对象清除器
private static class Deallocator
    implements Runnable
{
   //这个方法为清除器,实际执行的操作,即释放分配给iovecwrapper的物理内存
    public void run()
    {
        obj.free();
    }
    private final AllocatedNativeObject obj;

    Deallocator(AllocatedNativeObject allocatednativeobject)
    {
        obj = allocatednativeobject;
    }
}

从 get(int i),可以看出实际上,先获取线程本地缓存中的iovecwrapper,如果
iovecwrapper不为null,且容量小于i,则释放iovecwrapper内存,置空iovecwrapper;
否则创建容量为i的iovecwrapper,并将iovecwrapper添加的引用对象清除器Cleander,
并添加到线程本地缓存cache中。
再来其他方法
//添加字节数组
void setBuffer(int i, ByteBuffer bytebuffer, int j, int k)
 {
     //添加字节缓冲区到字节缓冲区包装类的字节数组中,并将字节缓冲区的position及
     容量remaining信息,存放到字节缓冲区包装类相应的数组中
     buf[i] = bytebuffer;
     position[i] = j;
     remaining[i] = k;
 }
//将字节缓冲区i的起始地址l写到内存中
void putBase(int i, long l)
{
    int j = SIZE_IOVEC * i + 0;
    if(addressSize == 4)
        //地址长度为4个字节
        vecArray.putInt(j, (int)l);
    else
        //地址长度为8个字节
        vecArray.putLong(j, l);
}
//将字节缓冲区i的容量l写到内存中
void putLen(int i, long l)
{
    int j = SIZE_IOVEC * i + LEN_OFFSET;
    if(addressSize == 4)
        vecArray.putInt(j, (int)l);
    else
        vecArray.putLong(j, l);
}
//添加字节缓冲区bytebuffer到字节缓冲区包装类的字节缓冲区副本数组中
 void setShadow(int i, ByteBuffer bytebuffer)
 {
     shadow[i] = bytebuffer;
 }
//获取索引i对应的字节缓冲区
 ByteBuffer getBuffer(int i)
 {
     return buf[i];
 }
//获取索引i对应的字节缓冲区Position
 int getPosition(int i)
 {
     return position[i];
 }
//获取索引i对应的字节缓冲区Remaining
 int getRemaining(int i)
 {
     return remaining[i];
 }
//获取索引i对应的字节缓冲区副本
 ByteBuffer getShadow(int i)
 {
     return shadow[i];
 }
//清除字节缓冲区包装类的字节缓冲区和相应的副本数组索引i对应的字节缓冲区
 void clearRefs(int i)
 {
     buf[i] = null;
     shadow[i] = null;
 }

小节:
IOVecWrapper构造,主要是初始化字节缓冲区包装类的容量,存放字节缓冲区数组,
存放字节缓冲区position数组,存放字节缓冲区容量数组,字节缓冲区副本数组,
创建存储字节数组起始地址的内存空间,初始化字节缓冲区包装类起始地址。
get(int i)方法,先获取线程本地缓存中的iovecwrapper,如果
iovecwrapper不为null,且容量小于i,则释放iovecwrapper内存,置空iovecwrapper;
否则创建容量为i的iovecwrapper,并将iovecwrapper添加的引用对象清除器Cleander,
并添加到线程本地缓存cache中。
在看完IOVecWrapper后,我们再回到写字节序列数组函数的关键部分:
 do
     //委托IOUtil写字节数组序列
     l = IOUtil.write(fd, abytebuffer, i, j, nd);
 while(l == -3L && isOpen());

//IOUtil
 static long write(FileDescriptor filedescriptor, ByteBuffer abytebuffer[], int i, int j, NativeDispatcher nativedispatcher)
        throws IOException
    {
        IOVecWrapper iovecwrapper;
        boolean flag;
        int k;
	//获取存放i个字节缓冲区的IOVecWrapper
        iovecwrapper = IOVecWrapper.get(j);
        flag = false;
        k = 0;
        long l1;
        int l = i + j;
        for(int i1 = i; i1 < l && k < IOV_MAX; i1++)
        {
            ByteBuffer bytebuffer = abytebuffer[i1];
            int j1 = bytebuffer.position();
            int k1 = bytebuffer.limit();
            if(!$assertionsDisabled && j1 > k1)
                throw new AssertionError();
            int j2 = j1 > k1 ? 0 : k1 - j1;
            if(j2 <= 0)
                continue;
	    //将字节缓冲区添加到iovecwrapper的字节缓冲区数组中
            iovecwrapper.setBuffer(k, bytebuffer, j1, j2);
            if(!(bytebuffer instanceof DirectBuffer))
            {
	       //获取容量为j2临时DirectByteBuffer
                ByteBuffer bytebuffer2 = Util.getTemporaryDirectBuffer(j2);
		//将字节序列写到DirectByteBuffer
                bytebuffer2.put(bytebuffer);
		//读写转换
                bytebuffer2.flip();
                iovecwrapper.setShadow(k, bytebuffer2);
                bytebuffer.position(j1);
                bytebuffer = bytebuffer2;
                j1 = bytebuffer2.position();
            }
	    //将字节缓冲区的起始地址写到iovecwrapper
            iovecwrapper.putBase(k, ((DirectBuffer)bytebuffer).address() + (long)j1);
	    //将字节缓冲区的实际容量写到iovecwrapper
            iovecwrapper.putLen(k, j2);
            k++;
        }

        if(k != 0)
            break MISSING_BLOCK_LABEL_267;
        l1 = 0L;
        if(!flag)
        {
            for(int i2 = 0; i2 < k; i2++)
            {
	        //获取iovecwrapper索引i2对应的字节序列副本
                ByteBuffer bytebuffer1 = iovecwrapper.getShadow(i2);
                if(bytebuffer1 != null)
		   //如果字节序列不为空,则添加到当前线程的缓存区中
                    Util.offerLastTemporaryDirectBuffer(bytebuffer1);
		 //清除索引i2对应的字节序列在iovecwrapper中的字节序列数组,及相应副本数组的信息
                iovecwrapper.clearRefs(i2);
            }

        }
        return l1;
        long l4;
	//委托给nativedispatcher,将iovecwrapper的缓冲区数据,写到filedescriptor对应的输出流中。
        long l2 = nativedispatcher.writev(filedescriptor, iovecwrapper.address, k);
	...
}

在IOUtil上面的write方法中我们需要关注的是下面这一句
//委托给nativedispatcher,将iovecwrapper的缓冲区数据,写到filedescriptor对应的输出流中。
 long l2 = nativedispatcher.writev(filedescriptor, iovecwrapper.address, k);

nativedispatcher在SocketChannelImpl中实际为SocketDispatcher
//SocketDispatcher
long writev(FileDescriptor filedescriptor, long l, int i)
        throws IOException
    {
        return writev0(filedescriptor, l, i);
    }
static native long writev0(FileDescriptor filedescriptor, long l, int i)
        throws IOException;

至此我们把SocketChannelImpl写ByteBuffer数组方法看完,首先同步写锁,确保通道,输出流打开,连接建立委托给IOUtil,将ByteBuffer数组写到输出流中,这一过程为获取存放i个字节缓冲区的IOVecWrapper,遍历ByteBuffer数组m,将字节缓冲区添加到iovecwrapper的字节缓冲区数组中,如果ByteBuffer非Direct类型,委托Util从当前线程的缓冲区获取容量为j2临时DirectByteBuffer,并将ByteBuffer写到DirectByteBuffer,并将DirectByteBuffer添加到iovecwrapper的字节缓冲区(Shadow-Direct)数组中,将字节缓冲区的起始地址写到iovecwrapper,字节缓冲区的实际容量写到iovecwrapper;遍历iovecwrapper的字节缓冲区(Shadow-Direct)数组,将Shadow数组中的DirectByteBuffer通过Util添加到本地线程的缓存区中,并清除DirectByteBuffer在iovecwrapper的相应数组中的信息;最后通过
SocketDispatcher,将iovecwrapper的缓冲区数据,写到filedescriptor对应的输出流中。

总结:

SocketChannelImpl写ByteBuffer数组方法,首先同步写锁,确保通道,输出流打开,连接建立委托给IOUtil,将ByteBuffer数组写到输出流中,这一过程为获取存放i个字节缓冲区的IOVecWrapper,遍历ByteBuffer数组m,将字节缓冲区添加到iovecwrapper的字节缓冲区数组中,如果ByteBuffer非Direct类型,委托Util从当前线程的缓冲区获取容量为j2临时DirectByteBuffer,并将ByteBuffer写到DirectByteBuffer,并将DirectByteBuffer添加到iovecwrapper的字节缓冲区(Shadow-Direct)数组中,将字节缓冲区的起始地址写到iovecwrapper,字节缓冲区的实际容量写到iovecwrapper;遍历iovecwrapper的字节缓冲区(Shadow-Direct)数组,将Shadow数组中的DirectByteBuffer通过Util添加到本地线程的缓存区中,并清除DirectByteBuffer在iovecwrapper的相应数组中的信息;最后通过
SocketDispatcher,将iovecwrapper的缓冲区数据,写到filedescriptor对应的输出流中。

SocketChannelImpl 解析三(接收数据):http://donald-draper.iteye.com/blog/2372590
0
0
分享到:
评论

相关推荐

    Ice-3.7.4.msi for windows版

    at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:574) at IceInternal.Network.doFinishConnect(Network.java:393) ... 6 more 这种报错是ICE服务端没有起来,telnet服务端ICE的端口不通...

    node-v0.10.13-sunos-x86.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    课设毕设基于SSM的高校二手交易平台-LW+PPT+源码可运行.zip

    课设毕设基于SSM的高校二手交易平台--LW+PPT+源码可运行

    软件设计师讲义.md

    软件设计师讲义.md

    时间序列预测,股票方向应用,使用transformer-lstm融合的模型算法

    适用人群 针对有一定机器学习和深度学习背景的专业人士,特别是那些对时间序列预测和Transformer以及LSTM模型有兴趣的人。需要一定的Python知识基础 适用场景 用于处理时间序列数据,尤其是在金融领域,示例是股票价格预测。Transformer模型和LSTM的混合使用表明,代码的目的是利用这两种模型的优势来提高预测准确性。 目标 代码的主要目标是利用Transformer模型和LSTM模型来预测时间序列数据,如股票价格。通过实现这两种模型,代码旨在提供一个强大的工具来进行更准确的时间序列分析和预测。

    Autojs-PJYSDK-泡椒云网络验证-v1.15.zip

    Autojs-PJYSDK-泡椒云网络验证-v1.15.zip

    nodejs-ia32-0.10.20.tgz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    Java项目之jspm足球俱乐部网上商城系统(源码 + 说明文档)

    Java项目之jspm足球俱乐部网上商城系统(源码 + 说明文档) 第二章 技术介绍 5 2.1 结构简介 5 2.2MySQL 介绍 5 2.3MySQL环境配置 5 2.4Java语言简介 6 2.5JSP技术 7 2.6 SSM框架 7 第三章 系统分析与设计 9 3.1系统说明 9 3.2系统可行性分析 9 3.2.1技术可行性 9 3.2.2经济可行性 9 3.2.3操作可行性 10 3.2.4运行可行性 10 3.3系统的设计思想 10 3.4系统功能结构 11 3.5系统流程分析 12 3.5.1操作流程 12 3.5.2添加信息流程 13 3.5.3删除信息流程 14 第四章 数据库设计 15 4.1数据库概念设计 15 4.2数据表设计 16 第五章 系统的详细设计 23 5.1系统首页的设计 23 5.2后台功能模块 25 5.2.1管理员功能模块 25 5.2.2用户功能模块 27 第六章 系统测试 29 6.1系统测试方法 29 6.2系统功能测试 29

    2024年第九届数维杯数学建模挑战赛题目.rar

    2024年第九届数维杯数学建模挑战赛题目.rar

    springboot(火车站订票管理系统)

    开发语言:Java JDK版本:JDK1.8(或11) 服务器:tomcat 数据库:mysql 5.6/5.7(或8.0) 数据库工具:Navicat 开发软件:idea 依赖管理包:Maven 代码+数据库保证完整可用,可提供远程调试并指导运行服务(额外付费)~ 如果对系统的中的某些部分感到不合适可提供修改服务,比如题目、界面、功能等等... 声明: 1.项目已经调试过,完美运行 2.需要远程帮忙部署项目,需要额外付费 3.本项目有演示视频,如果需要观看,请联系我v:19306446185 4.调试过程中可帮忙安装IDEA,eclipse,MySQL,JDK,Tomcat等软件 重点: 需要其他Java源码联系我,更多源码任你选,你想要的源码我都有! https://img-blog.csdnimg.cn/direct/e73dc0ac8d27434b86d886db5a438c71.jpeg

    springboot(英语知识应用网站)

    开发语言:Java JDK版本:JDK1.8(或11) 服务器:tomcat 数据库:mysql 5.6/5.7(或8.0) 数据库工具:Navicat 开发软件:idea 依赖管理包:Maven 代码+数据库保证完整可用,可提供远程调试并指导运行服务(额外付费)~ 如果对系统的中的某些部分感到不合适可提供修改服务,比如题目、界面、功能等等... 声明: 1.项目已经调试过,完美运行 2.需要远程帮忙部署项目,需要额外付费 3.本项目有演示视频,如果需要观看,请联系我v:19306446185 4.调试过程中可帮忙安装IDEA,eclipse,MySQL,JDK,Tomcat等软件 重点: 需要其他Java源码联系我,更多源码任你选,你想要的源码我都有! https://img-blog.csdnimg.cn/direct/e73dc0ac8d27434b86d886db5a438c71.jpeg

    node-v0.8.27-x64.msi

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    node-v0.11.7-sunos-x86.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    【课程设计】实现的金融风控贷款违约预测python源码.zip

    【课程设计】实现的金融风控贷款违约预测python源码.zip

    Java面向对象综合实验,实现拼图游戏.zip

    游戏的最终成品展示,然后再一步一步的从0开始,完成游戏里面每一个细节。 游戏运行之后,就是这样的界面。 刚开始打开,是登录页面,因为是第一次运行,需要注册。点击注册就会跳转到注册页面 在注册页面我们可以注册账号,用户名如果已存在则会注册失败。 在游戏主界面中,我们可以利用上下左右移动小图片去玩游戏,还有快捷键A可以查看最终效果,W一键通关。 我们在写游戏的时候,也是一部分一部分完成的。 先写游戏主界面,实现步骤如下: 1,完成最外层窗体的搭建。 2,再把菜单添加到窗体当中。 3,把小图片添加到窗体当中。 4,打乱数字图片的顺序。 5,让数字图片可以移动起来。 6,通关之后的胜利判断。 7,添加其他额外的功能。

    学位英语复习资料 学位英语复习资料

    学位英语复习资料

    1plusx_1_proj_test_231125_答案.zip

    1plusx_1_proj_test_231125_答案.zip

    人工智能+深度学习+深度学习数学基础+整理完整版

    【项目资源】:汇聚了云计算、区块链、网络安全、前端设计、后端架构、UI/UX设计、游戏开发、移动应用开发、虚拟现实(VR)、增强现实(AR)、3D建模与渲染、云计算服务、网络安全工具等各类技术项目的素材和模板。包括AWS、Azure、Docker、Kubernetes、React、Vue、Angular、Node.js、Django、Flask、Unity、Unreal Engine、Blender、Sketch、Figma、Wireshark、Nmap等项目的素材和模板。【项目质量】:所有素材和模板都经过精心筛选和整理,确保满足专业标准。在发布前,我们已经对功能进行了全面测试,确保其稳定性和可用性。【适用人群】:适合对技术充满热情的初学者、希望提升专业技能的中级开发者、以及寻求创新解决方案的高级工程师。无论是个人项目、团队合作、课程设计还是商业应用,都能在这里找到合适的资源。【附加价值】:这些项目资源不仅具有很高的学习价值,而且能够直接应用于实际项目中,提高开发效率。对于有志于深入研究或拓展新领域的人来说,它们提供了丰富的灵感和基础框架,帮助你快速构建出令人惊艳的作品。

    课设毕设基于SSM的人才公寓管理系统-LW+PPT+源码可运行.zip

    课设毕设基于SSM的人才公寓管理系统--LW+PPT+源码可运行

    unity大纲资料.txt

    unity游戏

Global site tag (gtag.js) - Google Analytics