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

时间: 2024-08-02 16:32:35

Socket Server-基于线程池的TCP服务器的相关文章

【Java TCP/IP Socket】基于线程池的TCP服务器(含代码)

了解线程池 在http://blog.csdn.net/ns_code/article/details/14105457(读书笔记一:TCP Socket)这篇博文中,服务器端采用的实现方式是:一个客户端对应一个线程.但是,每个新线程都会消耗系统资源:创建一个线程会占用CPU周期,而且每个线程都会建立自己的数据结构(如,栈),也要消耗系统内存,另外,当一个线程阻塞时,JVM将保存其状态,选择另外一个线程运行,并在上下文转换(context switch)时恢复阻塞线程的状态.随着线程数的增加,线

基于线程池的线程调度管控系统

本文将详细描述"基于线程池的线程调度管控系统"的实现原理,以梳理当时的编程思路. 简单叙述一下此线程池的开发背景:客户端是批量运行的,虽然客户端均运行在服务器上,但是大量客户端运行时它们对机器资源是抢占式的,所以客户端在大规模运行时与单次运行时的运行效果是不一样的,因为相同客户端在单次运行与大规模运行时所占有的资源量是不同的,理论上说大规模运行时客户端的数量越多,每个客户端所占有的资源量就越少,于是我们认为解决问题的关键应该是不管是单次运行还是大规模运行,相同客户端所获取的资源量就应该

基于进程池的多进程服务器通信

代码来源:游双的linux高性能服务器编程 主进程创建一个进程池,当有客户端发送请求时,主进程选择一个进程与该客户端进行通信,为了简单期间,去掉了一些信号处理代码,其中信号的用法可以参考基于多进程的网络聊天程序 服务器代码如下: #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h&

使用Android新式LruCache缓存图片,基于线程池异步加载图片

import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import a

基于线程池、消息队列和epoll模型实现Client-Server并发架构

引言 并发是什么?企业在进行产品开发过程中为什么需要考虑这个问题?想象一下天猫的双11和京东的618活动,一秒的点击量就有几十万甚至上百万,这么多请求一下子涌入到服务器,服务器需要对这么多的请求逐个进行消化掉,假如服务器一秒的处理能力就几万,那么剩下的不能及时得到处理的这些请求作何处理?总不能让用户界面一直等着,因此消息队列应运而生,所有的请求都统一放入消息队列,工作线程从消息队列不断的消费,消息队列相当于一个缓冲区,可达到解藕.异步和削峰的目的. Kafka.ActiveMQ.RabbitMQ

Android下基于线程池的网络访问基础框架

引言 现在的Android开发很多都使用Volley.OkHttp.Retrofit等框架,这些框架固然有优秀的地方(以后会写代码学习分享),但是我们今天介绍一种基于Java线程池的网络访问框架. 实现思路及实现 APP界面上面的数据都是通过网络请求获取的,我们能不能将网络请求依次入队,然后配合着Java线程池,让线程依次处理我们的请求,最后返回结果给我们.下面我们先来看一下线程池工具类的实现: 1 public class ThreadPoolUtils { 2 3 private Threa

C#线程系列讲座(3):线程池和文件下载服务器

如果设计一个服务器程序,每当处理用户请求时,都开始一个线程,将会在一定程序上消耗服务器的资源.为此,一个最好的解决方法就是在服务器启动之前,事先创建一些线程对象,然后,当处理客户端请求时,就从这些建好的线程中获得线程对象,并处理请求.保存这些线程对象的结构就叫做线程池. 在C#中可以通过System.Threading.ThreadPool类来实现,在默认情况下,ThreadPool最大可建立500个工作线程和1000个I/O线程(根据机器CPU个数和.net framework版本的不同,这些

基于线程池异步抓取

from multiprocessing.dummy import Pool #线程池模块 #必须只可以有一个参数 def my_requests(url): return requests.get(url=url,headers=headers).text start = time.time() urls = [ 'http://127.0.0.1:5000/bobo', 'http://127.0.0.1:5000/jay', 'http://127.0.0.1:5000/tom', ] p

基于线程池和连接池的Http请求

背景:最新项目需求调用http接口,所以打算使用最新的httpClient客户端写一个工具类,写好了以后在实际应用过程中遇到了一些问题,因为数据量还算 大,每次处理大概要处理600-700次请求,平均算下来大概需要20分钟,这个速度虽然是跑在定时任务中的,但是也是不能忍受的,所以有了这个博客. 1.首先想到的解决办法就是多线程发请求了,但是这个有坑,最后会在结果处说明. 2.代码方面如下 ExecutorService executor = Executors.newFixedThreadPoo