客户端(大概有100个)使用什么socket模型好

客户端(大概有100个)使用什么socket模型好,第1张

1、普通的阻塞和非阻塞编程。

利用线程池技术和内存池,SOCKET池技术,基本可以处理一千五百个左右的SOCKET连接,但我们一般使用的机器大约有两M内存,而在不改变线程堆栈的大小情况下,我们至多可以创建一千七八百个线程,不过也就基本动不了了。我们测试基本到一千个线程左右,机器就很慢了。

还有在WIN-XP-SP2中,对单进程中的线程的并发做了处理,默认是10个,修改的方法网上多的是。

2、SELECT模型(异步和同步)

这个模型在单线程的情况下默认是64个最大SOCKET连接。你可以修改WINSOCK2h这个文件中的FD_SETSIZE,但不得超过底层WINSOCK的限制(1024),但如果采用多线程的话,可以处理更多,其实际的最大数量,在单线程里建议不超过1000个,至于多线程,应该也要控制线程切换的效率和数据处理的时间。应该几千个没有什么问题。

3、WSAASYNCSELECT模型

这个模型利用的是消息机制,建议不超过1000个。

4、WSAEVENTSELECT模型

这个模型利用的是事件驱动方式,单个线程不超过64个(WSAWaitForMultipleEvents最多等待64个事件),如果多SOCKET并发宜采用线程池技术,应该几千个没什么问题。

5、重叠IO

应该几千个没问题。这个毕竟是下面IOCP的一个技术基础。

6、IOCP完成端口+重叠IO

这个是解决SOCKET通信的终极武器,可惜只用在WIN上和2000以上,LINUX上好象有一个类似的EPOLL,而且好象比这个还好用,没具体用过。这个东东解决几万个SOCKET并发应该是很轻松,当然你得编程水平和技术得跟上。

从网上查看说这个东西如果使用的服务器版的操作系统和机器最大可到一百W,太恐怖了吧,那家伙自己在XP上最大达到了五万,但不断出现内存溢出的BUG。他最高到了9W。

event_base_get_method函数能够获取对应的event_base使用的是哪个后端(即select、IOCP这些函数)。该函数返回一个字符串,字符串的内容就是select、poll、iocp这些后端的名字。 更正一下,由于在WIndows中,event_base_get_method函数返回的是win

第1章 计算机网络基础

11网络的概念和网络的组成

12计算机网络参考模型

121协议层次

122TCP/IP参考模型

123应用层(Application Layer)

124传输层(Transport Layer)

125网络层(Network Layer)

126链路层(Link Layer)

127物理层(Physical Layer)

13网络程序寻址方式

131 MAC地址

132 IP地址

133子网寻址

134端口号

135网络地址转换(NAT)

14网络应用程序设计基础

141网络程序体系结构

142网络程序通信实体

143网络程序开发环境

第2章 Winsock编程接口

21 Winsock库

211 Winsock库的装入和释放

212封装CInitSock类

22 Winsock的寻址方式和字节顺序

221 Winsock寻址

222字节顺序

223获取地址信息

23 Winsock编程详解

231 Winsock编程流程

232典型过程图

233 TCP服务器和客户端程序举例

234 UDP编程

24网络对时程序实例

241时间协议(Time Protocol)

242 TCP/IP实现代码

第3章 Windows套接字I/O模型

31套接字模式

311阻塞模式

312非阻塞模式

32选择(select)模型

321 select函数

322应用举例

33 WSAAsyncSelect模型

331消息通知和WSAAsyncSelect函数

332应用举例

34 WSAEventSelect模型

341 WSAEventSelect函数

342应用举例

343基于WSAEventSelect模型的服务器设计

35重叠(Overlapped)I/O模型

351重叠I/O函数

352事件通知方式

353基于重叠I/O模型的服务器设计

第4章 IOCP与可伸缩网络程序

41完成端口I/O模型

