socket 连接池

这是一个哥们写的一个文档,我贴出来和大家分享,希望有帮助

socket基础

对于我们大多数程序员来说,大都习惯于抽象层的开发,而对底层的认识确实不够。Socket是存在于这两者之间,就是我们都极力避免的底层细节和我们尽力处理的抽象层的开发。这里我们将研究足够的底层细节以使我们对抽象层更好的理解。

计算机之间的通讯是以一种非常简单的方式进行。计算机芯片是由一系列以1和0为开关的单元组成,由这些芯片存储和转发数据。当计算机要存储数据时,它要做的是不断的流化成百上千个这样的位或字节,而且它们要在速度,序列和时钟等达成一致。那么我们当要在应用之间传递信息应该如何考虑这些细节呢?

为了避免这种事情,我们需要用一系列packaged protocol来做这样的事情。使我们不用去关心底层的细节,而处理应用层的工作。这些packaged protocol我们称做stacks,现在我们最常用的是TCP/IP,大多数stacks包括TCP/IP都非常的接近ISO的OSI,ISO说一个可靠的网络架构应该具有七个的逻辑层。如图,TCP/IP映射了OSI的两层。我们不想钻研太多的底层细节,但我们也必须知道socket到底存在于哪里。

socket存在于会话层(如图),会话层在面向应用的高层和实时的数据交换的底层之间。会话层为在两台计算机进行通讯管理和控制数据流提供服务。作为这个层的一部分,socket隐藏了在物理线路上位和字节的传输的复杂性。换句话说,socket扮演了一个高层的接口,它隐藏了在不明确的信道上传输1和0的复杂性。

当你在代码中用sockets时,这个时候代码工作在表示层。表示层为应用层提供了一些通用的信息表示。

假如你准备把你的应用连接到一个只能接受EBCDICD的银行系统。而你的应用域内的对象是以ASCII存储。在这种情况下,你必须在表示层把数据从EBCDIC转化为ASCII,接着才能把域对象提交到应用层,这样你的应用层才能操作域对象。所编写的socket处理代码仅仅存在于表示层,你的应用层不需知道sockets究竟是如何工作的。

既然我们已经知道了sockets的作用,那么问题是什么是socket,Bruce Eckel在《Thinking in Java》中这样描述:socket是代表两台计算机终端连接的软件抽象。对于一个连接,那么在两台机器上都存在着socket可以想象为一条两端是socket的电缆连接着两台机器。

在核心,一台计算机上的socket与另一台计算机上的socket建立了一条通信信道。程序员可以利用这条信道在两台计算机上发送数据。当你发送数据时,TCP/IP STACK的每一层将加上适当的头信息来封装要发送的数据。这些头信息帮助你的信息可以发送到目的地。好消息是Java通过stream要发送的数据隐藏了这些细节。这就是streaming sockets。

如果你需要在两台计算机上通信不是通过高级的象ORBs(and Corba,RMI,IIOP)这些东西,那么socket是最适合你的。

sockets的底层细节将会实现这些东西。幸运的是,Java平台已经可以容易地通过简单而强大的高层抽象来建立和使用sockets。

sockets的类型:

通常,在Java语言中,有这两种常见的类型:

TCP sockets 实现Socket class,我们稍后讨论

UDP sockets 实现DatagramSocket

TCP和UDP可以达到相同的效果,但通过不同方式实现。两者都接受传输层的包,然后都把他们发送到表示层。

TCP把信息分割成包然后在接受端以正确的序列重新组装。它也处理请求重传的丢失包。在高层不需要担忧太多。

UDP不提供重传。它只简单的传输。在高层必须判断信息是否完整,是否正确。通常,UDP把低层的性能加在你的应用上,但只要你的应用不需要一次交换大量的数据,或不需要通过重装大量的数据报来完成一次信息。否则,TCP是最简单的而且可能是最有效的选择。

 

仔细探讨Java中的Socket

Java在java.net包中实现了sockets,在这个示例中,我们会用到这三个class:

URLConnection,Socket,ServerSocket

