`
sunqi
  • 浏览: 230120 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

accept建立连接(转)

 
阅读更多
编写服务器时,许多程序员习惯于使用高层次的组件、中间件(例如OO(面向对象)层层封装过的开源组件),相比于服务器的运行效率而言,他们更关注程序开发的效率,追求更快的完成项目功能点、希望应用代码完全不关心通讯细节。他们更喜欢在OO世界里,去实现某个接口、实现这个组件预定义的各种模式、设置组件参数来达到目的。学习复杂的通讯框架、底层细节,在习惯于使用OO语言的程序员眼里是绝对事倍功半的。以上做法无可厚非,但有一定的局限性,本文讲述的网络编程头前冠以“高性能”,它是指程序员设计编写的服务器需要处理很大的吞吐量,这与简单网络应用就有了质的不同。因为:1、高吞吐量下,容易触发到一些设计上的边界条件;2、偶然性的小概率事件,会在高吞吐量下变成必然性事件。3、IO是慢速的,高吞吐量通常意味着高并发,如同一时刻存在数以万计、十万计、百万计的TCP活动连接。所以,做高性能网络编程不能仅仅满足于学会开源组件、中间件是如何帮我实现期望功能的,对于企业级产品来说,需要了解更多的知识。
 
掌握高性能网络编程,涉及到对网络、操作系统协议栈、进程与线程、常见的网络组件等知识点,需要有丰富的项目开发经验,能够权衡服务器运行效率与项目开发效率。以下图来谈谈我个人对高性能网络编程的理解。
alt
 
上面这张图中,由上至下有以下特点:
•关注点,逐渐由特定业务向通用技术转移
•使用场景上,由专业领域向通用领域转移
•灵活性上要求越来越高
•性能要求越来越高
•对细节、原理的掌握,要求越来越高
•对各种异常情况的处理,要求越来越高
•稳定性越来越高,bug率越来越少
在做应用层的网络编程时,若服务器吞吐量大,则应该适度了解以上各层的关注点。
 
如上图红色文字所示,我认为编写高性能服务器的关注点有3个:
1、如果基于通用组件编程,关注点多是在组件如何封装套接字编程细节。为了使应用程序不感知套接字层,这些组件往往是通过各种回调机制来向应用层代码提供网络服务,通常,出于为应用层提供更高的开发效率,组件都大量使用了线程(Nginx等是个例外),当然,使用了线程后往往可以降低代码复杂度。但多线程引入的并发解决机制还是需要重点关注的,特别是锁的使用。另外,使用多线程意味着把应用层的代码复杂度扔给了操作系统,大吞吐量时,需要关注多线程给操作系统内核带来的性能损耗。
基于通用组件编程,为了程序的高性能运行,需要清楚的了解组件的以下特性:怎么使用IO多路复用或者异步IO的?怎么实现并发性的?怎么组织线程模型的?怎么处理高吞吐量引发的异常情况的?
 
2、通用组件只是在封装套接字,操作系统是通过提供套接字来为进程提供网络通讯能力的。所以,不了解套接字编程,往往对组件的性能就没有原理上的认识。学习套接字层的编程是有必要的,或许很少会自己从头去写,但操作系统的API提供方式经久不变,一经学会,受用终身,同时在项目的架构设计时,选用何种网络组件就非常准确了。
学习套接字编程,关注点主要在:套接字的编程方法有哪些?阻塞套接字的各方法是如何阻塞住当前代码段的?非阻塞套接字上的方法如何不阻塞当前代码段的?IO多路复用机制是怎样与套接字结合的?异步IO是如何实现的?网络协议的各种异常情况、操作系统的各种异常情况是怎么通过套接字传递给应用性程序的?
 
3、网络的复杂性会影响到服务器的吞吐量,而且,高吞吐量场景下,多种临界条件会导致应用程序的不正常,特别是组件中有bug或考虑不周或没有配置正确时。了解网络分组可以定位出这些问题,可以正确的配置系统、组件,可以正确的理解系统的瓶颈。
这里的关注点主要在:TCP、UDP、IP协议的特点?linux等操作系统如何处理这些协议的?使用tcpdump等抓包工具分析各网络分组。
 
一般掌握以上3点,就可以挥洒自如的实现高性能网络服务器了。
 
下面具体谈谈如何做到高性能网络编程。
众所周知,IO是计算机上最慢的部分,先不看磁盘IO,针对网络编程,自然是针对网络IO。网络协议对网络IO影响很大,当下,TCP/IP协议是毫无疑问的主流协议,本文就主要以TCP协议为例来说明网络IO。
网络IO中应用服务器往往聚焦于以下几个由网络IO组成的功能中:A)与客户端建立起TCP连接。B)读取客户端的请求流。C)向客户端发送响应流。D)关闭TCP连接。E)向其他服务器发起TCP连接。
要掌握住这5个功能,不仅仅需要熟悉一些API的使用,更要理解底层网络如何与上层API之间互相发生影响。同时,还需要对不同的场景下,如何权衡开发效率、进程、线程与这些API的组合使用。下面依次来说说这些网络IO。
 
 
1、与客户端建立起TCP连接
谈这个功能前,先来看看网络、协议、应用服务器间的关系
alt
 
上图中可知:
为简化不同场景下的编程,TCP/IP协议族划分了应用层、TCP传输层、IP网络层、链路层等,每一层只专注于少量功能。
例如,IP层只专注于每一个网络分组如何到达目的主机,而不管目的主机如何处理。
传输层最基本的功能是专注于端到端,也就是一台主机上的进程发出的包,如何到达目的主机上的某个进程。当然,TCP层为了可靠性,还额外需要解决3个大问题:丢包(网络分组在传输中存在的丢失)、重复(协议层异常引发的多个相同网络分组)、延迟(很久后网络分组才到达目的地)。
链路层则只关心以太网或其他二层网络内网络包的传输。
 
回到应用层,往往只需要调用类似于accept的API就可以建立TCP连接。建立连接的流程大家都了解--三次握手,它如何与accept交互呢?下面以一个不太精确却通俗易懂的图来说明之:
alt
 
研究过backlog含义的朋友都很容易理解上图。这两个队列是内核实现的,当服务器绑定、监听了某个端口后,这个端口的SYN队列和ACCEPT队列就建立好了。客户端使用connect向服务器发起TCP连接,当图中1.1步骤客户端的SYN包到达了服务器后,内核会把这一信息放到SYN队列(即未完成握手队列)中,同时回一个SYN+ACK包给客户端。一段时间后,在较中2.1步骤中客户端再次发来了针对服务器SYN包的ACK网络分组时,内核会把连接从SYN队列中取出,再把这个连接放到ACCEPT队列(即已完成握手队列)中。而服务器在第3步调用accept时,其实就是直接从ACCEPT队列中取出已经建立成功的连接套接字而已。
 
现有我们可以来讨论应用层组件:为何有的应用服务器进程中,会单独使用1个线程,只调用accept方法来建立连接,例如tomcat;有的应用服务器进程中,却用1个线程做所有的事,包括accept获取新连接。
 
原因在于:首先,SYN队列和ACCEPT队列都不是无限长度的,它们的长度限制与调用listen监听某个地址端口时传递的backlog参数有关。既然队列长度是一个值,那么,队列会满吗?当然会,如果上图中第1步执行的速度大于第2步执行的速度,SYN队列就会不断增大直到队列满;如果第2步执行的速度远大于第3步执行的速度,ACCEPT队列同样会达到上限。第1、2步不是应用程序可控的,但第3步却是应用程序的行为,假设进程中调用accept获取新连接的代码段长期得不到执行,例如获取不到锁、IO阻塞等。
 
那么,这两个队列满了后,新的请求到达了又将发生什么?
若SYN队列满,则会直接丢弃请求,即新的SYN网络分组会被丢弃;如果ACCEPT队列满,则不会导致放弃连接,也不会把连接从SYN列队中移出,这会加剧SYN队列的增长。所以,对应用服务器来说,如果ACCEPT队列中有已经建立好的TCP连接,却没有及时的把它取出来,这样,一旦导致两个队列满了后,就会使客户端不能再建立新连接,引发严重问题。
所以,如TOMCAT等服务器会使用独立的线程,只做accept获取连接这一件事,以防止不能及时的去accept获取连接。
 
那么,为什么如Nginx等一些服务器,在一个线程内做accept的同时,还会做其他IO等操作呢?
这里就带出阻塞和非阻塞的概念。应用程序可以把listen时设置的套接字设为非阻塞模式(默认为阻塞模式),这两种模式会导致accept方法有不同的行为。对阻塞套接字,accept行为如下图:
alt
 
这幅图中可以看到,阻塞套接字上使用accept,第一个阶段是等待ACCEPT队列不为空的阶段,它耗时不定,由客户端是否向自己发起了TCP请求而定,可能会耗时很长。
对非阻塞套接字,accept会有两种返回,如下图:
alt
 
非阻塞套接字上的accept,不存在等待ACCEPT队列不为空的阶段,它要么返回成功并拿到建立好的连接,要么返回失败。
 
所以,企业级的服务器进程中,若某一线程既使用accept获取新连接,又继续在这个连接上读、写字符流,那么,这个连接对应的套接字通常要设为非阻塞。原因如上图,调用accept时不会长期占用所属线程的CPU时间片,使得线程能够及时的做其他工作
分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

    没有accept可以建立TCP链接吗 668 - 684

    当收到客户端的ACK报文,服务端将连接移到全连接队列(ACCEPT队列),等待`accept()`函数取出,正式建立连接。 3. **客户端的connect()**:客户端调用`connect()`函数,发送SYN报文,等待服务端的响应,完成连接...

    网络编程socket之accept函数

    - 在多线程或多进程模型中,通常主进程或主线程负责监听连接请求,一旦`accept`函数返回一个已建立连接的套接字,就创建一个新的子进程或子线程来处理该连接。 2. **并发处理** - 对于高并发场景,可以使用事件...

    关于SOCKET中的accept函数的解释

    2. **建立连接**:一旦有客户端发起连接请求,内核就会从监听队列中取出对应的连接请求,并通过`accept`函数将其转换为连接套接字。 3. **返回连接套接字**:`accept`函数成功执行后,会返回一个新的连接套接字,该...

    Linux kernel tcp 连接建立详解listen_backlog

    在三次握手之后,连接会从未完成连接队列移动到已建立连接队列,这个已建立连接队列由accept()系统调用来处理。服务器通过accept()接受新的连接,并且可以对已建立的连接进行读写操作。 在本文中,作者通过编写简单...

    建立Http连接.rar

    HTTP连接还可以配置其他选项,如设置请求头(例如User-Agent、Accept等)、超时时间以及是否允许重定向。例如,你可以通过`connection.setRequestProperty()`方法设置请求头: ```java connection....

    java建立TCP长链接

    在TCP连接建立之前,需要经过三次握手的过程,而在连接结束后,通过四次挥手断开连接。 在Java中,我们通常使用`java.net.Socket`类和`java.net.ServerSocket`类来实现TCP通信。以下是建立TCP长连接的基本步骤: 1...

    TCP长短连接简单Demo

    在C++中,实现TCP连接通常会用到socket编程API,包括`socket()`函数创建套接字,`bind()`函数绑定本地地址,`listen()`函数开始监听,`accept()`函数接受连接请求,`connect()`函数建立连接,`send()`和`recv()`函数...

    C# socket异步长连接

    在长连接模式下,客户端可以反复发送和接收数据,而无需每次都重新建立连接,减少了网络资源的消耗和连接建立的时间成本。 在C#中实现异步长连接通常涉及以下几个关键步骤: 1. **创建Socket对象**:首先,你需要...

    vc 建立TCP连接服务器端实例

    在一个循环中,使用`accept`函数等待客户端的连接请求: ```cpp SOCKET ClientSocket = accept(ListenSocket, NULL, NULL); if (ClientSocket == INVALID_SOCKET) { // 错误处理:无法接受客户端连接 } ``` 一旦...

    面向连接的C/S编程

    这种模式与无连接的UDP协议不同,它更类似于电话系统,先拨号建立连接,通话完毕后再挂断。 在C/S架构中,客户端(Client)是发起请求的一方,而服务器端(Server)是接收并响应请求的一方。在面向连接的通信中,这...

    Socket连接-TCP测试

    客户端首先发送一个SYN(同步序列编号)包给服务器,表明它希望建立连接。服务器接收到这个包后,回复一个SYN+ACK(同步+确认)包,表示它已收到并同意建立连接。最后,客户端再次发送一个ACK包,确认服务器的响应,...

    Socket长连接demo

    服务器端通过`socket()`和`bind()`绑定端口,再用`listen()`等待连接请求,最后`accept()`接收连接请求,建立连接。 2. **数据传输**:连接建立后,客户端和服务器端可以通过`send()`和`recv()`或`write()`和`read...

    06TCP多连接建立与稳妥断开1

    在本文中,我们将探讨TCP多连接的建立与断开,主要关注TCP连接中的Socket对象管理和稳妥断开机制。TCP(Transmission Control Protocol)是一种面向连接的、可靠的传输层协议,它确保了数据的完整性和顺序。在多连接...

    对C#socket类通信的基本了解;程序内容:客户端 与服务端建立连接,客户端时时返回服务端的电脑时间;

    在标题和描述中提到的程序内容,是客户端与服务器端建立连接,并实时返回服务端的电脑时间。实现这一功能的关键步骤如下: 1. **服务器端**: - 创建一个Socket实例,指定协议类型(如TCP)和地址族。 - 使用`...

    LabVIEW TCP 通讯 自动连接 掉线自动重连

    1. **TCP客户端(TCP Client.vi)**:客户端通常发起连接请求,与服务器建立连接,发送数据,并接收服务器的响应。在`TCP Client.vi`中,我们可能会看到以下关键部分: - **连接函数**:使用`Open TCP Connection`...

    pb中建立一对一的连接.doc

    服务器端通过`object.accept(requestid)`函数接收客户端的连接请求,并通过`requestid`标识连接。当有新的连接时,可以弹出消息框显示连接ID。 4. 数据传输: - `dataarrival`事件:在服务器端,此事件处理客户端...

    HTTP连接请求.e.rar

    为了提高效率,HTTP/1.1引入了持久连接(Keep-Alive),允许在同一TCP连接上复用多个HTTP请求和响应,减少了建立新连接的开销。 9. HTTPS:HTTPS是HTTP的安全版本,它通过SSL/TLS协议提供数据加密、服务器身份验证...

    accept-woocommerce-master_javaweb_

    Woocommerce是一款基于WordPress的开源电子商务解决方案,广泛用于建立在线商店。这个插件的目标是使商店能够安全、便捷地接受各种支付方式,可能是信用卡、PayPal、Apple Pay或其他流行的支付网关。 在Java Web...

    Socket客户端连接源码

    TCP Socket遵循三次握手协议来建立连接,确保数据的可靠传输。首先,客户端发起连接请求,服务器响应,然后客户端确认连接,最后服务器再次确认,至此连接建立完成。当通信完成后,双方通过四次挥手释放连接。 ### ...

Global site tag (gtag.js) - Google Analytics