411什么是完成端口(completion port)对象

412使用IOCP的方法

413示例程序

414恰当地关闭IOCP

42 Microsoft扩展函数

421 GetAcceptExSockaddrs函数

422 TransmitFile函数

423 TransmitPackets函数

424 ConnectEx函数

425 DisconnectEx函数

43可伸缩服务器设计注意事项

431内存资源管理

432接受连接的方法

433恶意客户连接问题

434包重新排序问题

44可伸缩服务器系统设计实例

441 CIOCPServer类的总体结构

442数据结构定义和内存池方案

443自定义帮助函数

444开启服务和停止服务

445 I/O处理线程

446用户接口和测试程序

第5章 互联网广播和IP多播

51套接字选项和I/O控制命令

511套接字选项

512 I/O控制命令

52广播通信

53 IP多播(Multicasting)

531多播地址

532组管理协议(IGMP)

533使用IP多播

54基于IP多播的组讨论会实例

541定义组讨论会协议

542线程通信机制

543封装CGroupTalk类

544程序界面

第6章 原始套接字

第7章 Winsock服务提供者接口(SPI)

第8章 Windows网络驱动接口标准(NDIS)和协议驱动的开发

第9章 网络扫描与检测技术

第10章 点对点(P2P)网络通信技术

第11章 核心层网络封包截获技术

第12章 Windows网络防火墙开发技术

第13章 IP帮助函数

第14章 Email协议及其编程

……

作者:美团技术团队

链接:https://zhuanlanzhihucom/p/23488863

来源:知乎

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。

那么NIO的本质是什么样的呢?它是怎样与事件模型结合来解放线程、提高系统吞吐的呢?

本文会从传统的阻塞I/O和线程池模型面临的问题讲起,然后对比几种常见I/O模型,一步步分析NIO怎么利用事件模型处理I/O,解决线程池瓶颈处理海量连接,包括利用面向事件的方式编写服务端/客户端程序。最后延展到一些高级主题,如Reactor与Proactor模型的对比、Selector的唤醒、Buffer的选择等。

注:本文的代码都是伪代码,主要是为了示意,不可用于生产环境。

传统BIO模型分析

让我们先回忆一下传统的服务器端同步阻塞I/O处理(也就是BIO,Blocking I/O)的经典编程模型:

{

ExecutorService executor = ExcutorsnewFixedThreadPollExecutor(100);//线程池

ServerSocket serverSocket = new ServerSocket();

serverSocketbind(8088);

while(!ThreadcurrentThreadisInturrupted()){//主线程死循环等待新连接到来

Socket socket = serverSocketaccept();

executorsubmit(new ConnectIOnHandler(socket));//为新的连接创建新的线程

}

class ConnectIOnHandler extends Thread{

private Socket socket;

public ConnectIOnHandler(Socket socket){

thissocket = socket;

}

public void run(){

while(!ThreadcurrentThreadisInturrupted()&&!socketisClosed()){死循环处理读写事件

String someThing = socketread()//读取数据

if(someThing!=null){

//处理数据

socketwrite()//写数据

}

}

}

}

这是一个经典的每连接每线程的模型,之所以使用多线程,主要原因在于socketaccept()、socketread()、socketwrite()三个主要函数都是同步阻塞的,当一个连接在处理I/O的时候,系统是阻塞的,如果是单线程的话必然就挂死在那里;但CPU是被释放出来的,开启多线程,就可以让CPU去处理更多的事情。其实这也是所有使用多线程的本质:

利用多核。

当I/O阻塞系统,但CPU空闲的时候,可以利用多线程使用CPU资源。

现在的多线程一般都使用线程池,可以让线程的创建和回收成本相对较低。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的I/O并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。

不过,这个模型最本质的问题在于,严重依赖于线程。但线程是很"贵"的资源,主要表现在:

线程的创建和销毁成本很高,在Linux这样的操作系统中,线程本质上就是一个进程。创建和销毁都是重量级的系统函数。

线程本身占用较大内存,像Java的线程栈,一般至少分配512K~1M的空间,如果系统中的线程数过千,恐怕整个JVM的内存都会被吃掉一半。

线程的切换成本是很高的。操作系统发生线程切换的时候,需要保留线程的上下文,然后执行系统调用。如果线程数过高,可能执行线程切换的时间甚至会大于线程执行的时间,这时候带来的表现往往是系统load偏高、CPU sy使用率特别高(超过20%以上),导致系统几乎陷入不可用的状态。

容易造成锯齿状的系统负载。因为系统负载是用活动线程数或CPU核心数,一旦线程数量高但外部网络环境不是很稳定,就很容易造成大量请求的结果同时返回,激活大量阻塞线程从而使系统负载压力过大。

所以,当面对十万甚至百万级连接的时候,传统的BIO模型是无能为力的。随着移动端应用的兴起和各种网络游戏的盛行,百万级长连接日趋普遍,此时,必然需要一种更高效的I/O处理模型。

NIO是怎么工作的

很多刚接触NIO的人,第一眼看到的就是Java相对晦涩的API,比如:Channel,Selector,Socket什么的;然后就是一坨上百行的代码来演示NIO的服务端Demo……瞬间头大有没有?

我们不管这些,抛开现象看本质,先分析下NIO是怎么工作的。

常见I/O模型对比

所有的系统I/O都分为两个阶段:等待就绪和操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。

需要说明的是等待就绪的阻塞是不使用CPU的,是在“空等”;而真正的读写操作的阻塞是使用CPU的,真正在"干活",而且这个过程非常快,属于memory copy,带宽通常在1GB/s级别以上,可以理解为基本不耗时。

下图是几种常见I/O模型的对比:

以socketread()为例子:

传统的BIO里面socketread(),如果TCP RecvBuffer里没有数据,函数会一直阻塞,直到收到数据,返回读到的数据。

对于NIO,如果TCP RecvBuffer有数据,就把数据从网卡读到内存,并且返回给用户;反之则直接返回0,永远不会阻塞。

最新的AIO(Async I/O)里面会更进一步:不但等待就绪是非阻塞的,就连数据从网卡到内存的过程也是异步的。

换句话说,BIO里用户最关心“我要读”,NIO里用户最关心"我可以读了",在AIO模型里用户更需要关注的是“读完了”。

NIO一个重要的特点是:socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)。

如何结合事件模型使用NIO同步非阻塞特性

回忆BIO模型,之所以需要多线程,是因为在进行I/O操作的时候,一是没有办法知道到底能不能写、能不能读,只能"傻等",即使通过各种估算,算出来操作系统没有能力进行读写,也没法在socketread()和socketwrite()函数中返回,这两个函数无法进行有效的中断。所以除了多开线程另起炉灶,没有好的办法利用CPU。

NIO的读写函数可以立刻返回,这就给了我们不开线程利用CPU的最好机会:如果一个连接不能读写(socketread()返回0或者socketwrite()返回0),我们可以把这件事记下来,记录的方式通常是在Selector上注册标记位,然后切换到其它就绪的连接(channel)继续进行读写。

下面具体看下如何利用事件模型单线程处理所有I/O请求:

NIO的主要事件有几个:读就绪、写就绪、有新连接到来。

我们首先需要注册当这几个事件到来的时候所对应的处理器。然后在合适的时机告诉事件选择器:我对这个事件感兴趣。对于写操作,就是写不出去的时候对写事件感兴趣;对于读操作,就是完成连接和系统没有办法承载新读入的数据的时;对于accept,一般是服务器刚启动的时候;而对于connect,一般是connect失败需要重连或者直接异步调用connect的时候。

