【Java TCP/IP Socket】基于线程池的TCP服务器(含代码)
了解线程池
在http://blog.csdn.net/ns_code/article/details/14105457(读书笔记一:TCP Socket)这篇博文中,服务器端采用的实现方式是:一个客户端对应一个线程。但是,每个新线程都会消耗系统资源:创建一个线程会占用CPU周期,而且每个线程都会建立自己的数据结构(如,栈),也要消耗系统内存,另外,当一个线程阻塞时,JVM将保存其状态,选择另外一个线程运行,并在上下文转换(context switch)时恢复阻塞线程的状态。随着线程数的增加,线程将消耗越来越多的系统资源,这将最终导致系统花费更多的时间来处理上下文转换盒线程管理,更少的时间来对连接进行服务。在这种情况下,加入一个额外的线程实际上可能增加客户端总服务的时间。
我们可以通过限制线程总数并重复使用线程来避免这个问题。我们让服务器在启动时创建一个由固定线程数量组成的线程池,当一个新的客户端连接请求传入服务器,它将交给线程池中的一个线程处理,该线程处理完这个客户端之后,又返回线程池,继续等待下一次请求。如果连接请求到达服务器时,线程池中所有的线程都已经被占用,它们则在一个队列中等待,直到有空闲的线程可用。
实现步骤
1、与一客户一线程服务器一样,线程池服务器首先创建一个ServerSocket实例。
2、然后创建N个线程,每个线程反复循环,从(共享的)ServerSocket实例接收客户端连接。当多个线程同时调用一个ServerSocket实例的accept()方法时,它们都将阻塞等待,直到一个新的连接成功建立,然后系统选择一个线程,为建立起的连接提供服务,其他线程则继续阻塞等待。
3、线程在完成对一个客户端的服务后,继续等待其他的连接请求,而不终止。如果在一个客户端连接被创建时,没有线程在accept()方法上阻塞(即所有的线程都在为其他连接服务),系统则将新的连接排列在一个队列中,直到下一次调用accept()方法。
示例代码
我们依然实现http://blog.csdn.net/ns_code/article/details/14105457这篇博客中的功能,客户端代码相同,服务器端代码在其基础上改为基于线程池的实现,为了方便在匿名线程中调用处理通信细节的方法,我们对多线程类ServerThread做了一些微小的改动,如下:
package zyb.org.server; import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket; /**
* 该类为多线程类,用于服务端
*/
public class ServerThread implements Runnable { private Socket client = null;
public ServerThread(Socket client){
this.client = client;
} //处理通信细节的静态方法,这里主要是方便线程池服务器的调用
public static void execute(Socket client){
try{
//获取Socket的输出流,用来向客户端发送数据
PrintStream out = new PrintStream(client.getOutputStream());
//获取Socket的输入流,用来接收从客户端发送过来的数据
BufferedReader buf = new BufferedReader(new InputStreamReader(client.getInputStream()));
boolean flag =true;
while(flag){
//接收从客户端发送过来的数据
String str = buf.readLine();
if(str == null || "".equals(str)){
flag = false;
}else{
if("bye".equals(str)){
flag = false;
}else{
//将接收到的字符串前面加上echo,发送到对应的客户端
out.println("echo:" + str);
}
}
}
out.close();
buf.close();
client.close();
}catch(Exception e){
e.printStackTrace();
}
}
@Override
public void run() {
execute(client);
} }
这样我们就可以很方便地在匿名线程中调用处理通信细节的方法,改进后的服务器端代码如下:
package zyb.org.server; import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket; /**
* 该类实现基于线程池的服务器
*/
public class serverPool { private static final int THREADPOOLSIZE = 2; public static void main(String[] args) throws IOException{
//服务端在20006端口监听客户端请求的TCP连接
final ServerSocket server = new ServerSocket(20006); //在线程池中一共只有THREADPOOLSIZE个线程,
//最多有THREADPOOLSIZE个线程在accept()方法上阻塞等待连接请求
for(int i=0;i<THREADPOOLSIZE;i++){
//匿名内部类,当前线程为匿名线程,还没有为任何客户端连接提供服务
Thread thread = new Thread(){
public void run(){
//线程为某连接提供完服务后,循环等待其他的连接请求
while(true){
try {
//等待客户端的连接
Socket client = server.accept();
System.out.println("与客户端连接成功!");
//一旦连接成功,则在该线程中与客户端通信
ServerThread.execute(client);
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
//先将所有的线程开启
thread.start();
}
}
}
结果分析
为了便于测试,程序中,我们将线程池中的线程总数设置为2,这样,服务器端最多只能同事连接2个客户端,如果已有2个客户端与服务器建立了连接,当我们打开第3个客户端的时候,便无法再建立连接,服务器端不会打印出第3个“与客户端连接成功!”的字样。
这第3个客户端如果过了一段时间还没接收到服务端发回的数据,便会抛出一个SocketTimeoutException异常,从而打印出如下信息(客户端代码参见:http://blog.csdn.net/ns_code/article/details/14105457):
如果在抛出SocketTimeoutException异常之前,有一个客户端的连接关掉了,则第3个客户端便会与服务器端建立起连接,从而收到返回的数据
改进
在创建线程池时,线程池的大小是个很重要的考虑因素,如果创建的线程太多(空闲线程太多),则会消耗掉很多系统资源,如果创建的线程太少,客户端还是有可能等很长时间才能获得服务。因此,线程池的大小需要根据负载情况进行调整,以使客户端连接的时间最短,理想的情况是有一个调度的工具,可以在系统负载增加时扩展线程池的大小(低于大上限值),负载减轻时缩减线程池的大小。一种解决的方案便是使用Java中的Executor接口。
Executor接口代表了一个根据某种策略来执行Runnable实例的对象,其中可能包括了排队和调度等细节,或如何选择要执行的任务。Executor接口只定义了一个方法:
interface Executor{
void execute(Runnable task);
}
Java提供了大量的内置Executor接口实现,它们都可以简单方便地使用,ExecutorService接口继承于Executor接口,它提供了一个更高级的工具来关闭服务器,包括正常的关闭和突然的关闭。我们可以通过调用Executors类的各种静态工厂方法来获取ExecutorService实例,而后通过调用execute()方法来为需要处理的任务分配线程,它首先会尝试使用已有的线程,但如果有必要,它会创建一个新的线程来处理任务,另外,如果一个线程空闲了60秒以上,则将其移出线程池,而且任务是在Executor的内部排队,而不像之前的服务器那样是在网络系统中排队,因此,这个策略几乎总是比前面两种方式实现的TCP服务器效率要高。
改进的代码如下:
package zyb.org.server; import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors; /**
* 该类通过Executor接口实现服务器
*/
public class ServerExecutor { public static void main(String[] args) throws IOException{
//服务端在20006端口监听客户端请求的TCP连接
ServerSocket server = new ServerSocket(20006);
Socket client = null;
//通过调用Executors类的静态方法,创建一个ExecutorService实例
//ExecutorService接口是Executor接口的子接口
Executor service = Executors.newCachedThreadPool();
boolean f = true;
while(f){
//等待客户端的连接
client = server.accept();
System.out.println("与客户端连接成功!");
//调用execute()方法时,如果必要,会创建一个新的线程来处理任务,但它首先会尝试使用已有的线程,
//如果一个线程空闲60秒以上,则将其移除线程池;
//另外,任务是在Executor的内部排队,而不是在网络中排队
service.execute(new ServerThread(client));
}
server.close();
}
}
转自:http://blog.csdn.net/ns_code/article/details/14451911
【Java TCP/IP Socket】基于线程池的TCP服务器(含代码)的更多相关文章
- Python网络爬虫之cookie处理、验证码识别、代理ip、基于线程池的数据爬去
本文概要 session处理cookie proxies参数设置请求代理ip 基于线程池的数据爬取 引入 有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时, ...
- requests模块处理cookie,代理ip,基于线程池数据爬取
引入 有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时,如果使用之前requests模块常规操作时,往往达不到我们想要的目的. 一.基于requests模块 ...
- Socket Server-基于线程池的TCP服务器
了解线程池 在http://blog.csdn.net/ns_code/article/details/14105457(读书笔记一:TCP Socket)这篇博文中,服务器端采用的实现方式是:一个客 ...
- 基于线程池的多并发Socket程序的实现
Socket“服务器-客户端”模型的多线程并发实现效果的大体思路是:首先,在Server端建立“链接循环”,每一个链接都开启一个“线程”,使得每一个Client端都能通过已经建立好的线程来同时与Ser ...
- TCP IP SOCKET 笔记
网络由下往上分为 物理层.数据链路层.网络层.传输层.会话层.表示层和应用层. 通过初步的了解,我知道IP协议对应于网络层,TCP协议对应于传输层,而HTTP协议对应于应用层, 三者从本质上来说没有可 ...
- 基于线程池的多线程售票demo2.0(原创)
继上回基于线程池的多线程售票demo,具体链接: http://www.cnblogs.com/xifenglou/p/8807323.html以上算是单机版的实现,特别使用了redis 实现分布式锁 ...
- 基于线程池的多线程售票demo(原创)
废话不多说,直接就开撸import org.springframework.util.StopWatch;import java.util.concurrent.*;/** * 基于线程池实现的多线程 ...
- Java并发(四)线程池使用
上一篇博文介绍了线程池的实现原理,现在介绍如何使用线程池. 目录 一.创建线程池 二.向线程池提交任务 三.关闭线程池 四.合理配置线程池 五.线程池的监控 线程池创建规范 一.创建线程池 我们可以通 ...
- java 22 - 20 多线程之线程池
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互. 而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池. 线程池里的每一个线程代码结束后 ...
随机推荐
- oracle row_number的使用
create table studentInfo( id number(8) primary key, name varchar2(20) not null, ObjectName varcha ...
- javaEE(12)_数据库连接池
一.直接获取数据库连接和通过池获取示意图: 二.编写数据库连接池 1.实现DataSource接口,并实现连接池功能的步骤: •在DataSource构造函数中批量创建与数据库的连接,并把创建的连接加 ...
- Linux关于FTP安全
https://www.cnblogs.com/Hyber/archive/2017/02/04/6362916.htmlhttps://www.cnblogs.com/ichunqiu/p/7300 ...
- Ukulele 原来你也在这里
- [POJ]1164 The Castle
//markdown复制进来一堆问题 还是链接方便点 POJ 1164 The Castle 首先想到用9个方格来表示一个房间,如此一来复杂许多,MLE代码如下: //Writer:GhostCai ...
- shell-note-1-基础篇
1. Shell is a program written in C. It provides an interface for users to access to the service of o ...
- u-boot顶层目录config.mk分析
1. 设置obj与src ifneq ($(OBJTREE),$(SRCTREE)) ifeq ($(CURDIR),$(SRCTREE)) dir := else dir := $(subst $( ...
- OpenGLES2.0着色器语言glsl
OpenGLES2.0中是强制使用可编程的渲染管线的,使用的是glsl着色器语言,因为着色器语言是使用的GPU,即图形处理单元,而不是CPU,这样可以使CPU从繁重的几何计算和像素的处理中解脱出来了. ...
- 合肥工业大学数据结构上机实验代码与实验报告(全)github地址
我已经将这个学期的所有数据结构上机实验的代码与报告上传到github上了,一直都有这个想法,但没抽出时间来学习git.经过上周简单的练习后,我已经基本学会运营自己的代码仓库了.所有代码都是C++写的类 ...
- PAT Basic 1052
1052 卖个萌 萌萌哒表情符号通常由“手”.“眼”.“口”三个主要部分组成.简单起见,我们假设一个表情符号是按下列格式输出的: [左手]([左眼][口][右眼])[右手] 现给出可选用的符号集合,请 ...