这是一个哥们写的一个文档,我贴出来和大家分享,希望有帮助
一 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