参考:《Java 网络编程》
对于接受连接的服务器,Java 提供对了表示服务器socket的ServerSocket类。实质上,服务器socket的任务就是坐在电话旁等电话。从技术上讲,服务器socket运行于服务器,监听入站TCP连接。每个socket服务器监听服务器机器的某个端口。当远程主机的客户端尝试连接此端口是,服务器就被唤醒,协商客户端和服务器之间的连接,并返回一个表示两台主机间socket的正常Socket对象。换句话说,服务器socket等待连接,而客户端socket发起连接。一旦ServerSocket建立起连接,服务器就使用一个正常的Socket对象向客户端发送数据。数据总是通过一般的socket传输。
在Java中,服务器程序的基本生命周期是:
1、使用一个ServerSocket()构造函数在某个端口创建一个新的ServerSocket。
2、ServerSocket使用其accept()方法监听此端口的入站连接。accept()会一直阻塞,知道客户端尝试进行连接,这时accept()将返回一个连接客户端和服务器的Socket对象。
3、根据服务器类型,会调用Socket的getInputStream()方法或getOutputStream()方法,或者这个两个方法都用,已获得与客户端的通信的输入输出流。
4、服务器和客户端根据写上的协议交互,知道关闭连接。
5、服务器或客户端(或二者)关闭连接。
6、服务器返回到步骤2,等待下一次连接。
Java程序应当生成一个线程与客户端交互,这样服务器就能尽快处理下一个连接。
下面的例子:
try {
ServerSocket server = new ServerSocket(5776);
while(true) {
Socket connection = server.accept();
try {
writer out = new OutputStreamWriter(connection.getOutputStream());
out.write(“you’ve connected to this server.bye now \r\n”);
out.flush();
conncetion.close();
} catch (IOException e) {
//这里一般是这个连接的暂时行的错误,如客户端
//过早中断了连接,因此不要终止循环或是显示错误消息。
//但是可以在日志中记录此异常。
} finally {
//在结束时确保socket已关闭。
try {
if (connection != null) {
connection.close();
connection = null;
}
}catch(IOException e) { }
}
} catch (IOException e) {
System.err.println(e);
}
值得注意的是判断一个ServerSocket是否是打开的,必须同时检查isBound()返回true,isClosed()返回false。
public static boolean isOpen(ServerSocket ss) {
return ss.isBound() && !ss.isClose();
}
注意:isClose(),以无参数的ServerSocket()构造函数创建的ServerSocket对象尚未绑定至任何端口,这种对象并不认为是关闭的,isClose()会返回false。
isBound()有一定的误导性。如果ServerSocket曾经绑定至某个端口,即使它目前已经被关闭了,但是isBound()依然会返回true。
例子:简单的HTTP服务器,可以提供一个完整的文档树,包括图片、applet、HTML文件、文本文件等等。是相当轻量的。
主线程启动以后,会把每一个接受的请求放入到连接池中等待处理。有一个RequestProcessor类实例从池中移走连接并处理。
JHTTP是主线程。RequestProcessor处理http请求的线程池。
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class JHTTP extends Thread {
private File documentRootDirectory;
private String indexFileName = "index.html";
private ServerSocket server;
private int numberThreads = 50;
public JHTTP(File documentRootDirectory, int port, String indexFileName) throws IOException {
if (!documentRootDirectory.isDirectory()) {
throw new IOException(documentRootDirectory + " does not exist as a directory");
}
this.documentRootDirectory = documentRootDirectory;
this.indexFileName = indexFileName;
this.server = new ServerSocket(port);
}
public JHTTP(File documentRootDirectory, int port) throws IOException {
this(documentRootDirectory, port, "index.html");
}
public JHTTP(File documentRootDirectory) throws IOException {
this(documentRootDirectory, 80, "index.html");
}
public void run() {
for (int i = 0; i < numberThreads; i++) {
Thread t = new Thread(new RequestProcessor(documentRootDirectory, indexFileName));
t.start();
}
System.out.println("Accepting connections on port " + server.getLocalPort());
System.out.println("Document Root: " + documentRootDirectory);
while (true) {
try {
Socket request = server.accept();
RequestProcessor.processRequest(request);
} catch (IOException e) {
}
}
}
}
RequestProcessor类:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.Socket;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
public class RequestProcessor implements Runnable {
private static List pool = new LinkedList(); //buffer pool
private File documentRootDirectory;
private String indexFileName = "index.html";
public RequestProcessor(File documentRootDirectory, String indexFileName) {
if (documentRootDirectory.isFile()) {
throw new IllegalArgumentException("documentRootDirectory must be a directory, not a file");
}
this.documentRootDirectory = documentRootDirectory;
try {
this.documentRootDirectory = documentRootDirectory.getCanonicalFile();
} catch (IOException e) {
}
if (indexFileName != null) {
this.indexFileName = indexFileName;
}
}
public static void processRequest(Socket request) {
synchronized (pool) { //这里没有设定线程池的大小,只是有50个线程在处理。
pool.add(pool.size(), request);// 这个地方是不是,先让List,wait一下啊,或者说List是在wait状态下是可以add或removed,是吧。没有仔细研究一下
pool.notifyAll(); //重新唤醒List的pool
}
}
@Override
public void run() {
// TODO Auto-generated method stub
//安全性检查
String root = documentRootDirectory.getPath();
Socket connection;
while (true) {
synchronized (pool) { //从缓冲池中取Socket,先判断缓冲池是否为空,如果为空,则等待,当有缓冲池有Socket时取出第一个Socket进行处理。
while (pool.isEmpty()) {
try {
pool.wait();
} catch (InterruptedException ex) {
}
}
connection = (Socket) pool.remove(0);
}
try {
String fileName;
String contentType;
OutputStream raw = new BufferedOutputStream(connection.getOutputStream());
Writer out = new OutputStreamWriter(raw);
Reader in = new InputStreamReader(new BufferedInputStream(connection.getInputStream()), "ASCII");
StringBuffer requestLine = new StringBuffer();
int c;
while (true) {
c = in.read();
if (c == '\r' || c == '\n' )
break;
requestLine.append((char) c);
}
String get = requestLine.toString();
//记录请求的日志
System.out.println("从客户端读取到的数据: " + get);
StringTokenizer st = new StringTokenizer(get);
String mothod = st.nextToken();
String version = "";
if (mothod.equals("GET")) {
fileName = st.nextToken();
if (fileName.endsWith("/"))
fileName += indexFileName;
contentType = guessContentTypeFromName(fileName);
if (st.hasMoreTokens()) {
version = st.nextToken();
}
File theFile = new File(documentRootDirectory, fileName.substring(1, fileName.length()));
if (theFile.canRead() && theFile.getCanonicalPath().startsWith(root)) {
//不让请求超出文档根目录
DataInputStream fis = new DataInputStream(new BufferedInputStream(new FileInputStream(theFile)));
byte[] theData = new byte[(int) theFile.length()];
fis.readFully(theData);
fis.close();
if (version.startsWith("HTTP ")) { //发送MIME首部
out.write("HTTP/1.0 200 OK\r\n");
Date now = new Date();
out.write("Date: " + now + "\r\n");
out.write("Server: JHTTP/1.0\r\n");
out.write("Content-length: " + theData.length + "\r\n");
out.write("Content-type: " + contentType + "\r\n");
out.flush();
}//if end
//发送文件; 可能是图片或是其他的二进制数据
//所以使用底层输出流而不是书写器;
raw.write(theData);
raw.flush();
} //if end
else { //无法找到文件的情况
if (version.startsWith("HTTP ")) { //发送MIME首部
out.write("HTTP/1.0 404 File Not Found\r\n");
Date now = new Date();
out.write("Date: " + now + "\r\n");
out.write("Server: JHTTP/1.0\r\n");
out.write("Content-type: text/html\r\n\r\n");
}
out.write("<HTML>\r\n");
out.write("<HEAD><TITLE>File Not Found</TITLE>\r\n");
out.write("</HEAD\r\n>");
out.write("<BOTY>");
out.write("<H1>HTTP Error 404: File Not Found</H1>\r\n");
out.write("</BODY></HTML>\r\n");
out.flush();
}
} else { //方法不等于GET
if (version.startsWith("HTTP ")) { //发送MIME首部
out.write("HTTP/1.0 501 Not Implemented\r\n");
Date now = new Date();
out.write("Date: " + now + "\r\n");
out.write("Server: JHTTP 1.0\r\n");
out.write("Content-type: text/html\r\n\r\n");
}
out.write("<HTML>\r\n");
out.write("<HEAD><TITLE>Not Implemented</TITLE>\r\n");
out.write("</HEAD>\r\n");
out.write("<BODY>");
out.write("<H1>HTTP Error 501: Not Implemented</H1>\r\n");
out.write("</BODY></HTML>\r\n");
out.flush();
}
} catch (IOException e) {
} finally {
try {
if (connection != null) {
connection.close();
}
connection = null;
} catch (IOException e) {
}
}
}//while end
}// run end
public static String guessContentTypeFromName(String name) {
if (name.endsWith(".html") || name.endsWith(".htm")) {
return "text/html";
}
else if (name.endsWith(".txt") || name.endsWith(".java")) {
return "text/plain";
}
else if (name.endsWith(".gif")) {
return "image/gif";
}
else if (name.endsWith(".class")){
return "application/octet-stream";
}
else if (name.endsWith(".jpg") || name.endsWith(".jpeg")) {
return "image/jpeg";
}
else return "text/plain";
}
}
JHTTPMain执行类:
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
public class JHTTPMain {
public static void main(String[] args) {
//得到文档根
File documentRootDirectory = null;
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
System.out.println("Please input the documentRootDirectory: ");
documentRootDirectory = new File(in.readLine());
} catch (IOException e) {
System.err.println(e);
}
//设置监听端口
int port;
try {
System.out.println("Please input the server's port: ");
port = Integer.parseInt(in.readLine());
if (port < 0 || port > 65535)
port = 80;
} catch (Exception e) {
port = 80;
// System.err.println(e);
}
try {
JHTTP webServer = new JHTTP(documentRootDirectory, port);
webServer.start();
} catch (IOException e) {
System.out.println("Server couldn't start because of an " + e.getClass());
System.out.println(e);
}
}
}