原贴地址: http://jeffchen.cnblogs.com/archive/2005/12/29/307269.aspx
前一段时间看了IBM developerWork上的一篇关于socket的tutorial,由浅入深的实现了server-client程序,现在这里整理一下。
什么是socket?
think in java 里给的回答:套接字是一种软件抽象,用于表达两台机器之间的连接“终端”。对于一个给定的连接,每台机器上都有一个套接字,您也可以想象它们之间有一条虚拟的“电缆”,“电缆”的每一端都插入到套接字中。当然,机器之间的物理硬件和电缆连接都是完全未知的。抽象的全部目的是使我们无须知道不必知道的细节。简言之,一台机器上的套接字与另一台机器上的套接字交谈就创建一条通信通道.
Socket 和 ServerSocket例子
一.客户端步骤:
1)用您想连接的机器的 IP 地址和端口实例化 Socket(如有问题则抛出 Exception)。
2)获取 Socket 上的流。
3)把流包装进 BufferedReader/PrintWriter 的实例,如果这样做能使事情更简单的话。
4)对 Socket 进行读写。
5)关闭打开的流。
RemoteFileClient类
import java.io.*;
import java.net.*;
public class 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) {//向远程服务器请求 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:\\WINNT\\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);
}
}
getFile() 的实现:
public String getFile(String fileNameToGet) {
StringBuffer fileLines = new StringBuffer();
try {
socketWriter.println(fileNameToGet); //把请求发送到主机,PrintWriter 是我们在创建连接期间建立的
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();
}
tearDownConnection()方法的实现:
public void tearDownConnection() {
try {
socketWriter.close();
socketReader.close();
} catch (IOException e) {
System.out.println("Error tearing down socket connection: " + e);
}
}
在上次做的聊天服务器程序中,因为"清除"的方法不对,导致占用大量内存:(
二.服务器步骤:
1)用一个您想让它侦听传入客户机连接的端口来实例化一个 ServerSocket(如有问题则抛出 Exception)。
2)调用 ServerSocket 的 accept() 以在等待连接期间造成阻塞。
3)获取位于该底层 Socket 的流以进行读写操作。
4)按使事情简单化的原则包装流。
5)对 Socket 进行读写。
6)关闭打开的流(并请记住,永远不要在关闭 Writer 之前关闭 Reader)。
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) { //与客户机 Socket 交互以将您所请求的文件的内容发送到客户机
}
}
main() 方法的实现:
public static void main(String[] args) {
RemoteFileServer server = new RemoteFileServer();
server.acceptConnections();
}
acceptConnections()方法的实现:
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);
}
}
我们通过调用该 ServerSocket 的 accept() 来告诉它开始侦听。accept() 方法将造成阻塞直到来了一个连接请求。此时,accept() 返回一个新的 Socket,这个 Socket 绑定到服务器上一个随机指定的端口,返回的 Socket 被传递给 handleConnection()。
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);
}
}
如果您在关闭 streamWriter 之前关闭 streamReader,则您可以往 Socket 写任何东西,但却没有任何数据能通过通道(通道被关 闭了)。
三.使服务器支持多线程
一般步骤:
1)修改 acceptConnections() 以用缺省为 50(或任何您想要的大于 1 的指定数字)实例化 ServerSocket。
2)修改 ServerSocket 的 handleConnection() 以用 ConnectionHandler 的一个实例生成一个新的 Thread。
3)借用 RemoteFileServer 的 handleConnection() 方法的代码实现 ConnectionHandler 类
public void acceptConnections() {
try {
ServerSocket server = new ServerSocket(listenPort, 5);
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);
}
}
假设我们指定待发数(backlog 值)是 5 并且有五台客户机请求连接到我们的服务器。我们的服务器将着手处理第一个连接,但处理该连接需要很长时间。由于我们的待发值是 5,所以我们一次可以放五个请求到队列中。我们正在处理一个,所以这意味着还有其它五个正在等待。等待的和正在处理的一共有六个。当我们的服务器仍忙于接受一号连接(记住队列中还有 2—6 号)时,如果有第七个客户机提出连接申请,那么,该第七个客户机将遭到拒绝。
public void handleConnection(Socket connectionToHandle) {
new Thread(new ConnectionHandler(connectionToHandle)).start();
}
我们对 RemoteFileServer 所做的大改动就体现在这个方法上。我们仍然在服务器接受一个连接之后调用 handleConnection(),但现在我们把该 Socket 传递给 ConnectionHandler 的一个实例,它是 Runnable 的。我们用 ConnectionHandler 创建一个新 Thread 并启动它。ConnectionHandler 的 run() 方法包含Socket 读/写和读 File 的代码,这些代码原来在 RemoteFileServer 的 handleConnection() 中。
ConnectionHandler 类
import java.io.*;
import java.net.*;
public class ConnectionHandler implements Runnable{
Socket socketToHandle;
public ConnectionHandler(Socket aSocketToHandle) {
socketToHandle = aSocketToHandle;
}
public void run() {
}
}
run() 方法的实现,同RemoteFileServer 的 handleConnection():
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 将为它提供服务。这种设计能带来以下好处:
1)它限定了允许同时连接的数目。
2)我们只需启动 ConnectionHandler Thread 一次。
步骤:
1)创建一个新种类的连接处理程序(我们称之为 PooledConnectionHandler)来处理池中的连接。
2)修改服务器以创建和使用一组 PooledConnectionHandler
在服务器端,我们在服务器启动时创建一定数量的 ConnectionHandler,我们把进入的连接放入“池”中并让 ConnectionHandler 打理剩下的事情。这种设计中有很多我们不打算讨论的可能存在的技巧。例如,我们可以通过限定允许在“池”中建立的连接的数目来拒绝客户机。
PooledRemoteFileServer类
import java.io.*;
import java.net.*;
import java.util.*;
public class PooledRemoteFileServer {
protected int maxConnections; //我们的服务器能同时处理的活动客户机连接的最大数目
protected int listenPort; //进入的连接的侦听端口
protected ServerSocket serverSocket; //接受客户机连接请求的 ServerSocket
public PooledRemoteFileServer(int aListenPort, int maxConnections) {
listenPort = aListenPort;
this.maxConnections = maxConnections;
}
public static void main(String[] args) {
}
public void setUpHandlers() { //创建数目为 maxConnections 的大量 PooledConnectionHandler
}
public void acceptConnections() {
}
protected void handleConnection(Socket incomingConnection) {
}
}
main() 方法的实现:
public static void main(String[] args) {
PooledRemoteFileServer server = new PooledRemoteFileServer(3000, 3);
server.setUpHandlers();
server.acceptConnections();
}
我们实例化一个新的 PooledRemoteFileServer,它将通过调用 setUpHandlers() 来建立三个 PooledConnectionHandler。一旦服务器就绪,我们就告诉它 acceptConnections()。
public void setUpHandlers() {
for (int i = 0; i < maxConnections; i++) {
PooledConnectionHandler currentHandler = new PooledConnectionHandler();
new Thread(currentHandler, "Handler " + i).start();
}
}
setUpHandlers() 方法创建 maxConnections(例如 3)个 PooledConnectionHandler 并在新 Thread 中激活它们。用实现了 Runnable 的对象来创建 Thread 使我们可以在 Thread 调用 start() 并且可以期望在 Runnable 上调用了 run()。换句话说,我们的 PooledConnectionHandler 将等着处理进入的连接,每个都在它自己的 Thread 中进行。我们在示例中只创建三个 Thread,而且一旦服务器运行,这就不能被改变。
我们实现需作改动的 handleConnections() 方法,它将委派 PooledConnectionHandler 处理连接:
protected void handleConnection(Socket connectionToHandle) {
PooledConnectionHandler.processRequest(connectionToHandle);
}
PooledConnectionHandler 类:
import java.io.*;
import java.util.*;
public class PooledConnectionHandler implements Runnable {
protected Socket connection; //当前正在处理的 Socket
protected static List pool = new LinkedList(); //名为 pool 的静态 LinkedList 保存需被处理的连接
public PooledConnectionHandler() {
}
public void handleConnection() {
}
public static void processRequest(Socket requestToHandle) {
}
public void run() {
}
}
processRequest() 方法,它将把传入请求添加到池中,并告诉其它正在等待的对象该池已经有一些内容:
public static void processRequest(Socket requestToHandle) {
synchronized (pool) {
pool.add(pool.size(), requestToHandle);
pool.notifyAll();
}
}
实现 PooledConnectionHandler 上需作改动的 run()方法,它将在连接池上等待,并且池中一有连接就处理它:
public void run() {
while (true) {
synchronized (pool) {
while (pool.isEmpty()) {
try {
pool.wait();
} catch (InterruptedException e) {
return;
}
}
connection = (Socket) pool.remove(0);
}
handleConnection();
}
}
实现需做改动的 handleConnection() 方法,该方法将攫取连接的流,使用它们,并在任务完成之后清除它们:
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);
}
}
总结:在现实生活中使用套接字只是这样一件事,即通过贯彻优秀的 OO 设计原则来保护应用程序中各层间的封装。
posted on 2007-01-23 09:55
OMG 阅读(314)
评论(0) 编辑 收藏 所属分类:
Soket