如果没有关闭accept对象对象!明星会说想成家吗

要写网络程序就必须用Socket这是程序员都知道的。而且面试的时候,我们也会问对方会不会Socket编程一般来说,很多人都会说Socket编程基本就是listen,accept以及sendwrite等几个基本的操作。昰的就跟常见的文件操作一样,只要写过就一定知道

对于网络编程,我们也言必称TCP/IP似乎其它网络协议已经不存在了。对于TCP/IP我们还知道TCP和UDP,前者可以保证数据的正确和可靠性后者则允许数据丢失。最后我们还知道,在建立连接前必须知道对方的IP地址和端口号。除此普通的程序员就不会知道太多了,很多时候这些知识已经够用了最多,写服务程序的时候会使用多线程来处理并发访问。

我们還知道如下几个事实:


1一个指定的端口号不能被多个程序共用。比如如果IIS占用了80端口,那么Apache就不能也用80端口了
2。很多防火墙只允许特定目标端口的数据包通过
3。服务程序在listen某个端口并accept某个连接请求后会生成一个新的socket来对该请求进行处理。

于是一个困惑了我很久嘚问题就产生了。如果一个socket创建后并与80端口绑定后是否就意味着该socket占用了80端口呢?如果是这样的那么当其accept一个请求后,生成的新的socket到底使用的是什么端口呢(我一直以为系统会默认给其分配一个空闲的端口号)如果是一个空闲的端口,那一定不是80端口了于是以后的TCP數据包的目标端口就不是80了--防火墙一定会组织其通过的!实际上,我们可以看到防火墙并没有阻止这样的连接,而且这是最常见的连接請求和处理方式我的不解就是,为什么防火墙没有阻止这样的连接它是如何判定那条连接是因为connet80端口而生成的?是不是TCP数据包里有什麼特别的标志或者防火墙记住了什么东西?

后来我又仔细研读了TCP/IP的协议栈的原理,对很多概念有了更深刻的认识比如,在TCP和UDP同属于傳输层共同架设在IP层(网络层)之上。而IP层主要负责的是在节点之间(End to End)的数据包传送这里的节点是一台网络设备,比如计算机因為IP层只负责把数据送到节点,而不能区分上面的不同应用所以TCP和UDP协议在其基础上加入了端口的信息,端口于是标识的是一个节点上的一個应用除了增加端口信息,UPD协议基本就没有对IP层的数据进行任何的处理了而TCP协议还加入了更加复杂的传输控制,比如滑动的数据发送窗口(Slice Window)以及接收确认和重发机制,以达到数据的可靠传送不管应用层看到的是怎样一个稳定的TCP数据流,下面传送的都是一个个的IP数據包需要由TCP协议来进行数据重组。

所以我有理由怀疑,防火墙并没有足够的信息判断TCP数据包的更多信息除了IP地址和端口号。而且峩们也看到,所谓的端口是为了区分不同的应用的,以在不同的IP包来到的时候能够正确转发

TCP/IP只是一个协议栈,就像操作系统的运行机淛一样必须要具体实现,同时还要提供对外的操作接口就像操作系统会提供标准的编程接口,比如Win32编程接口一样TCP/IP也必须对外提供编程接口,这就是Socket编程接口--原来是这么回事啊!

在Socket编程接口里设计者提出了一个很重要的概念,那就是socket这个socket跟文件句柄很相似,实际上茬BSD系统里就是跟文件句柄一样存放在一样的进程句柄表里这个socket其实是一个序号,表示其在句柄表中的位置这一点,我们已经见过很多叻比如文件句柄,窗口句柄等等这些句柄,其实是代表了系统中的某些特定的对象用于在各种函数中作为参数传入,以对特定的对潒进行操作--这其实是C语言的问题在C++语言里,这个句柄其实就是this指针实际就是对象指针啦。

现在我们知道socket跟TCP/IP并没有必然的联系。Socket编程接口在设计的时候就希望也能适应其他的网络协议。所以socket的出现只是可以更方便的使用TCP/IP协议栈而已,其对TCP/IP进行了抽象形成了几个最基本的函数接口。比如createlisten,acceptconnect,read和write等等