其次,用一个死循环选择就绪的事件,会执行系统调用(Linux 26之前是select、poll,26之后是epoll,Windows是IOCP),还会阻塞的等待新事件的到来。新事件到来的时候,会在selector上注册标记位,标示可读、可写或者有连接到来。

注意,select是阻塞的,无论是通过操作系统的通知(epoll)还是不停的轮询(select,poll),这个函数是阻塞的。所以你可以放心大胆地在一个while(true)里面调用这个函数而不用担心CPU空转。

所以我们的程序大概的模样是:

  interface ChannelHandler{

     void channelReadable(Channel channel);

     void channelWritable(Channel channel);

  }

  class Channel{

    Socket socket;

    Event event;//读,写或者连接

  }

  //IO线程主循环:

  class IoThread extends Thread{

  public void run(){

  Channel channel;

  while(channel=Selectorselect()){//选择就绪的事件和对应的连接

     if(channelevent==accept){

        registerNewChannelHandler(channel);//如果是新连接,则注册一个新的读写处理器

     }

     if(channelevent==write){

        getChannelHandler(channel)channelWritable(channel);//如果可以写,则执行写事件

     }

     if(channelevent==read){

         getChannelHandler(channel)channelReadable(channel);//如果可以读,则执行读事件

     }

   }

  }

  Map<Channel,ChannelHandler> handlerMap;//所有channel的对应事件处理器

 }

这个程序很简短,也是最简单的Reactor模式:注册所有感兴趣的事件处理器,单线程轮询选择就绪事件,执行事件处理器。

优化线程模型

由上面的示例我们大概可以总结出NIO是怎么解决掉线程的瓶颈并处理海量连接的:

NIO由原来的阻塞读写(占用线程)变成了单线程轮询事件,找到可以进行读写的网络描述符进行读写。除了事件的轮询是阻塞的(没有可干的事情必须要阻塞),剩余的I/O操作都是纯CPU操作,没有必要开启多线程。

并且由于线程的节约,连接数大的时候因为线程切换带来的问题也随之解决,进而为处理海量连接提供了可能。

单线程处理I/O的效率确实非常高,没有线程切换,只是拼命的读、写、选择事件。但现在的服务器,一般都是多核处理器,如果能够利用多核心进行I/O,无疑对效率会有更大的提高。

仔细分析一下我们需要的线程,其实主要包括以下几种:

事件分发器,单线程选择就绪的事件。

I/O处理器,包括connect、read、write等,这种纯CPU操作,一般开启CPU核心个线程就可以。

业务线程,在处理完I/O后,业务一般还会有自己的业务逻辑,有的还会有其他的阻塞I/O,如DB操作,RPC等。只要有阻塞,就需要单独的线程。

Java的Selector对于Linux系统来说,有一个致命限制:同一个channel的select不能被并发的调用。因此,如果有多个I/O线程,必须保证:一个socket只能属于一个IoThread,而一个IoThread可以管理多个socket。

另外连接的处理和读写的处理通常可以选择分开,这样对于海量连接的注册和读写就可以分发。虽然read()和write()是比较高效无阻塞的函数,但毕竟会占用CPU,如果面对更高的并发则无能为力。

NIO在客户端的魔力

通过上面的分析,可以看出NIO在服务端对于解放线程,优化I/O和处理海量连接方面,确实有自己的用武之地。那么在客户端上,NIO又有什么使用场景呢

常见的客户端BIO+连接池模型,可以建立n个连接,然后当某一个连接被I/O占用的时候,可以使用其他连接来提高性能。

但多线程的模型面临和服务端相同的问题:如果指望增加连接数来提高性能,则连接数又受制于线程数、线程很贵、无法建立很多线程,则性能遇到瓶颈。

每连接顺序请求的Redis

对于Redis来说,由于服务端是全局串行的,能够保证同一连接的所有请求与返回顺序一致。这样可以使用单线程+队列,把请求数据缓冲。然后pipeline发送,返回future,然后channel可读时,直接在队列中把future取回来,done()就可以了。

