客户端socket请求链接Serversocket的请求链接,按照请求顺序进入客户端链接请求队列(队列的容量是由操做系统完成的),ServerSocket的构造函数中的backlog就是用来指定请求队列的长度。 这个值会失效的三种状况:大于操做系统默认值|小于等于0|没有设置。 (见下面)html
serversocket中的accept方法,会从客户端链接请求队列(先进先出)中取出一个请求链接的请求,生成一个用于通讯的socket。只有当serversocket的accept方法成功返回时,才代表客户端与服务端创建了链接。java
socket链接是Java中进行通讯的基本方式,也是效率最高的方式,虽然他有http等让是进行http请求,可是若是是进行tcp、下载等通讯,仍是使用socket更好。Java中封装了很是完美的socket机制,使用也很是简单。主要包括socket和serversocket。算法
socket的使用很是简单,主要包括的构造方法有:socket(),socket(string host,string port),socket(Inetaddress address,int port)等,很是明白了,经过传入host和port进行socket的请求,当在建立相应的套接字实例的时候,会自动去对相应的ip和port进行链接,只有当链接成功,才表示相应的套接字创建成功,才能够进行相应的I/O操做。经过getOutputStream和getInputStream获取相应的输入输出流,进行相应的I/O操做,可是有一个是比较特别的,getChannel用来获取SocketChannel,他之因此特别是由于他属于java.nio.channels下面的类,其继承于java.nio.channels.SelectableChannel,就是说在进行nio非阻塞式的请求链接时,他很是有用,具体参见http://www.cnblogs.com/likwo/archive/2010/06/29/1767814.html。可能有些人会问,对于能够经过设置服务器链接的timeout来防止过多的阻塞,可是若是对于超过timeout,socket通常是抛出超时异常,这样就算对异常进行了处理,也将会重新创建socket链接,浪费消费 重建的资源。例如QQ聊天,当你打开一个聊天面板,好久不说话的时候,并不会自动为你断开socket链接,而是一直处于阻塞状态,直到你发送了新的信息,再进行处理,所以nio的阻塞方式会更好些。shell
对于serversocket也比较简单,经常使用的只有四个构造函数:数据库
l ServerSocket()throws IOException
l ServerSocket(int port) throws IOException
l ServerSocket(int port, int backlog) throws IOException
l ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException 编程
分别对这几个构造进行简单的解释:windows
第一个无参构造,只是建立一个serversocket实例,可是不进行任何端口的监听,你还必须经过bind方法进行端口的绑定,好处就是在绑定以前能够进行相应属性的设置,例如so_reuseaddress等;设计模式
第二个构造函数须要一个端口(1024-65535),通常不使用0-1023之间的,这个属于系统占用的预留端口,可是若是你传入的端口号为0,则会默认使用匿名端口,就是系统随机分配一个端口,进行暂时通讯,这个匿名方式,通常状况下不使用;浏览器
第三个构造函数须要一个端口,和一个backlog(监听对列大小),serversocket进行某端口的监听,当有多个链接请求是,每一个请求默认都会放入一个请求队列里,若是你没有设置这个值,则默认为操做系统的值,根据不一样的系统有所不一样,例如40等,有几种状况,这个值将会失效:大于操做系统默认值|小于等于0|没有设置。若是设置了,而没有及时对队列进行处理,则会报ConnectException异常;缓存
第四个构造函数除了具备端口、队列大小外,还具备一个参数是ip地址,就是进行相应ip地址的绑定。固然,这个进行的是服务器ip地址的绑定,不会限制客户端的ip访问。当一台服务器存在多个网卡的时候,就须要经过这个参数来设置客户端访问的ip。
服务器socket的关闭,经过使用close进行关闭,使用isclosed进行判断,还能够进行端口的绑定判断。
对于服务器close方法,须要有一点进行说明:调用close方法以后,操做系统并不会当即进行端口的释放,依旧会对旧端口占用一段时间,以防止客户端发送的数据有延迟现象。所以有时候,就算你进行了close方法的调用,进行了端口的释放,可是若是你当即进行同一个端口的链接时,依旧会包端口占用异常,这个是能够理解的。
serversocket经过使用accept方法进行客户端请求的处理,每当请求队列里有客户端请求时,serversocket就会从队列顶端取一个socket请求进行处理,生成一个socket来负责与客户端通讯。若是一个时间只能处理一个socket,当有多个客户端请求时,则必需要排队处理,等待全部前面的socket处理完,这是个极其痛苦,而且不合理的过程。所以,引入了线程的概念。
为了实现客户端请求的快速相应和快速处理,据是高并发,则必须使用多线程机制。主题思想是:serversocket经过accept创建一个socket,而后起一个线程,把这个socket扔给新建的线程进行处理,而serversocket所在的主线程,则继续去监听端口,以此实现多线程通讯。通常有三种方式:
上面的方式很是简单,可以处理基本的多线程问题,当数据量不大时,应该没有什么问题,可是若是数据量过大时,就会出现严重的性能,甚至是宕机问题。其缺点主要有以下几个:
a:每一个socket请求,创建一个链接,当每一个都是进行简短的通讯时,则异常的耗费系统创建、销毁线程资源。
b:若是创建线程太多,每一个线程都会占用必定的系统内存,这样将致使内存溢出。
c:频繁地对线程进行创建 销毁,会致使操做系统进行频繁的cpu切换线程切换,这样也会很是耗费系统资源。
本身写线程池,可以对线程池的工做原理以及工做状况,更加的了解和控制,可是因为线程池必然涉及到多线程问题,所以为了防止出现死锁、线程泄漏、并发错误、任务过载等问题,须要性能很是好的机制,通常不推荐我的现实。若是非要实现,能够经过使用linkedlist<runnable>的数据结构来实现一个多线程队列。下面,仍是主要推荐jdk已经帮你实现的线程池。
这个jar包都是一些并发编程会常用到的工具类,主要有阻塞队列,原子操做的map以及线程池等。其基本包括Executor、ExecutorService接口和Executors类,两个接口定义了执行线程的方法,而Executors则定义了管理线程池的方法,主要能够建立的经常使用线程池有:
newSingleThreadScheduledExecutor()
建立一个能够延迟执行和定时执行的单线程线程池
newSingleThreadExecutor()
建立一个运行单线程的线程池 new LinkedBlockingQueue<Runnable>()) --任务队列
newScheduledThreadPool(int corePoolSize)
建立一个能够延迟执行和定时执行的线程池,设定线程数,经常使用来代替Timer(定时器)
newFixedThreadPool(int nThreads)
建立一个固定线程数的线程池 new LinkedBlockingQueue<Runnable>()) --任务队列
newCachedThreadPool()
建立一个带有缓冲区的线程池 new SynchronousQueue<Runnable>()
而后经过生成的线程池的execute方法进行线程的执行,具体能够百度下哈哈
使用线程池有如下几点风险:
一、死锁。任何多线程都不可避免的问题。可是对于线程池可能会存在另外一种死锁:就是线程池中的线程都在等待一个资源,而这个资源须要执行A后获得,而因为线程池没有可用的线程,致使A没法执行,故而也会发生死锁。
二、系统资源不足。多线程一定须要大量的内存资源,可能出现内存泄漏问题。
三、并发错误。
四、线程泄漏。就是全部的线程池中的线程都在等待输入资源,或者都抛出了异常而没有捕获,则会致使线程池中全部的线程假死。
五、线程过载。运行线程过多,致使过载,这个能够经过设置线程池的大小来进行必定成功的避免。
至于如何避免,主要是要在使用多线程时要当心,同时不要使用destroy despause 等操做,尽可能使用sleep notify wait等操做,在这里不详细说明了。
在多线程大师Doug Lea的贡献下,在JDK1.5中加入了许多对并发特性的支持,例如:线程池。这里介绍的就是1.5种的线程池的简单使用方法。
一个任务经过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。
当一个任务经过execute(Runnable)方法欲添加到线程池时:
也就是:处理任务的优先级为:
核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,若是三者都满了,使用handler处理被拒绝的任务。
当线程池中的线程数量大于 corePoolSize时,若是某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池能够动态的调整池中的线程数。
unit可选的参数为java.util.concurrent.TimeUnit中的几个静态属性:
NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。
workQueue我经常使用的是:java.util.concurrent.ArrayBlockingQueue
handler有四个选择:
对这两段程序的说明:
Socket是应用层与TCP/IP协议族通讯的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来讲,一组简单的接口就是所有,让Socket去组织数据,以符合指定的协议。
Socket 通讯示例
主机 A 的应用程序要能和主机 B 的应用程序通讯,必须经过 Socket 创建链接,而创建 Socket 链接必须须要底层 TCP/IP 协议来创建 TCP 链接。创建 TCP 链接须要底层 IP 协议来寻址网络中的主机。咱们知道网络层使用的 IP 协议能够帮助咱们根据 IP 地址来找到目标主机,可是一台主机上可能运行着多个应用程序,如何才能与指定的应用程序通讯就要经过 TCP 或 UPD 的地址也就是端口号来指定。这样就能够经过一个 Socket 实例惟一表明一个主机上的一个应用程序的通讯链路了。
首先看两个概念: 短链接--长链接
send()函数返回了实际发送的长度,在网络不断的状况下,它毫不会返回(发送失败的)错误,最多就是返回0。对于TCP你能够字节写一个循环发送。当send函数返回SOCKET_ERROR时,才标志着有错误。但对于UDP,你不要写循环发送,不然将给你的接收带来极大的麻烦。因此UDP须要用SetSockOpt来改变Socket内部Buffer的大小,以能容纳你的发包。明确一点,TCP做为流,发包是不会整包到达的,而是源源不断的到,那接收方就必须组包。而UDP做为消息或数据报,它必定是整包到达接收方。
二、关于接收,通常的发包都有包边界,首要的就是你这个包的长度要让接收方知道,因而就有个包头信息,对于TCP,接收方先收这个包头信息,而后再收包数据。一次收齐整个包也能够,可要对结果是否收齐进行验证。这也就完成了组包过程。UDP,那你只能整包接收了。要是你提供的接收Buffer太小,TCP将返回实际接收的长度,余下的还能够收,而UDP不一样的是,余下的数据被丢弃并返回WSAEMSGSIZE错误。注意TCP,要是你提供的Buffer佷大,那么可能收到的就是多个发包,你必须分离它们,还有就是当Buffer过小,而一次收不完Socket内部的数据,那么Socket接收事件(OnReceive),可能不会再触发,使用事件方式进行接收时,密切注意这点。这些特性就是体现了流和数据包的区别。参考:http://www.cnblogs.com/canghaitianyuan/archive/2012/11/16/2772987.html
http://my.oschina.net/ksfzhaohui/blog/95451
《 java并发编程实践》
《java网络编程精解》