在java.net中包含很多的class,这些是经常用到的,我们首先介绍URLConnection,这个类让你在你的代码中运用socket而不需要知道socket的低层细节。URLConnection是所有在一个应用和一个URL之间建立通信链路的类的抽象父类。URLConnections是在WEB服务器获得文档时非常有用,但也可被用于连接任何被URL标示的资源。这个类的实例可被用于读写到这些资源。比如,你可以连接到一个servlet,可以发送一个XML的String到服务器来处理。URLConnection的子类(如HttpURLConnection)是提供额外的特性。我们的例子,我们不做特殊的说明,我们将利用URLConnection提供的默认的行为。

连接到一个URL涉及到以下几步:

1)建立URLConnection

2)用多个setter方法配置URLConnection

3)连接到URL

4)用多个getter方法进行交互

接下来,我们会看到一些例子,这些例子说明了如何用URLConnection来从服务器请求一个document。

 URL客户端的结构:

import java.io.*;

import java.net.*;

public class URLClient {

    protected URLConnection connection;

    public static void main(String[] args) {

    }

    public String getDocumnetAt(String urlString) {

    }

}

在程序中,我们有一个URLConnection变量。

有一个main()方法,用来处理浏览网页的逻辑。有一个getDocument()方法来连接server和请求一个特定的页面,接下来我们会讨论每个方法的细节实现。

public static void main(String[] args) {

    URLClient client = new URLClient();

    String yahoo = client.getDocumentAt("http://www.yahoo.com");

    System.out.print(yahoo);

}

main方法简单的创建了一个URLClient对象,并且通过传递一个合法的URL来调用它的getDocumentAt()方法。然后把返回的值打印到终端,真正的工作其实都包括在getDocumentAt()方法中。

public String getDocumentAt(String urlString) {

    StringBuffer document = new StringBuffer();

    try {

        URL url = new URL(urlString);

        URLConnection conn = url.openConnection();

BufferedReader reader = new

    BufferedReader(new InputStreamReader(conn.getInputStream()));

        String line=null;

        while((line=reader.readLine())!=null) document.append(line+"\n");

        reader.close();

    } catch(IOException e) {

        System.out.println("Unable to connect to URL:"+urlString);

    } catch(IOException e) {

        System.out.println("IOException when connecting to URL:"+urlString);

    }

    return document.toString();

}

从上面的描述中,可以看出URLConnection是用socket从我们给定的URL中打开连接。

一个简单的例子

在这一章我们给的例子会说明如何使用Socket和ServerSocket,客户端用Socket连接到服务器。服务器会用ServerSocket监听3000端口。客户程序请求服务器上的C盘上的一个文件。

 

客户端程序RemoteFileClient结构:

import java.io.*;

import java.net.*;

public classs RemoteFileClient {

    protected String hostIp;

    protected int hostPort;

    protected BufferedReader socketReader;

    protected PrintWriter socketWriter;

 

    public RemoteFIleClient(String aHostIp,int aHostPort) {

        hostIp = aHostIp;

        hostPort = aHostPort;

    }

    public static void main(String[] args) {

    }

    public void setUpConnection() {

    }

    public String getFile(String fileNameToGet) {

    }

    public void tearDownConnection() {

    }

}

首先我们实现main函数

public static void main(String[] args) {

    RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1", 3000);

    remoteFileClient.setUpConnection();

    String fileContents = remoteFileClient.getFile("C:\\Temp\\RemoteFile.txt");

    remoteFileClient.tearDownConnection();

    System.out.println(fileContents);

}

下面是建立连接函数的实现setUpConnection()

public void setUpConnection() {

    try {

        Socket client = new Socket(hostIp,hostPort);

        socketReader = new

BufferedReader(new InputStreamReader(client.getInputStream()));

        socketWriter = new PrintWriter(client.getOutputStream());

    } catch (UnknownHostException e) {

System.out.println("Error setting up socket connection: unknown host at " + hostIp + ":" + hostPort);

    } catch (IOException e) {

        System.out.println("Error setting up socket connection: " + e);

    }

}

我们把Socket的InputStream封装BufferedReader以使我们可以从stream中获得数据。我们把Socket的OutputStream封装在PrinterWriter以使我们可以向服务器请求文件。

下面是getFile()的实现

public String getFile(String fileNameToGet) {

    StringBuffer fileLines = new StringBuffer();

    try {

        socketWriter.println(fileNameToGet);

        socketWriter.flush();

        String line = null;

        while ((line = socketReader.readLine()) != null) fileLines.append(line + "\n");

    } catch (IOException e) {

        System.out.println("Error reading from file: " + fileNameToGet);

    }

    return fileLines.toString();

}

