Java 网络编程 服务器Socket

参考:《Java 网络编程》

对于接受连接的服务器,Java 提供对了表示服务器socketServerSocket类。实质上,服务器socket的任务就是坐在电话旁等电话。从技术上讲,服务器socket运行于服务器,监听入站TCP连接。每个socket服务器监听服务器机器的某个端口。当远程主机的客户端尝试连接此端口是,服务器就被唤醒,协商客户端和服务器之间的连接,并返回一个表示两台主机间socket的正常Socket对象。换句话说,服务器socket等待连接,而客户端socket发起连接。一旦ServerSocket建立起连接,服务器就使用一个正常的Socket对象向客户端发送数据。数据总是通过一般的socket传输。

Java中,服务器程序的基本生命周期是:

1、使用一个ServerSocket()构造函数在某个端口创建一个新的ServerSocket

2、ServerSocket使用其accept()方法监听此端口的入站连接。accept()会一直阻塞,知道客户端尝试进行连接,这时accept()将返回一个连接客户端和服务器的Socket对象。

3、根据服务器类型,会调用SocketgetInputStream()方法或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()返回trueisClosed()返回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);
		}
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值