伪代码如下:

class RedisClient Implements ChannelHandler{

private BlockingQueue CmdQueue;

private EventLoop eventLoop;

private Channel channel;

class Cmd{

 String cmd;

 Future result;

}

public Future get(String key){

  Cmd cmd= new Cmd(key);

  queueoffer(cmd);

  eventLoopsubmit(new Runnable(){

       List list = new ArrayList();

       queuedrainTo(list);

       if(channelisWritable()){

        channelwriteAndFlush(list);

       }

  });

}

public void ChannelReadFinish(Channel channel,Buffer Buffer){

   List result = handleBuffer();//处理数据

   //从cmdQueue取出future,并设值,futuredone();

}

public void ChannelWritable(Channel channel){

  channelflush();

}

}

这样做,能够充分的利用pipeline来提高I/O能力,同时获取异步处理能力。

多连接短连接的HttpClient

类似于竞对抓取的项目,往往需要建立无数的HTTP短连接,然后抓取,然后销毁,当需要单机抓取上千网站线程数又受制的时候,怎么保证性能呢

何不尝试NIO,单线程进行连接、写、读操作?如果连接、读、写操作系统没有能力处理,简单的注册一个事件,等待下次循环就好了。

如何存储不同的请求/响应呢?由于http是无状态没有版本的协议,又没有办法使用队列,好像办法不多。比较笨的办法是对于不同的socket,直接存储socket的引用作为map的key。

常见的RPC框架,如Thrift,Dubbo

这种框架内部一般维护了请求的协议和请求号,可以维护一个以请求号为key,结果的result为future的map,结合NIO+长连接,获取非常不错的性能。

NIO高级主题

Proactor与Reactor

一般情况下,I/O 复用机制需要事件分发器(event dispatcher)。 事件分发器的作用,即将那些读写事件源分发给各读写事件的处理者,就像送快递的在楼下喊: 谁谁谁的快递到了, 快来拿吧!开发人员在开始的时候需要在分发器那里注册感兴趣的事件,并提供相应的处理者(event handler),或者是回调函数;事件分发器在适当的时候,会将请求的事件分发给这些handler或者回调函数。

涉及到事件分发器的两种模式称为:Reactor和Proactor。 Reactor模式是基于同步I/O的,而Proactor模式是和异步I/O相关的。在Reactor模式中,事件分发器等待某个事件或者可应用或个操作的状态发生(比如文件描述符可读写,或者是socket可读写),事件分发器就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。

而在Proactor模式中,事件处理者(或者代由事件分发器发起)直接发起一个异步读写操作(相当于请求),而实际的工作是由操作系统来完成的。发起时,需要提供的参数包括用于存放读到数据的缓存区、读的数据大小或用于存放外发数据的缓存区,以及这个请求完后的回调函数等信息。事件分发器得知了这个请求,它默默等待这个请求的完成,然后转发完成事件给相应的事件处理者或者回调。举例来说,在Windows上事件处理者投递了一个异步IO操作(称为overlapped技术),事件分发器等IO Complete事件完成。这种异步模式的典型实现是基于操作系统底层异步API的,所以我们可称之为“系统级别”的或者“真正意义上”的异步,因为具体的读写是由操作系统代劳的。

举个例子,将有助于理解Reactor与Proactor二者的差异,以读操作为例(写操作类似)。

在Reactor中实现读

注册读就绪事件和相应的事件处理器。

事件分发器等待事件。

事件到来,激活分发器,分发器调用事件对应的处理器。

事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。

在Proactor中实现读:

处理器发起异步读操作(注意:操作系统必须支持异步IO)。在这种情况下,处理器无视IO就绪事件,它关注的是完成事件。

事件分发器等待操作完成事件。

在分发器等待过程中,操作系统利用并行的内核线程执行实际的读操作,并将结果数据存入用户自定义缓冲区,最后通知事件分发器读操作完成。