现在我们明白,如果一个程序创建了一个socket并让其监听80端口,其实是向TCP/IP协议栈声明了其对80端口的占有以后,所有目标是80端口的TCP数据包都会转发给该程序(这里的程序因为使用的是Socket编程接口,所以首先由Socket层来处理)所谓accept函数,其實抽象的是TCP的连接建立过程accept函数返回的新socket其实指代的是本次创建的连接,而一个连接是包括两部分信息的一个是源IP和源端口,另一个昰宿IP和宿端口所以,accept可以产生多个不同的socket而这些socket里包含的宿IP和宿端口是不变的,变化的只是源IP和源端口这样的话,这些socket宿端口就可鉯都是80而Socket层还是能根据源/宿对来准确地分辨出IP包和socket的归属关系,从而完成对TCP/IP协议的操作封装!而同时放火墙的对IP包的处理规则也是清晰明了,不存在前面设想的种种复杂的情形

明白socket只是对TCP/IP协议栈操作的抽象,而不是简单的映射关系这很重要!


手机能够使用联网功能昰因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在“无差别”的网络之上

建立起一个TCP连接需要经过“三次握手”:

第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态等待服务器确认;

第二佽握手:服务器收到syn包,必须确认客户的SYN(ack=j+1)同时自己也发送一个SYN包(syn=k),即SYN+ACK包此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1)此包发送完毕,客户端和服务器进入ESTABLISHED状态完成三次握手。

握手过程中传送的包里不包含数据三次握掱完毕后,客户端与服务器才正式开始传送数据理想状态下,TCP连接一旦建立在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被┅直保持下去断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写了就是服務器和客户端交互,最终确定断开)

HTTP协议即超文本传送协议(Hypertext Transfer Protocol )是Web联网的基础,也是手机联网常用的协议之一HTTP协议是建立在TCP协议之上的一種应用。

HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应在请求结束后,会主动释放连接从建立连接到关闭连接的過程称为“一次连接”。

1)在HTTP 1.0中客户端的每次请求都要求建立一次单独的连接,在处理完本次请求后就自动释放连接。


2)在HTTP 1.1中则可以茬一次连接中处理多个请求并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求
由于HTTP在每次请求结束后都会主動释放连接,因此HTTP连接是一种“短连接”要保持客户端程序的在线状态,需要不断地向服务器发起连接请求通常的做法是即时不需要獲得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求服务器在收到该请求后对客户端进行回复,表奣知道客户端“在线”若服务器长时间无法收到客户端的请求,则认为客户端“下线”若客户端长时间无法收到服务器的回复,则认為网络已经断开
套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元它是网络通信过程中端点的抽象表示,包含进行网絡通信必须的五种信息:连接使用的协议本地主机的IP地址,本地进程的协议端口远地主机的IP地址,远地进程的协议端口

应用层通过傳输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端ロ传输数据。为了区别不同的应用程序进程和连接许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输層通过Socket接口区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务

Redis是一个开源的使用ANSI 编写、支持网絡、可基于内存亦可持久化的日志型、Key-Value并提供多种语言的API。从2010年3月15日起Redis的开发工作由VMware主持。从2013年5月开始Redis的开发由Pivotal赞助。

--有序集合)和hash(哈希类型)这些都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的在此基础上,redis支持各种不同方式的排序与memcached一样,为了保证效率数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件并苴在此基础上实现了master-slave(主从)同步。

Redis是c语言开发的

第一步:redis的源码包上传到linux系统。

第二步:解压缩redis

第三步:编译。进入redis源码目录make

PREFIX参数指萣redis的安装目录。一般软件安装到/usr目录下

-h:连接的服务器的地址

Redis中所有的数据都是字符串命令不区分大小写,key是区分大小写的Redis是单线程嘚。Redis中不适合保存内容大的数据

incr:加一(生成id)

使用hash对key进行归类。

List:有顺序可重复

Set:元素无顺序不能重复

还有集合运算命令,自学

設置key的过期时间。

Redis的所有数据都是保存到内存中的

  • Rdb:快照形式,定期把内存中当前时刻的数据保存到磁盘Redis默认支持的持久化方案。
  • aof形式:append only file把所有对redis数据库操作的命令,增删改操作的命令保存到文件中。数据库恢复时把所有的命令执行一遍即可

两种持久化方案同时開启使用aof文件来恢复数据库。

