`

如果你用单线程写Socket,为什么要折腾?--单线程、多线程、线程池

阅读更多
在开发Socket项目的时候,如果是开发一个自己玩玩,当然不用考虑效率、安全性等问题,可是如果是一个企业级的,你就不得不关注这几点。本系列文章将我们实验室里的Socket程序变成企业级的应用。
NIO编程肯定是一个很好的解决方案,不过这部分留在以后讨论。今天我想说说如何让你的阻塞的Scoket程序高效、安全的跑起来。
一开始,大家会编出一个单线程的Scoket程序,然后我们发现这个程序根本不能够连接多个客户端,于是我们引入“多线程”,使我们的程序能够同时处理多个客户端。
我相信,到现在为止如果没有深入研究过Socket编程,大家一般还是停留在“一客户一线程”的初级模式。如果是个位数的客户,当然你不会发现什么明显的性能问题。但是如果你的客户连接数量达到百位级,我靠,你的CPU就关顾着在各个线程间切换,你的内存似乎也有些吃不消了(每个线程都有自己独立的内存),更多的系统资源的消耗,更多的线程上下文转换,更复杂的线程管理(OS有一套自己的机制),将拖垮你的application。再加上多客户端尝试并发连接,及时响应客户端的连接将变得像癞蛤蟆追求天鹅一样不给力,因为线程的创建将占用服务器大量的CPU周期。
单线程多线程我们没法解决的问题,必然有新的英雄站出来解决,他就是“线程池”。
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * ThreadPool演示
 * @project : socket
 * @author  贾懂凯 @ netjava 
 * @date  2010-12-14 下午12:24:40
 * @since jdk1.6.0_10
 */
public class TCPServerPool {
	
	public static void main(String args[]) throws IOException{
		//ensure the parameter is right!
		if(args.length!=2){
			throw new IllegalArgumentException("Parameter(s):<port> <ThreadSize>");
		}
		int server_port=Integer.parseInt(args[0]);
		int threadpool_size=Integer.parseInt(args[1]);
		
		//create a server socket to accept client connection requests
		final ServerSocket serSock=new ServerSocket(server_port);
		final Logger logger=Logger.getLogger("thredPoolLog");
		
		//spawn a fixed number of threds to service clients
		for(int i=0;i<threadpool_size;i++){
			Runnable run=new Runnable(){

				public void run() {
					while(true){
						try {
							Socket clientSock=serSock.accept();
							/**
							 * 把它交给一个独立的handler处理
							 * 你可以将handler定义为一个独立的线程(注意性能)
							 * 或者定义为一个静态方法(注意并发的同步问题)。
							 */
						} catch (IOException e) {
							logger.log(Level.WARNING,"Client accept failed",e);
						}
					}
				}
			};
			Thread t=new Thread(run,"Thread-"+i);
			t.start();
			logger.info("create and start a thread named "+t.getName());
		}
	}
	
}

     这里我为accept()方法加上了线程池,我们发现,我们不必担心少量的多个客户并发连接的问题了,因为有多个线程对应的accept()在等待客户端连接进来。一旦客户成功连接进来,该线程重新返回线程池。如果并发访问的客户端超过线程池的size,那么连接请求将在网络中形成一个队列等待,这明显是不利于维护的,并且线程的大小没有适应性,因为它总是一成不变的。
    并且连接进来的客户端显然不能交给单独的线程来处理,否则我们控制线程数量过多造成的性能瓶颈的初衷将无疾而终。不过,如果给连接进来的客户端创建一个线程池,就要考虑到维护问题,我们创建一个等待队列,来维护那些超过客户端线程池size的线程。这样将引起一致命的问题,如果其中有几个正在接受服务的线程阻塞等待或者由于未捕获异常死亡,在等待队列中的线程将因迟迟得不到资源而被饿死。
     线程池的初步使用出现了这么多问题,问题总是伴随着被解决的可能性诞生的,就像出现了怪兽总会出现奥特曼一样,于是我们的英雄又出现了-“Executor”,它是系统提供的,它可以帮助我们来管理线程池。
如何管理,待我吃完饭回来继续……(见下一篇)
分享到:
评论
28 楼 killeraction 2011-05-11  
快去学习用netty或者是mima把? 楼主你Out了..
27 楼 贾懂凯 2010-12-18  
pengzhoushuo 写道
LZ,你有没有考虑到一个问题呢,比如说游戏服务器或者IM服务器,要保持长连接吧,你想多个线程来处理很多的Socket,而每个Socket里的都有wile(true)呢。

阻塞编程中,如果是大量常量的话用线程池显然不是一个号的选择。线程池主要是服务于大量的短连接的系统,可以节约创建和销毁线程的系统消耗。
如果是有大量长连接,用NIO的selector监听会比较好。我也没做过游戏服务器,我暂时是这么考虑的。
还是throw new MyExcpetion(this question),求大家解答吧~~
26 楼 pengzhoushuo 2010-12-18  
LZ,你有没有考虑到一个问题呢,比如说游戏服务器或者IM服务器,要保持长连接吧,你想多个线程来处理很多的Socket,而每个Socket里的都有wile(true)呢。
25 楼 skzr.org 2010-12-18  
mercyblitz 写道
贾懂凯 写道
skzr.org 写道
真的是折腾,直接使用NIO.2
呵呵,很简单就完成了

就是要折腾嘛!呵呵~
好久没在JE上看见你了,最近挺忙的?



他看Oracle Java大会去了,:-)!

最近半年忙着做工程,没怎么开发,郁闷中。。。

javaone确实不需此行,比csdn的好多了

折腾好啊,可以更加的深入理解
折腾中会引入更多的问题和要解决的东西,就像

C_J 写道
其实你还有更多的问题要考虑:
1,池中没有可用线程,怎么办?
2,任务队列满了,怎么办?
3,超时机制。


期待凯哥此文的综合帖
持续关注 中。。。
^ ^
24 楼 C_J 2010-12-18  
其实你还有更多的问题要考虑:
1,池中没有可用线程,怎么办?
2,任务队列满了,怎么办?
3,超时机制。
23 楼 mercyblitz 2010-12-17  
yeshucheng 写道
借这个帖子,很想了解下这里有朋友去看activeMQ的源代码吗,呵呵。很多帖子都是写了一些如何运用activeMQ,并没有深入了解它的原理



先了解JMS就淡定了,然后才是传输协议的细节。
22 楼 yeshucheng 2010-12-17  
借这个帖子,很想了解下这里有朋友去看activeMQ的源代码吗,呵呵。很多帖子都是写了一些如何运用activeMQ,并没有深入了解它的原理
21 楼 mercyblitz 2010-12-17  
贾懂凯 写道
skzr.org 写道
真的是折腾,直接使用NIO.2
呵呵,很简单就完成了

就是要折腾嘛!呵呵~
好久没在JE上看见你了,最近挺忙的?



他看Oracle Java大会去了,:-)!
20 楼 贾懂凯 2010-12-17  
skzr.org 写道
真的是折腾,直接使用NIO.2
呵呵,很简单就完成了

就是要折腾嘛!呵呵~
好久没在JE上看见你了,最近挺忙的?
19 楼 skzr.org 2010-12-17  
真的是折腾,直接使用NIO.2
呵呵,很简单就完成了
18 楼 mercyblitz 2010-12-16  
贾懂凯 写道
mercyblitz 写道
贾懂凯 写道
taolei0628 写道
用Executor不见得就比你现在的方法好。
等你贴出线程池的用法后再看看有没有我预计到的问题出现吧。
给你个提示,网络连接是个比线程更重量级的资源。

确实如此,三次握手会占比较长时间,只有到第三次握手完成才会返回socket实例。我觉得如果并发访问要求比较高的话,用我上面的代码中的线程池方法应该能增加效率。不过,如果在accept方法上加一个超时机制效果会更好。
求taolei0628指点~~



TCP/IP上面还有UDP,你的例子是TCP而已。资源消耗大,更大的程度是因为走IO总线。

是netjava小马哥吧,我是学员。我知道做通信我还有很长的路要走的,现在刚开始……



是的,没有关系,慢慢来啊,你会stronger的。

相互学习哦,呵呵!看看我给你的留言,你可以考虑一下!
17 楼 taolei0628 2010-12-16  
在多数java应用里用TCP还是比较靠谱的,用UDP可能要解决的问题TCP都已经帮我们解决了。
我说到的问题其实是我看到的一段基于线程池的Socket服务器端框架代码,不知道是不是普遍存在。
在那段代码中先accept,此时连接已经建立,再把它放进任务队列等待处理。
通常这个等待是有并发和等待超时限制的,我认为正确的方式是在真正能提供服务的时候再accept。
楼主的非线程池的代码正好回避了这个问题,所以我才说了上面的话。
16 楼 贾懂凯 2010-12-16  
mercyblitz 写道
贾懂凯 写道
taolei0628 写道
用Executor不见得就比你现在的方法好。
等你贴出线程池的用法后再看看有没有我预计到的问题出现吧。
给你个提示,网络连接是个比线程更重量级的资源。

确实如此,三次握手会占比较长时间,只有到第三次握手完成才会返回socket实例。我觉得如果并发访问要求比较高的话,用我上面的代码中的线程池方法应该能增加效率。不过,如果在accept方法上加一个超时机制效果会更好。
求taolei0628指点~~



TCP/IP上面还有UDP,你的例子是TCP而已。资源消耗大,更大的程度是因为走IO总线。

是netjava小马哥吧,我是学员。我知道做通信我还有很长的路要走的,现在刚开始……
15 楼 yeshucheng 2010-12-16  
LZ最好把你的认识深入浅出写出,这样挺好的。学习,呵呵
14 楼 贾懂凯 2010-12-16  
yeshucheng 写道
accept:内核数+1
handler交给worker线程做

其实NIO很多都类似这样干的

个人觉得NIO相对于阻塞编程有以下优点:
1、节约了多客户阻塞等待的系统开销(用Selector成组监听)
2、一定程度上限制了同步问题的发生(如果你用独立的线程处理handler的话那就未必)
3、将阻塞编程中本来用stream屏蔽的套接字缓冲暴露给程序员,这样做的好处是程序员可以
根据不同的系统需求自己设计缓冲区(并且“直接缓冲区”的应用也很给力),能很好的在内存和CPU的开销见找到平衡点(这个很难,得有点功力了)。
4、当然,控制某些连接的优先级也不像阻塞编程中(仅仅通过设置线程优先级)那样不可预测了。
……
准备过一段时间专门写一篇,今天不多说了。
13 楼 贾懂凯 2010-12-16  
rainsilence 写道
吃完饭回来没下文了?

见:http://www.iteye.com/topic/842139?page=2#1806256
12 楼 rainsilence 2010-12-16  
吃完饭回来没下文了?
11 楼 mercyblitz 2010-12-16  
贾懂凯 写道
taolei0628 写道
用Executor不见得就比你现在的方法好。
等你贴出线程池的用法后再看看有没有我预计到的问题出现吧。
给你个提示,网络连接是个比线程更重量级的资源。

确实如此,三次握手会占比较长时间,只有到第三次握手完成才会返回socket实例。我觉得如果并发访问要求比较高的话,用我上面的代码中的线程池方法应该能增加效率。不过,如果在accept方法上加一个超时机制效果会更好。
求taolei0628指点~~



TCP/IP上面还有UDP,你的例子是TCP而已。资源消耗大,更大的程度是因为走IO总线。
10 楼 yeshucheng 2010-12-16  
accept:内核数+1
handler交给worker线程做

其实NIO很多都类似这样干的
9 楼 贾懂凯 2010-12-16  
zhaoxin1943 写道
贾懂凯 写道
taolei0628 写道
用Executor不见得就比你现在的方法好。
等你贴出线程池的用法后再看看有没有我预计到的问题出现吧。
给你个提示,网络连接是个比线程更重量级的资源。

确实如此,三次握手会占比较长时间,只有到第三次握手完成才会返回socket实例。我觉得如果并发访问要求比较高的话,用我上面的代码中的线程池方法应该能增加效率。不过,如果在accept方法上加一个超时机制效果会更好。
求taolei0628指点~~

楼主为何不考虑用现成的框架呢?我们项目也需要用到socket编程,前一阵子用过netty,后来发现XSocket更好用。

框架易用,底层难把握,况且需求各异要求设计应时而变。

相关推荐

    单线程与多线程socket通信

    Java编写的简易socket通信,既有单线程socket通信也有多线程socket通信,使用Java原生sdk实现,可以运行。

    多线程的FTP客户端,SOCKET,MFC,多线程

    多线程的FTP客户端 VC++6.0 mfc SOCKET 套接字 多线程 C/C++

    serversocket单线程跟多线程例子

    serversocket单线程跟多线程例子,很值得学习的

    java socket线程池

     //Runtime的availableProcessors()方法返回当前系统的CPU的数目 //系统的CPU越多,线程池中工作线程的数目也越多 executorService= Executors.newFixedThreadPool(   Runtime.getRuntime()....

    多线程Socket.

    多线程Socket阻塞模式下通信的例子 BCB-Socket阻塞模式通讯测试(多线程) 作者:Sncel(地狱情人) QQ:6522203 指导:invalid(空心菜) QQ:309283 http://bcb.vicp.net 测试方法: 在单机上测试: 如果有多个客户...

    C# socket多线程编程

    所谓单个写入程序/多个阅读程序的线程同步问题,是指任意数量的线程访问共享资源时,写入程序(线程)需要修改共享资源,而阅读程序(线程)需要读取数据。在这个同步问题中,很容易得到下面二个要求: 1) 当一个...

    Socket多线程文件传输

    用Delphi XE5 编写的 Socket多线程文件传输 客户端是单线程 服务器端是多线程 记录类型与文件流联合传输,通过记录类型传递信息给服务器 传输速度快 与网络拷贝相同 服务器端会通过MD5码验证接收到文件的正确性 并...

    服务器客户端-socket(进程线程)

    服务器客户端-socket(进程线程),包括套接字,多线程,多进程,单进程,并发,互斥锁,tcp/ip,udp等

    为什么说Redis是单线程的以及Redis为什么这么快!

    简单解释下第二条:上下文切换就是cpu在多线程之间进行轮流执行(枪战cpu资源),而redis单线程的,因此避免了繁琐的多线程上下文切换。 重点解释下多路复用: 多路-指的是多个socket连接,复用-指的是复用一个线程...

    java socket多线程聊天

    该软件是一个基于java语言的socket编程,可以实现单个服务器对应多个客户端的聊天。采用swt,功能还是比较完善的。

    单线程实现同时监听多个端口(windows平台c++代码)

    单线程实现同时监听多个端口(windows平台c++代码)。文章查看https://www.cnblogs.com/yuanchenhui/p/icop_accept.html

    用于socket的单线程QQ聊天

    实现基于服务器转发的任意多点间的数据共享与交换,仅供交流,水平有限,有不足之处还请各位大牛指点!。

    java socket 单线程实现P2P通信

    简单大循环 单线程实现通信 1. TCP连接通信 2. Server先监听,等待Client连接 3. 双方都可以发"Stop!"停止通信,但此程序Client只会停止,Server可以一直监听,即断开后,Client可以再次连接 4. 不能一对多通信,...

    socket工程文件多线程版本

    socket通信的客户端和服务器端的工程文件,这个是多线程版本的,实现了多个客户端向服务器端持续发送数据

    java 多线程 socket

    Java,socket多线程编程,及socket输入输出单独提出思想。

    socket多线程代码

    Java,socket多线程编程,及socket输入输出单独提出思想。

    C++ socket 网络单、多线程源代码

    C++ socket 网络单、多线程源代码。这是我特定写下来分享给大家的,包含了多线程和单线程的源代码框架。是非常好的学习框架,希望对大家有帮助。 如是编译有错,加QQ497680864

    Java的多线程和socket实现聊天室代码

    Java实现简单的聊天室,单服务进程,多客户线程,用socket进行通信。适合新手学习socket。

    C++编写 win32控制台下 Socket通信 多线程 聊天室 服务器 客户端

    C++语言编写,SOCKET套接字通信,分服务器和客户端,多线程模型,Win32控制台。 首先聊天室分为服务端和客户端,通过TCP连接通信。运行顺序为: 1.首先打开服务服; 2.打开客户端,用户输入名字,连接到服务端后...

    单线程多路复用(异步通信)

    IO多路复用服务器编程

Global site tag (gtag.js) - Google Analytics