事件分发器呼唤处理器。

事件处理器处理用户自定义缓冲区中的数据,然后启动一个新的异步操作,并将控制权返回事件分发器。

可以看出,两个模式的相同点,都是对某个I/O事件的事件通知(即告诉某个模块,这个I/O操作可以进行或已经完成)。在结构上,两者也有相同点:事件分发器负责提交IO操作(异步)、查询设备是否可操作(同步),然后当条件满足时,就回调handler;不同点在于,异步情况下(Proactor),当回调handler时,表示I/O操作已经完成;同步情况下(Reactor),回调handler时,表示I/O设备可以进行某个操作(can read 或 can write)。

下面,我们将尝试应对为Proactor和Reactor模式建立可移植框架的挑战。在改进方案中,我们将Reactor原来位于事件处理器内的Read/Write操作移至分发器(不妨将这个思路称为“模拟异步”),以此寻求将Reactor多路同步I/O转化为模拟异步I/O。以读操作为例子,改进过程如下:

注册读就绪事件和相应的事件处理器。并为分发器提供数据缓冲区地址,需要读取数据量等信息。

分发器等待事件(如在select()上等待)。

事件到来,激活分发器。分发器执行一个非阻塞读操作(它有完成这个操作所需的全部信息),最后调用对应处理器。

事件处理器处理用户自定义缓冲区的数据,注册新的事件(当然同样要给出数据缓冲区地址,需要读取的数据量等信息),最后将控制权返还分发器。

如我们所见,通过对多路I/O模式功能结构的改造,可将Reactor转化为Proactor模式。改造前后,模型实际完成的工作量没有增加,只不过参与者间对工作职责稍加调换。没有工作量的改变,自然不会造成性能的削弱。对如下各步骤的比较,可以证明工作量的恒定:

标准/典型的Reactor:

步骤1:等待事件到来(Reactor负责)。

步骤2:将读就绪事件分发给用户定义的处理器(Reactor负责)。

步骤3:读数据(用户处理器负责)。

步骤4:处理数据(用户处理器负责)。

改进实现的模拟Proactor:

步骤1:等待事件到来(Proactor负责)。

步骤2:得到读就绪事件,执行读数据(现在由Proactor负责)。

步骤3:将读完成事件分发给用户处理器(Proactor负责)。

步骤4:处理数据(用户处理器负责)。

对于不提供异步I/O API的操作系统来说,这种办法可以隐藏Socket API的交互细节,从而对外暴露一个完整的异步接口。借此,我们就可以进一步构建完全可移植的,平台无关的,有通用对外接口的解决方案。

代码示例如下:

interface ChannelHandler{

     void channelReadComplate(Channel channel,byte[] data);

