文成小盆友python-num10 socketserver 原理相关。
本节主要内容:
1.IO多路复用
2.多线程多进程
3.小知识点补充(python中作用域相关)
4.socketserver源码分析补充
一。IO多路复用
I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
linux中的io多路复用机制:select,poll,epoll
- select
- select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
- select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。
- select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
- 另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。
- poll
- poll在1986年诞生于System V Release ,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
- poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
- 另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。
- epoll
- 直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
- epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
- epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
- 另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
python中的select模块:
Python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用。
但是再不同的平台,它们所提供的内容支持是不相同的如下:(网络操作,文件操作,终端操作均都属于IO操作,对于windows只支持Socket操作,其他系统支持其他IO操作,但是无法检测 普通文件操作 自动上次读取是否已经变化。)
- Windows Python:
- 提供: select
- Mac Python:
- 提供: select
- Linux Python:
- 提供: select、poll、epoll
select方法如下:
- 句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间)
- 参数: 可接受四个参数(前三个必须)
- 返回值:三个列表
- select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。
- 1、当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中
- 2、当 参数2 序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2 序列中
- 3、当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中
- 4、当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化
- 当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。
简单的io多路复用实例:
- ####简单的io多路复用(伪并发) -- 12345
- import socket
- import select
- sk = socket.socket()
- sk.bind(('127.0.0.1',9999,))
- sk.listen(5)
- inputs = [sk,]
- while True:
- rlist,w,e, = select.select(inputs,[],[],1)
- print(len(inputs),len(rlist))
- print(type(rlist),rlist)
- for r in rlist:
- if r == sk:
- print(r)
- conn,address = r.accept()
- inputs.append(conn)
- conn.sendall(bytes("hello",encoding='utf-8'))
- else:
- r.recv(1024)
简单的IO多路复用,实现读写分离:
- ####简单的io多路复用(实现读写分离) -- 123456
- import socket
- import select
- sk = socket.socket()
- sk.bind(('127.0.0.1',9999,))
- sk.listen(5)
- inputs = [sk,]
- outputs = []
- while True:
- rlist,wlist,e, = select.select(inputs,outputs,[],1)
- print(len(inputs),len(rlist),len(outputs),len(wlist))
- for r in rlist:
- if r == sk:
- print(r)
- conn,address = r.accept()
- inputs.append(conn)
- conn.sendall(bytes("hello",encoding='utf-8'))
- else:
- #开始接收消息了
- print("recv".center(60,"*"))
- try:
- ret = r.recv(1024)
- if not ret:
- raise Exception("断开链接")
- else:
- outputs.append(r)
- print(outputs)
- except Exception as e:
- inputs.remove(r)
- for w in wlist:
- w.sendall(bytes("----response----",encoding="utf-8"))
- outputs.remove(w)
二.多线程多进程
线程:多任务可以由多进程完成,也可以由一个进程内的多线程完成,一个进程内的所有线程,共享同一块内存python中创建线程比较简单,导入threading模块如下所示:
- def f1(i):
- time.sleep(1)
- print(i)
- if __name__ == '__main__':
- for i in range(5):
- t = threading.Thread(target=f1, args=(i,))
- t.start()
- print('start') # 主线程等待子线程完成,子线程并发执行
- >>start
- >>2
- >>1
- >>3
- >>0
- >>4
主线程从上到下执行,创建5个子线程,打印出'start',然后等待子线程执行完结束,如果想让线程要一个个依次执行完,而不是并发操作,那么就要使用join方法:
- import threading
- import time
- def f1(i):
- time.sleep(1)
- print(i)
- if __name__ == '__main__':
- for i in range(5):
- t = threading.Thread(target=f1, args=(i,))
- t.start()
- t.join()
- print('start') # 线程从上到下依次执行,最后打印出start
- >>0
- >>1
- >>2
- >>3
- >>4
- >>start
上面的代码不使用join的结果就是主线程会默认等待子线程结束,才会结束,如果不想让主线程等待子线程的话,可以子线程启动之前设置将其设置为后台线程,如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止,前台线程则相反,若果不加指定的话,默认为前台线程,下面从代码来看一下,如何设置为后台线程。例如下面的例子,主线程直接打印start,执行完后就结束,而不会去等待子线程,子线程中的数据也就不会打印出来
- import threading
- import time
- def f1(i):
- time.sleep(1)
- print(i)
- if __name__ == '__main__':
- for i in range(5):
- t = threading.Thread(target=f1, args=(i,))
- t.setDaemon(True)
- t.start()
- print('start') # 主线程不等待子线程
- >> start
除此之外,自己可以为线程自定义名字,通过 t = threading.Thread(target=f1, args=(i,), name='mythread{}'.format(i)) 中的name参数,除此之外,Thread还有一下一些方法
- t.getName() : 获取线程的名称
- t.setName() : 设置线程的名称
- t.name : 获取或设置线程的名称
- t.is_alive() : 判断线程是否为激活状态
- t.isAlive() :判断线程是否为激活状态
- t.isDaemon() : 判断是否为守护线程
进程:
线程的上一级就是进程,进程可包含很多线程,进程和线程的区别是进程间的数据不共享,多进程也可以用来处理多任务,不过多进程很消耗资源,计算型的任务最好交给多进程来处理,IO密集型最好交给多线程来处理,此外进程的数量应该和cpu的核心说保持一致。
(进程内容下篇会再次补充--)
三。小知识点补充(python中作用域相关)
python 中的作用域相关问题补充:
- python 中没有块级作用域
- python中是以函数为作用域
- python 中 作用域 链 从内往外找。
- python 的作用域在执行之前已经确定。
- #python 中无块级作用域名
- if 1 == 1:
- name = "zhaowencheng"
- print(name)
- ##
- for i in range(10):
- name = i
- print(name)
- ##打印内容如下:
- zhaowencheng
- 9
- Process finished with exit code 0
- def fuck():
- name = "zhaowencheng"
- print(name) #-----报错内容为未定义。
- #显示如下
- NameError: name 'name' is not defined
- Process finished with exit code 1
- name = "zhaowencheng"
- def f1():
- print(name)
- def f2():
- name = "zhao"
- f1()
- f2()
- #显示如下:
- zhaowencheng
- Process finished with exit code 0
- li = [x+100 for x in range(10)]
- print(li)
- li = [lambda :x for x in range(10)]
- r = li[0]()
- print(r)
- ##显示如下:
- [100, 101, 102, 103, 104, 105, 106, 107, 108, 109]
- 9
- Process finished with exit code 0
四。socket server源码分析补充:
再次重申下使用ThreadingTCPServer的要素:
- 创建一个继承自 SocketServer.BaseRequestHandler 的类
- 类中必须定义一个名称为 handle 的方法
- 启动ThreadingTCPServer
ThreadingTCPserver.BaseRequestHandler的源码实现,主要函数关系以及调用顺序:
内部调用流程如下:(主要流程)
- 启动服务端程序
- 执行 TCPServer.__init__ 方法,创建服务端Socket对象并绑定 IP 和 端口
- 执行 BaseServer.__init__ 方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 MyRequestHandle赋值给 self.RequestHandlerClass
- 执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 ...
- 当客户端连接到达服务器
- 执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求
- 执行 ThreadingMixIn.process_request_thread 方法
- 执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass() 即:执行 自定义 MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)
主要源码粘贴如下:
- class BaseServer:
- """Base class for server classes.
- Methods for the caller:
- - __init__(server_address, RequestHandlerClass)
- - serve_forever(poll_interval=0.5)
- - shutdown()
- - handle_request() # if you do not use serve_forever()
- - fileno() -> int # for select()
- Methods that may be overridden:
- - server_bind()
- - server_activate()
- - get_request() -> request, client_address
- - handle_timeout()
- - verify_request(request, client_address)
- - server_close()
- - process_request(request, client_address)
- - shutdown_request(request)
- - close_request(request)
- - handle_error()
- Methods for derived classes:
- - finish_request(request, client_address)
- Class variables that may be overridden by derived classes or
- instances:
- - timeout
- - address_family
- - socket_type
- - allow_reuse_address
- Instance variables:
- - RequestHandlerClass
- - socket
- """
- timeout = None
- def __init__(self, server_address, RequestHandlerClass):
- """Constructor. May be extended, do not override."""
- self.server_address = server_address
- self.RequestHandlerClass = RequestHandlerClass
- self.__is_shut_down = threading.Event()
- self.__shutdown_request = False
- def server_activate(self):
- """Called by constructor to activate the server.
- May be overridden.
- """
- pass
- def serve_forever(self, poll_interval=0.5):
- """Handle one request at a time until shutdown.
- Polls for shutdown every poll_interval seconds. Ignores
- self.timeout. If you need to do periodic tasks, do them in
- another thread.
- """
- self.__is_shut_down.clear()
- try:
- while not self.__shutdown_request:
- # XXX: Consider using another file descriptor or
- # connecting to the socket to wake this up instead of
- # polling. Polling reduces our responsiveness to a
- # shutdown request and wastes cpu at all other times.
- r, w, e = _eintr_retry(select.select, [self], [], [],
- poll_interval)
- if self in r:
- self._handle_request_noblock()
- finally:
- self.__shutdown_request = False
- self.__is_shut_down.set()
- def shutdown(self):
- """Stops the serve_forever loop.
- Blocks until the loop has finished. This must be called while
- serve_forever() is running in another thread, or it will
- deadlock.
- """
- self.__shutdown_request = True
- self.__is_shut_down.wait()
- # The distinction between handling, getting, processing and
- # finishing a request is fairly arbitrary. Remember:
- #
- # - handle_request() is the top-level call. It calls
- # select, get_request(), verify_request() and process_request()
- # - get_request() is different for stream or datagram sockets
- # - process_request() is the place that may fork a new process
- # or create a new thread to finish the request
- # - finish_request() instantiates the request handler class;
- # this constructor will handle the request all by itself
- def handle_request(self):
- """Handle one request, possibly blocking.
- Respects self.timeout.
- """
- # Support people who used socket.settimeout() to escape
- # handle_request before self.timeout was available.
- timeout = self.socket.gettimeout()
- if timeout is None:
- timeout = self.timeout
- elif self.timeout is not None:
- timeout = min(timeout, self.timeout)
- fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
- if not fd_sets[0]:
- self.handle_timeout()
- return
- self._handle_request_noblock()
- def _handle_request_noblock(self):
- """Handle one request, without blocking.
- I assume that select.select has returned that the socket is
- readable before this function was called, so there should be
- no risk of blocking in get_request().
- """
- try:
- request, client_address = self.get_request()
- except socket.error:
- return
- if self.verify_request(request, client_address):
- try:
- self.process_request(request, client_address)
- except:
- self.handle_error(request, client_address)
- self.shutdown_request(request)
- def handle_timeout(self):
- """Called if no new request arrives within self.timeout.
- Overridden by ForkingMixIn.
- """
- pass
- def verify_request(self, request, client_address):
- """Verify the request. May be overridden.
- Return True if we should proceed with this request.
- """
- return True
- def process_request(self, request, client_address):
- """Call finish_request.
- Overridden by ForkingMixIn and ThreadingMixIn.
- """
- self.finish_request(request, client_address)
- self.shutdown_request(request)
- def server_close(self):
- """Called to clean-up the server.
- May be overridden.
- """
- pass
- def finish_request(self, request, client_address):
- """Finish one request by instantiating RequestHandlerClass."""
- self.RequestHandlerClass(request, client_address, self)
- def shutdown_request(self, request):
- """Called to shutdown and close an individual request."""
- self.close_request(request)
- def close_request(self, request):
- """Called to clean up an individual request."""
- pass
- def handle_error(self, request, client_address):
- """Handle an error gracefully. May be overridden.
- The default is to print a traceback and continue.
- """
- print '-'*40
- print 'Exception happened during processing of request from',
- print client_address
- import traceback
- traceback.print_exc() # XXX But this goes to stderr!
- print '-'*40
- BaseServer
BaseServer
- class TCPServer(BaseServer):
- """Base class for various socket-based server classes.
- Defaults to synchronous IP stream (i.e., TCP).
- Methods for the caller:
- - __init__(server_address, RequestHandlerClass, bind_and_activate=True)
- - serve_forever(poll_interval=0.5)
- - shutdown()
- - handle_request() # if you don't use serve_forever()
- - fileno() -> int # for select()
- Methods that may be overridden:
- - server_bind()
- - server_activate()
- - get_request() -> request, client_address
- - handle_timeout()
- - verify_request(request, client_address)
- - process_request(request, client_address)
- - shutdown_request(request)
- - close_request(request)
- - handle_error()
- Methods for derived classes:
- - finish_request(request, client_address)
- Class variables that may be overridden by derived classes or
- instances:
- - timeout
- - address_family
- - socket_type
- - request_queue_size (only for stream sockets)
- - allow_reuse_address
- Instance variables:
- - server_address
- - RequestHandlerClass
- - socket
- """
- address_family = socket.AF_INET
- socket_type = socket.SOCK_STREAM
- request_queue_size = 5
- allow_reuse_address = False
- def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
- """Constructor. May be extended, do not override."""
- BaseServer.__init__(self, server_address, RequestHandlerClass)
- self.socket = socket.socket(self.address_family,
- self.socket_type)
- if bind_and_activate:
- try:
- self.server_bind()
- self.server_activate()
- except:
- self.server_close()
- raise
- def server_bind(self):
- """Called by constructor to bind the socket.
- May be overridden.
- """
- if self.allow_reuse_address:
- self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- self.socket.bind(self.server_address)
- self.server_address = self.socket.getsockname()
- def server_activate(self):
- """Called by constructor to activate the server.
- May be overridden.
- """
- self.socket.listen(self.request_queue_size)
- def server_close(self):
- """Called to clean-up the server.
- May be overridden.
- """
- self.socket.close()
- def fileno(self):
- """Return socket file number.
- Interface required by select().
- """
- return self.socket.fileno()
- def get_request(self):
- """Get the request and client address from the socket.
- May be overridden.
- """
- return self.socket.accept()
- def shutdown_request(self, request):
- """Called to shutdown and close an individual request."""
- try:
- #explicitly shutdown. socket.close() merely releases
- #the socket and waits for GC to perform the actual close.
- request.shutdown(socket.SHUT_WR)
- except socket.error:
- pass #some platforms may raise ENOTCONN here
- self.close_request(request)
- def close_request(self, request):
- """Called to clean up an individual request."""
- request.close()
- TCPServer
TCPServer
- class ThreadingMixIn:
- """Mix-in class to handle each request in a new thread."""
- # Decides how threads will act upon termination of the
- # main process
- daemon_threads = False
- def process_request_thread(self, request, client_address):
- """Same as in BaseServer but as a thread.
- In addition, exception handling is done here.
- """
- try:
- self.finish_request(request, client_address)
- self.shutdown_request(request)
- except:
- self.handle_error(request, client_address)
- self.shutdown_request(request)
- def process_request(self, request, client_address):
- """Start a new thread to process the request."""
- t = threading.Thread(target = self.process_request_thread,
- args = (request, client_address))
- t.daemon = self.daemon_threads
- t.start()
- ThreadingMixIn
ThreadingMixIn
- class BaseRequestHandler:
- """Base class for request handler classes.
- This class is instantiated for each request to be handled. The
- constructor sets the instance variables request, client_address
- and server, and then calls the handle() method. To implement a
- specific service, all you need to do is to derive a class which
- defines a handle() method.
- The handle() method can find the request as self.request, the
- client address as self.client_address, and the server (in case it
- needs access to per-server information) as self.server. Since a
- separate instance is created for each request, the handle() method
- can define arbitrary other instance variariables.
- """
- def __init__(self, request, client_address, server):
- self.request = request
- self.client_address = client_address
- self.server = server
- self.setup()
- try:
- self.handle()
- finally:
- self.finish()
- def setup(self):
- pass
- def handle(self):
- pass
- def finish(self):
- pass
- SocketServer.BaseRequestHandler
BaseRequestHandler
++++++++++++
文成小盆友python-num10 socketserver 原理相关。的更多相关文章
- 文成小盆友python-num7 -常用模块补充 ,python 牛逼的面相对象
本篇内容: 常用模块的补充 python面相对象 一.常用模块补充 1.configparser模块 configparser 用于处理特定格式的文件,起内部是调用open()来实现的,他的使用场景是 ...
- 文成小盆友python-num11-(2) python操作Memcache Redis
本部分主要内容: python操作memcache python操作redis 一.python 操作 memcache memcache是一套分布式的高速缓存系统,由LiveJournal的Brad ...
- 文成小盆友python-num12 Redis发布与订阅补充,python操作rabbitMQ
本篇主要内容: redis发布与订阅补充 python操作rabbitMQ 一,redis 发布与订阅补充 如下一个简单的监控模型,通过这个模式所有的收听者都能收听到一份数据. 用代码来实现一个red ...
- 文成小盆友python-num9 socket编程
socket编程 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. Socket的英文原义是“孔”或“插座”.作为BSD UNIX的进程通信机制,取后一种意思 ...
- 文成小盆友python-num5 -装饰器回顾,模块,字符串格式化
一.装饰器回顾与补充 单层装饰器: 如上篇文章所讲单层装饰器指一个函数用一个装饰器来装饰,即在函数执行前或者执行后用于添加相应的操作(如判断某个条件是否满足). 具体请见如下: 单层装饰器 双层装饰器 ...
- 文成小盆友python-num4 装饰器,内置函数
一 .python 内置函数补充 chr() -- 返回所给参数对应的 ASCII 对应的字符,与ord()相反 # -*- coding:utf-8 -*- # Author:wencheng.z ...
- 文成小盆友python-num3 集合,函数,-- 部分内置函数
本接主要内容: set -- 集合数据类型 函数 自定义函数 部分内置函数 一.set 集合数据类型 set集合,是一个无序且不重复的元素集合 集合基本特性 无序 不重复 创建集合 #!/bin/en ...
- 文成小盆友python-num2 数据类型、列表、字典
一.先聊下python的运行过程 计算机是不能够识别高级语言的,所以当我们运行一个高级语言程序的时候,就需要一个“翻译机”来从事把高级语言转变成计算机能读懂的机器语言的过程.这个过程分成两类,第一种是 ...
- 文成小盆友python-num15 - JavaScript基础
一.JavaScript简介 JavaScript一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型.它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的 ...
随机推荐
- 转:对于linux下system()函数的深度理解(整理)
这几天调程序(嵌入式linux),发现程序有时就莫名其妙的死掉,每次都定位在程序中不同的system()函数,直接在shell下输入system()函数中调用的命令也都一切正常.就没理这个bug,以为 ...
- ural 1353. Milliard Vasya's Function
http://acm.timus.ru/problem.aspx?space=1&num=1353 #include <cstdio> #include <cstring&g ...
- reg51.h 详解
/* BYTE Register */ sfr P0 = 0x80; //P0口 sfr P1 = 0x90; //P1口 sfr P2 = 0xA0; //P2口 sfr P3 = 0xB0; // ...
- C# 在腾讯的发展(作者是微软连续10年的MVP)
本文首发我的微信公众号"dotnet跨平台", 内容得到大家热烈的欢迎,全文重新发布在博客,欢迎转载,请注明出处. .NET 主要的开发语言是 C# , .NET 平台泛指遵循EC ...
- MyBatis insert后返回自增字段的值
如下情况适用支持自增的DB,如MySQL.其他情况参见:MyBatis魔法堂:Insert操作详解(返回主键.批量插入) 1.model public class UserInfo { private ...
- delphi7编写客户端调用java服务器端webservice示例
1. 首先取得java-webservice服务器端地址.我的是:http://localhost:8080/mywebservice/services/mywebservice?wsdl 2. 然后 ...
- NCPC 2015 - Problem A - Adjoin the Networks
题目链接 : http://codeforces.com/gym/100781/attachments 题意 : 有n个编号为0-n-1的点, 给出目前已经有的边(最多n-1条), 问如何添加最少的边 ...
- 寻访上海西服定制店_Enjoy·雅趣频道_财新网
寻访上海西服定制店_Enjoy·雅趣频道_财新网 寻访上海西服定制店
- checkbox 与JS的应用
JS是一种基于(面向)对象的语言.所有的东西都基本上是对象. 基于对象和面向对象概念上基本上没有什么区别. js没有类,它把类功能称为原型对象.是同一个概念.主要是因为js没有class关键字.类== ...
- 深入解读ESB与SOA的关系
时至今日,SOA的概念渐渐清晰了. 有关ESB的概念,已经吵了好多年了,还是没有定论. 我个人认为,ESB本来就是抽象的概念,而且内涵丰富,在不同的场合含义不同.因此应该从不同的角度来认识. ...