flush()的作用,使数据发送到服务器而不需要关闭Socket。

下面是关闭连接的函数的实现tearDownConnection()

public void tearDownConnection() {

    try {

        socketWriter.close();

        socketReader.close();

    } catch (IOException e) {

        System.out.println("Error tearing down socket connection: " + e);

    }

}

主要是关闭一些相应的资源。

 

服务器端的程序RemoteFileServer结构:

import java.io.*;

import java.net.*;

public class RemoteFileServer {

    protected int listenPort = 3000;

    public static void main(String[] args) {

    }

    public void acceptConnections() {

    }

    public void handleConnection(Socket incomingConnection) {

    }

}

首先我们实现main函数

public static void main(String[] args) {

    RemoteFileServer server = new RemoteFileServer();

    server.acceptConnections();

}

实现cceptConnections()函数

public void acceptConnections() {

    try {

        ServerSocket server = new ServerSocket(listenPort);//

        Socket incomingConnection = null;

        while (true) {

            incomingConnection = server.accept();

            handleConnection(incomingConnection);

        }

    } catch (BindException e) {

        System.out.println("Unable to bind to port " + listenPort);

    } catch (IOException e) {

        System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);

    }

}

可以设置超时,用setSoTimeout()

实现handleConnection()函数

public void handleConnection(Socket incomingConnection) {

    try {

        OutputStream outputToSocket = incomingConnection.getOutputStream();

        InputStream inputFromSocket = incomingConnection.getInputStream();

        BufferedReader streamReader = new

BufferedReader(new InputStreamReader(inputFromSocket));

        FileReader fileReader = new FileReader(new File(streamReader.readLine()));

 

        BufferedReader bufferedFileReader = new BufferedReader(fileReader);

        PrintWriter streamWriter = new PrintWriter(incomingConnection.getOutputStream());

        String line = null;

        while ((line = bufferedFileReader.readLine()) != null) {

            streamWriter.println(line);

        }

        fileReader.close();

        streamWriter.close();

        streamReader.close();

    } catch (Exception e) {

        System.out.println("Error handling a client: " + e);

    }

}

一个多线程的例子

前面的例子一次只能处理一个客户端的请求,原因是handleConnection()是一个阻塞性的方法,他只能处理完一个客户端的请求才能进行下一个的处理,但这是我们在实际的情况中很少用到的,大多数的情况下,我们需要一个多线程的应用。

 

在acceptConnections()的方法中,我们修改

ServerSocket server = new ServerSocket(listenPort)为:

ServerSocket server = new ServerSocket(listenPort, 5)

说明:5代表我们可以同时处理5个客户端的请求,第六个将被阻塞在请求队列中。

修改handleConnection()为:

public void handleConnection(Socket connectionToHandle) {

    new Thread(new ConnectionHandler(connectionToHandle)).start();

}

我们需要新建一个类ConnectionHandler结构:

import java.io.*;

import java.net.*;

public class ConnectionHandler implements Runnable {

    Socket socketToHandle;

    public ConnectionHandler(Socket aSocketToHandle) {

        socketToHandle = aSocketToHandle;

    }

    public void run() {

    }

}

run()的实现:

public void run() {

    try {

        PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream());

        BufferedReader streamReader = new BufferedReader(new InputStreamReader(socketToHandle.getInputStream()));

        String fileToRead = streamReader.readLine();

        BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));

        String line = null;

        while ((line = fileReader.readLine()) != null) streamWriter.println(line);

        fileReader.close();

        streamWriter.close();

        streamReader.close();

    } catch (Exception e) {

        System.out.println("Error handling a client: " + e);

    }

}

连接池的例子

在上面的多线程的例子中,每当客户端请求时,将新建一个ConnectionHandler的一个线程。那就意味着将会存在大量的运行的线程。如何我们考虑性能的要求的话,那我们任何更有效的处理服务器端的问题呢?我们可以建立一个有有限连接数量的请求连接池。这样的设计有两方面的好处,一是它限制了同步连接数。二是我们只需设置一次ConnectionHandler线程。我们的客户端的程序还是不变。在服务器端当服务器启动时,我们创建一定数量的ConnectionHandler。

 

