Socket Server-基于线程池的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
Socket Server-基于线程池的TCP服务器的更多相关文章
- 【Java TCP/IP Socket】基于线程池的TCP服务器(含代码)
了解线程池 在http://blog.csdn.net/ns_code/article/details/14105457(读书笔记一:TCP Socket)这篇博文中,服务器端采用的实现方式是:一个客 ...
- 基于线程池的多并发Socket程序的实现
Socket“服务器-客户端”模型的多线程并发实现效果的大体思路是:首先,在Server端建立“链接循环”,每一个链接都开启一个“线程”,使得每一个Client端都能通过已经建立好的线程来同时与Ser ...
- 基于线程开发一个FTP服务器
一,项目题目:基于线程开发一个FTP服务器 二,项目要求: 基本要求: 1.用户加密认证 2.允许同时多用户登录 3.每个用户有自己的家目录 ,且只能访问自己的家目录 4.对用户进行磁盘配 ...
- requests模块session处理cookie 与基于线程池的数据爬取
引入 有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时,如果使用之前requests模块常规操作时,往往达不到我们想要的目的,例如: #!/usr/bin/ ...
- Python网络爬虫之cookie处理、验证码识别、代理ip、基于线程池的数据爬去
本文概要 session处理cookie proxies参数设置请求代理ip 基于线程池的数据爬取 引入 有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时, ...
- Linux网络通信(线程池和线程池版本的服务器代码)
线程池 介绍 线程池: 一种线程使用模式.线程过多会带来调度开销,进而影响缓存局部性和整体性能.而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务.这避免了在处理短时间任务时创建与销毁线程的 ...
- 设计模式:基于线程池的并发Visitor模式
1.前言 第二篇设计模式的文章我们谈谈Visitor模式. 当然,不是简单的列个的demo,我们以电商网站中的购物车功能为背景,使用线程池实现并发的Visitor模式,并聊聊其中的几个关键点. 一,基 ...
- 基于线程池的多线程售票demo2.0(原创)
继上回基于线程池的多线程售票demo,具体链接: http://www.cnblogs.com/xifenglou/p/8807323.html以上算是单机版的实现,特别使用了redis 实现分布式锁 ...
- 基于线程池的多线程售票demo(原创)
废话不多说,直接就开撸import org.springframework.util.StopWatch;import java.util.concurrent.*;/** * 基于线程池实现的多线程 ...
随机推荐
- 【LeetCode OJ】Same Tree
Problem Link: https://oj.leetcode.com/problems/same-tree/ The following recursive version is accepte ...
- html默认属性
对于display为block来说width默认是满长的,即父级得100%,而高度是0,除非手动设置为100%或指定高度.
- SqlServer不能将text列类型更改为ntext的问题
可以先将text类型更改为nvarchar,在将nvarchar更改为ntext即可. alter db_note alter column [content] nvarchar; alter db_ ...
- 07——为多态基类声明为virtual析构函数
当基类确定被继承的时候,析构函数声明为virtual是必须的 当返回的派生类的指针或引用的时候,调用析构函数容易发生内存泄漏 当基类作为抽象类使用,声明pure virtual析构函数 析构函数的顺序 ...
- VTK的学习资源
本文介绍从哪儿开始学习VTK(Visualization Toolkit的简称),如何在网上找寻VTK的学习资源. 首先,可以到维基百科或者百度百科上查看VTK条目,了解VTK是什么. http:// ...
- css3背景图水平垂直顺时针逆时针翻转旋转
.bgPlay{ background:url(../images/bg.jpg) no-repeat; /* background-size:auto auto || cover 代表以宽或高填满元 ...
- oracle查询一个时间段每天的数据量
1.需求: 从所有数据中,查出一个时间段中每天的数据量,即:按日做汇总. 2.SQL语句模板: select trunc(date_col) date, sum(num_col) num, count ...
- Linux中Main函数的执行过程
1. 问题:Linux如何执行main函数. 本文使用一个简单的C程序(simple.c)作为例子讲解.代码如下, int main() { return(0); } 2. 编译 -#gcc -o ...
- Linux系统初始流程
一.0S(内核的功能):平台类软件(通用软件) 进程管理:进程调度器(scheduler)维持一个任务结构(task_struct) 内存管理:如何使用线性地址空间,如何分段,如何分页,如何避免内存( ...
- 【python】Redis介绍及简单使用
一.redis redis是一个key-value存储系统.和 Memcached类似,它支持存储的value类型相对更多,包括string(字符串). list(链表).set(集合).zset(s ...