     void channelWritable(Channel channel);

  }

  class Channel{

    Socket socket;

    Event event;//读,写或者连接

  }

  //IO线程主循环:

  class IoThread extends Thread{

  public void run(){

  Channel channel;

  while(channel=Selectorselect()){//选择就绪的事件和对应的连接

     if(channelevent==accept){

        registerNewChannelHandler(channel);//如果是新连接,则注册一个新的读写处理器

        Selectorinterested(read);

     }

     if(channelevent==write){

        getChannelHandler(channel)channelWritable(channel);//如果可以写,则执行写事件

     }

     if(channelevent==read){

         byte[] data = channelread();

         if(channelread()==0)//没有读到数据,表示本次数据读完了

         {

         getChannelHandler(channel)channelReadComplate(channel,data;//处理读完成事件

         }

         if(过载保护){

         Selectorinterested(read);

         }

     }

    }

   }

  Map<Channel,ChannelHandler> handlerMap;//所有channel的对应事件处理器

  }

Selectorwakeup()

主要作用

解除阻塞在Selectorselect()/select(long)上的线程,立即返回。

两次成功的select之间多次调用wakeup等价于一次调用。

如果当前没有阻塞在select上,则本次wakeup调用将作用于下一次select——“记忆”作用。

为什么要唤醒?

注册了新的channel或者事件。

channel关闭,取消注册。

优先级更高的事件触发(如定时器事件),希望及时处理。

原理

Linux上利用pipe调用创建一个管道,Windows上则是一个loopback的tcp连接。这是因为win32的管道无法加入select的fd set,将管道或者TCP连接加入select fd set。

wakeup往管道或者连接写入一个字节,阻塞的select因为有I/O事件就绪,立即返回。可见,wakeup的调用开销不可忽视。

Buffer的选择

通常情况下,操作系统的一次写操作分为两步:

将数据从用户空间拷贝到系统空间。

从系统空间往网卡写。同理,读操作也分为两步:

① 将数据从网卡拷贝到系统空间;

② 将数据从系统空间拷贝到用户空间。

对于NIO来说,缓存的使用可以使用DirectByteBuffer和HeapByteBuffer。如果使用了DirectByteBuffer,一般来说可以减少一次系统空间到用户空间的拷贝。但Buffer创建和销毁的成本更高,更不宜维护,通常会用内存池来提高性能。

如果数据量比较小的中小应用情况下,可以考虑使用heapBuffer;反之可以用directBuffer。

NIO存在的问题

使用NIO != 高性能,当连接数<1000,并发程度不高或者局域网环境下NIO并没有显著的性能优势。

NIO并没有完全屏蔽平台差异,它仍然是基于各个操作系统的I/O系统实现的,差异仍然存在。使用NIO做网络编程构建事件驱动模型并不容易,陷阱重重。

推荐大家使用成熟的NIO框架,如Netty,MINA等。解决了很多NIO的陷阱,并屏蔽了操作系统的差异,有较好的性能和编程模型。

老司机带你分析SpringMVC框架设计原理与实现

链接:https://panbaiducom/s/1cksL0_VmSMdkIXWFSOx19g

密码:57w4

Netty粘包分包现象及解决方案实战,防socket攻击

链接:https://panbaiducom/s/1kTF2oqHOqvrPJrKa7TpXOQ

密码:dk9n

大型企业级高并发下数据库水平切分之读写分离技巧详解

链接:https://panbaiducom/s/1OrXSGCCboqgVX2vgfC7Z7Q

密码:ri8q

分布式事务出现场景及解决方案详细剖析

链接:https://panbaiducom/s/1BBf6cePibN0xawFEY7A6ZA

密码:380p

以上都是小编收集了大神的灵药,喜欢的拿走吧!喜欢小编就轻轻关注一下吧!

消除瓶颈是提高服务器性能和并发能力的唯一途径。

如果你能够消除所有的瓶颈,你就能够最大的发挥硬件性能,让系统的性能和并发数到达最佳。

采用多线程多核编程,使用事件驱动或异步消息机制,尽量减少阻塞和等待操作(如I/O阻塞、同步等待或计时/超时等)。

原理:

1、多线程多核编程,消除cpu瓶颈。

2、采用IOCP或epoll,利用状态监测和通知方式,消除网络I/O阻塞瓶颈。

3、采用事件驱动或异步消息机制,可以消除不必要的等待操作。

4、如果是Linux,可以采用AIO来消除磁盘I/O阻塞瓶颈。

5、在事件驱动框架或异步消息中统一处理timer事件,变同步为异步,而且可以在一个线程处理无数timer事件。

6、深入分析外部的阻塞来源,消除它。

比如数据库查询较慢,导致服务器处理较慢,并发数上不去,这时就要优化数据库性能。

7、如果与某个其他server通信量很大,导致性能下降较多。

可以考虑把这两个server放在一个主机上,采用共享内存的方式来做IPC通信,可以大大提高性能。

DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
网站模板库 » 客户端(大概有100个)使用什么socket模型好

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情