我们需要创建连接池的程序,PooledRemoteFileServer,

结构:

import java.io.*;

import java.net.*;

import java.util.*;

 

public class PooledRemoteFileServer {

    protected int maxConnections;

    protected int listenPort;

    protected ServerSocket serverSocket;

    public PooledRemoteFileServer(int aListenPort, int maxConnections) {

        listenPort = aListenPort;

        this.maxConnections = maxConnections;

    }

    public static void main(String[] args) {

    }

    public void setUpHandlers() {

    }

    public void acceptConnections() {

    }

    protected void handleConnection(Socket incomingConnection) {

    }

}

setUpHandlers()主要建立maxConnections 个PooledConnectionHandler。

acceptConnections()和前面的函数的作用是一样的,监听来自客户端的请求连接。

handleConnection()实际上是处理每一个客户端已经建立好的连接。

首先我们实现main()函数

public static void main(String[] args) {

    PooledRemoteFileServer server = new PooledRemoteFileServer(3000, 3);

    server.setUpHandlers();

    server.acceptConnections();

}

PooledRemoteFileServer server = new PooledRemoteFileServer(3000, 3)是初始化一个PooledRemoteFileServer对象。接着调用setUpHandlers()方法设置3个PooledConnectionHandlers,一旦服务器准备好,那我们就调用acceptConnections()。

下面是setUpHandlers()的实现

public void setUpHandlers() {

    for (int i = 0; i < maxConnections; i++) {

        PooledConnectionHandler currentHandler = new PooledConnectionHandler();

        new Thread(currentHandler, "Handler " + i).start();

    }

}

创建了maxConnections个PooledConnectionHandler然后在新的线程中启动。

 

下面我们要重新修改一下上面的handleConnections()的函数:

protected void handleConnection(Socket connectionToHandle) {

    PooledConnectionHandler.processRequest(connectionToHandle);

}

 

我们通过新建的PooledConnectionHandler的类来处理客户请求的连接,下面是PooledConnectionHandler的结构:

 

import java.io.*;

import java.net.*;

import java.util.*;

public class PooledConnectionHandler implements Runnable {

    protected Socket connection;

    protected static List pool = new LinkedList();

    public PooledConnectionHandler() {

    }

    public void handleConnection() {

    }

    public static void processRequest(Socket requestToHandle) {

    }

    public void run() {

    }

}

 

这个Helper类中的两个成员变量,connection是代表当前要处理的连接。Pool是存储的是需要被处理的连接。我们首先实现processRequest()的方法。

public static void processRequest(Socket requestToHandle) {

    synchronized (pool) {

        pool.add(pool.size(), requestToHandle);

        pool.notifyAll();

    }

}

 

这里我们用到了同步块,我们只要简单的理解为一个客户请求进行这个同步块操作时,其他的只能等待。这个方法中,我们填充了池即pool。

 

接着是我们需要从pool中得到服务器的连接,我们需要实现run()方法。(见下页)

 

 

 

 

 

 

 

public void run() {

    while (true) {

        synchronized (pool) {

            while (pool.isEmpty()) {

            try {

                pool.wait();

            } catch (InterruptedException e) {

                return;

            }

            }

            connection = (Socket) pool.remove(0);

        }

        handleConnection();

    }

}

如果pool为空,就需要等待,不为空时就从pool中取出第一个连接。然后处理handleConnection()

重新对handleConnection()进行设计,这个函数是PooledConnectionHandler的成员函数。

public void handleConnection() {

    try {

        PrintWriter streamWriter = new PrintWriter(connection.getOutputStream());

        BufferedReader streamReader =

        new BufferedReader(new InputStreamReader(connection.getInputStream()));

        String fileToRead = streamReader.readLine();

        BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));

        String line = null;

        while ((line = fileReader.readLine()) != null)

            streamWriter.println(line);

        fileReader.close();

        streamWriter.close();

        streamReader.close();

    } catch (FileNotFoundException e) {

        System.out.println("Could not find requested file on the server.");

    } catch (IOException e) {

        System.out.println("Error handling a client: " + e);

    }

}

 


联系方式请点击进入:http://www.seewn.com

 

 

转载于:https://www.cnblogs.com/gzcb/archive/2012/10/17/2727970.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值