(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

(2)节点的fail是通过集群中超过半数的节点检测夨效时才生效.

(3)客户端与redis节点直连,不需要中间proxy.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可

key 使用 crc16 算法算出一个结果嘫后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽redis 会根据节点数量大致均等的将哈希槽映射到不同的节点

Redis集群中至少应該有三个节点。要保证集群的高可用需要每个节点有一个备份机。

Redis集群至少需要6台服务器

搭建伪分布式。可以使用一台虚拟机运行6个redis實例需要修改redis的端口号

4.2.1集群搭建环境(Redis5.0及以上版本不需要搭建)

5.0版本搭建可以参考:

1、使用ruby脚本搭建集群。需要ruby的运行环境

2、安装ruby脚本运荇使用的包。

需要6台redis服务器搭建伪分布式。

第一步:创建6个redis实例每个实例运行在不同的端口。需要修改redis.conf配置文件配置文件中还需要紦cluster-enabled yes前的注释去掉。

第二步:启动每个redis实例

第三步:使用ruby脚本搭建集群。

-c:代表连接的是redis集群

 // 第一步:创建一个Jedis对象需要指定服务端的ip忣端口。
 // 第二步:使用Jedis对象操作数据库每个redis命令对应一个方法。
 // 第三步:打印结果

5.2连接单机版使用连接池(推荐)

 // 第一步:创建一个JedisPool对象。需要指定服务端的ip及端口
 // 第四步:操作完毕后关闭jedis对象,连接池回收资源
 // 第二步:直接使用JedisCluster对象操作redis。在系统中单例存在
 // 第三步:打印结果

版权声明:本文为博主原创文章未经博主允许不得转载。 /qq_/article/details/

下方是Eureka Client从Eureka Server获取实例信息的总体流程图后面会详细介绍每个步骤。

 //略去部分无用代码.....
 //此时正在创建Client实例对象吔就是说Client刚启动的时候,全量获取一次实例信息保存到本地
 //根据所有实例的信息生成一个HashCode
 
//负责读动作的http请求,全量获取以及增量获取
 



注意下面getApplicationsInternal方法里面的try代码块,并没有catch(并不是我给删了)甚至连log都不打。也就是说在获取实例信息的时候,即便发生异常也不管如果发苼异常就返回null的Applications。


 



 //省略部分代码...
 



 //如果没读到那么从一级缓存中读,然后再保存到二级缓存
 
默认先从二级缓存中读取如果二级缓存没有命中,那么接着从再去一级缓存中读取那么如果一级缓存也没有命中呢,我们再来看看这个readWriteCacheMap这个采用了Google Guava Cache,在remove的时候可以自定义回调茬get方法没有返回值的时候,去调用load(Key key)方法将返回值返回并保存


//谷歌Guava缓存,有自动过期功能这里是默认3分钟
 



 



 //从最近改变的队列里面获取最菦改变的实例信息
 //获取注册到其他Region的实例信息
 
那么这个recentlyChangedQueue里面都放着什么样的数据呢,或者说什么时候会向这个队列里面添加数据呢


 //省略蔀分代码...
 //省略部分代码...
 //发生了新的注册,失效readWriteCacheMap部分缓存且不是清空全部缓存
 
这里只列出了注册方法,调用internalCancel、statusUpdate、deleteStatusOverride方法也会向这个队列里面添加数据当Client发起增量获取请求的时候,就会从这个队列里面消费


细心考虑的话,会发现register、internalCancel、statusUpdate、deleteStatusOverride这几个方法在一个平稳运行的生产环境,这几个方法都不会经常被调用(相信生产环境不会经常有实例发生状态改变也不会经常有实例上下线),换句话说这个队列里面基本仩会一直保持空的状态。


回到最初的问题为什么Eureka Client默认设置的是开启增量获取。


正是因为正常运行的环境,各个实例的状态不会经常发苼改变Client不需要经常去做遍历覆盖等操作,client端保存的实例信息就是最新的也是没有改变过的。client默认每30s向Eureka Server 发送一次获取实例信息请求这30s內发生实例状态改变的概率还是非常小的。即便服务实例的数量很大那么也不会每30s就会发生一次实例状态改变。


当然如果服务实例的數量很大(1000个实例),如果关闭了增量获取那么Client每30秒就要从Server端获取包含这1000个实例的response,也是一件很消耗流量的事情


另外,这个recentlyChangedQueue队列会定时更噺有个定时任务会默认每30s执行一次清除操作,会清除30s(默认)前加进来的实例信息也就是说Client从这个队列里面拿到的都是最多30s*2内发生改变的實例信息。


 //默认只保留30s内加入到这个队列面的item超过30秒的,全部移除
 
这里只讲了一个大概的流程有些地方没有很详细的讲,有兴趣的同學可以对照最上面的流程图去看源码也可以对照着去debug。如果有任何疑问或者讲的不够准确的地方感谢提出!

我要回帖

更多关于 如果没有关闭accept对象 的文章

 

随机推荐