Java网络编程 Ch9 服务器Socket
2018-03-16 09:19
435 查看
基本流程
SO_TIMEOUT 0永不超时
////在指定端口新建ServerSocket try (ServerSocket server = new ServerSocket(1024)){ while (true) { //accept阻塞调用,一直等待 try (Socket connection = server.accept()) { Writer out = new OutputStreamWriter(connection.getOutputStream()); Date now = new Date(); //回车换行对结束。不能用System.getProperty("line.separator")或者sout out.write(now.toString()+"\r\n"); //刷新输出 out.flush(); //try-with-resource会自动关闭connection,否则需要在finally'中关闭 connection.close(); }catch (IOException ex){ //仅关闭该连接,不影响服务器 } }catch (IOException ex){ //关闭服务器 System.err.println(); } }
多线程服务器
package Ch8; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.Date; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class PooledDaytimeServer { public final static int PORT = 13; public static void main(String[] args) { //创建size为50的线程池,避免大量创建线程导致资源耗尽。 ExecutorService pool = Executors.newFixedThreadPool(50); try(ServerSocket server = new ServerSocket(PORT)){ while (true){ //这里不适用try-with-resource是因为,到达while末尾时,会自动关闭Socket,而此时该线程可能还没使用完Socket。 try{ Socket connection = server.accept(); //将事件包装到新线程中。 Callable<Void> task = new DayTimeTask(connection); //将Callable提交给executor。任务队列。与Thread子类启动线程不同。 pool.submit(task); }catch (IOException ex){} } }catch (IOException ex){ System.err.println("Couldn't start server"); } } private static class DayTimeTask implements Callable<Void>{ private Socket connection; DayTimeTask(Socket connection){ this.connection = connection; } @Override public Void call(){ try{ Writer out = new OutputStreamWriter(connection.getOutputStream()); Date now = new Date(); out.write(now.toString()+"\r\n"); out.flush(); }catch (IOException ex){ System.err.println(ex); }finally { try{ connection.close(); }catch (IOException ex){} } return null; } } }
检查ServerSocket是否打开
public static boolean isOpen(ServerSocket ss){ return ss.isBound()&&!ss.isClosed(); }
日志???
构造服务器Socket
java public ServerSocket() throws BindException, IOException public ServerSocket(int port) throws BindException, IOException public ServerSocket(int port,int queueLength) throws BindException, IOException //端口号,入站连接最大数,特定本地IP地址 public ServerSocket(int port,int queueLength, InetAddress bindAddress) throws BindException, IOException //无法创建并绑定到指定端口,抛出BindException。 //端口已经被使用IOException
构造但不绑定端口
无参构造器,使用public void bind(SocketAddress endpoint) public void bind(SocketAddress endpoint, int queueLength) throws IOException
获取属性
Socket选项
在accept()之前设置SO_TIMEOUT 0永不超时
public void setSoTimeout(int timeout) getSoTimeout()
SO_REUSEADDR
SO_RCVBUF服务类型
public void setPerformancePreference(int connectionTime, int latency, int bandwith ) //(2,1,3)表示延迟最不重要,带宽最重要
HTTP服务器???
单文件服务器
/*********** 4000 *******************************服务器*****************************************/ package Ch8; import java.io.*; import java.net.*; import java.nio.charset.Charset; import java.nio.file.*; import java.util.concurrent.*; import java.util.logging.*; public class SingleFileHTTPServer { private static final Logger logger = Logger.getLogger("SingleFileHTTPServer"); private final byte[] content; private final byte[] header; private final int port; private final String encoding; //构造函数 public SingleFileHTTPServer(String data, String encoding, String mimeType, int port) throws UnsupportedEncodingException{ this(data.getBytes(encoding),encoding,mimeType,port); } public SingleFileHTTPServer(byte[] data, String encoding, String mimeType, int port){ this.content = data; this.port = port; this.encoding = encoding; String header = "HTTP/1.0 200 OK\r\n" +"Server: OneFile 2.0\r\n" +"Content-length: "+this.content.length+"\r\n" +"Content-type: "+mimeType+"; charset="+encoding+"\r\n\r\n"; this.header = header.getBytes(Charset.forName("US-ASCII")); } // 服务器处理区 public void start(){ ExecutorService pool = Executors.newFixedThreadPool(100); try(ServerSocket server = new ServerSocket(this.port)){ logger.info("Accepting connections on port "+server.getLocalPort()); logger.info("Data to be sent:"); logger.info(new String(this.content, encoding)); while(true){ try{ Socket connection = server.accept(); pool.submit(new HTTPHandler(connection)); }catch (IOException ex){ logger.log(Level.WARNING, "Exception accepting connection",ex); }catch (RuntimeException ex){ logger.log(Level.SEVERE, "Unexpected error",ex);} } }catch (IOException ex){ logger.log(Level.SEVERE,"Could not start server",ex); } } private class HTTPHandler implements Callable<Void> { private final Socket connection; HTTPHandler(Socket connection){ this.connection = connection; } @Override public Void call() throws IOException{ try { OutputStream out = new BufferedOutputStream(connection.getOutputStream()); InputStream in = new BufferedInputStream(connection.getInputStream()); StringBuilder request = new StringBuilder(80); while (true) { //如果没有输入,in会一直等待,阻塞。Connection没有关闭,Client有可能再发送消息。或者由于网络延迟,丢包。流暂时不能结束。 int c = in.read(); if (c == '\r' || c == '\n' || c == -1) break; request.append((char) c); } if (request.toString().indexOf("HTTP/") != -1) { System.out.println("YES"); out.write(header); } //因为finally中将connection关闭了,所以client的reader能读到末尾。 out.write(content); /*Writer out1 = new OutputStreamWriter(out); out1.write("\r\n");*/ out.flush(); }catch (IOException ex){ logger.log(Level.WARNING,"Error writing to client",ex); }finally { connection.close(); } return null; } } //完成服务器资源配置,属性设置,以及启动服务器。 public static void main(String[] args) { int port; try{ port = Integer.parseInt(args[1]); if(port<1||port>65535) port = 80; }catch (RuntimeException ex){ port = 80; } String encoding = "UTF-8"; if(args.length>2) encoding = args[2]; try{ Path path = Paths.get(args[0]); byte[] data = Files.readAllBytes(path); String contentType = URLConnection.getFileNameMap().getContentTypeFor(args[0]); SingleFileHTTPServer server = new SingleFileHTTPServer(data, encoding, contentType, port); server.start(); }catch (ArrayIndexOutOfBoundsException ex){ System.out.println("Usage: java SingleFileHTTPServer filename port encoding"); }catch (IOException ex){ logger.severe(ex.getMessage()); } } } /*************************************客户端************************************************/ package Ch8; import java.io.*; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; public class TryGet { public static void main(String[] args) { Socket socket = new Socket(); try { SocketAddress address = new InetSocketAddress("localhost", 80); socket.connect(address,10); InputStream in = socket.getInputStream(); InputStreamReader reader = new InputStreamReader(in,"UTF-8"); OutputStream out = socket.getOutputStream(); OutputStreamWriter writer = new OutputStreamWriter(out); //要加\r\n的原因是,HTTP规范决定了用\r\n换行。 /*也有效 OutputStream out = new BufferedOutputStream(socket.getOutputStream()); out.write("HTTP1.1\r\n".getBytes("UTF-8")); out.flush();*/ //不加\r\n,不会有结果。 writer.write("HTTP/1.1\r\n"); //不刷新缓存内容也不会有结果, writer.flush(); String result = ""; int tem; while((tem=reader.read())!=-1){ result+=(char)tem; } System.out.println(result); }catch (IOException ex){ System.out.println("err"); } } }
功能完备的HTTP服务器
/***********************************服务器启动***********************************************/ package Ch8; import java.io.*; import java.net.*; import java.util.concurrent.*; import java.util.logging.*; public class JHTTP { //创建JHTTP的日志记录器 private static final Logger logger = Logger.getLogger(JHTTP.class.getCanonicalName()); private static final int NUM_THREADS = 50; private static final String INDEX_FILE = "index.html"; private final File rootDirectory; private final int port; public JHTTP(File rootDirectory, int port) throws IOException{ //判断是否是目录 if(!rootDirectory.isDirectory()){ throw new IOException(rootDirectory+" does not exist as a directory"); } this.rootDirectory = rootDirectory; this.port = port; } public void start() throws IOException{ ExecutorService pool = Executors.newFixedThreadPool(NUM_THREADS); try(ServerSocket server = new ServerSocket(port)){ //便于记录多个线程时,每个线程进入的端口。 logger.info("Accepting connections on port "+server.getLocalPort()); logger.info("Document Root: "+rootDirectory); while(true){ try{ Socket request = server.accept(); Runnable r = new RequestProcessor(rootDirectory,INDEX_FILE,request); pool.submit(r); }catch (IOException ex){ logger.log(Level.WARNING,"Error" + " accepting connection",ex); } } } } public static void main(String[] args) { File docroot; try{ docroot = new File(args[0]); }catch (ArrayIndexOutOfBoundsException ex){ System.out.println("Usage: java JHTTP docroot port"); return; } int port; try{ port = Integer.parseInt(args[1]); if(port<0||port>65535) port = 80; }catch (RuntimeException ex){ port =80; } try{ JHTTP webserver = new JHTTP(docroot,port); webserver.start(); }catch (IOException ex){ logger.log(Level.SEVERE,"Server cound not start",ex); } } } /***********************************服务器业务逻辑****************************************/ package Ch8; import java.net.*; import java.nio.file.Files; import java.util.*; import java.io.*; import java.util.logging.*; public class RequestProcessor implements Runnable { private final static Logger logger=Logger.getLogger(RequestProcessor.class.getCanonicalName()); private File rootDirectory; private String indexFileName = "index.html"; private Socket connection; //构造函数,将Server的参数传入。 public RequestProcessor(File rootDirectory, String indexFileName, Socket connection){ if(rootDirectory.isFile()) {throw new IllegalArgumentException("rootDirectory must be a dirctory, not a file"); } try{ rootDirectory = rootDirectory.getCanonicalFile(); }catch (IOException ex){ } this.rootDirectory = rootDirectory; if(indexFileName!=null) this.indexFileName = indexFileName; this.connection = connection; } @Override public void run(){ String root = rootDirectory.getPath(); try{ OutputStream raw = new BufferedOutputStream(connection.getOutputStream()); Writer out = new OutputStreamWriter(raw); //字节转为字符时,编码和解码字符集一致,才能正确。 Reader in = new InputStreamReader(new BufferedInputStream(connection.getInputStream()),"UTF-8"); StringBuilder requestLine = new StringBuilder(); //读取第一行 while(true){ int c = in.read(); if(c=='\r'||c=='\n') break; requestLine.append((char)c); } String get = requestLine.toString(); // System.out.println(get); logger.info(connection.getRemoteSocketAddress()+" "+get); //处理首行。 方法 URI 版本。 //使用空格来划分,所以文件名不能含空格,且注意文件名的编码格式保持一致。 String[] tokens = get.split("\\s+"); String method = tokens[0]; String version = ""; if(method.equals("GET")){ String fileName = tokens[1]; if(fileName.endsWith("/")) fileName+=indexFileName; //获取文件内容格式 String contentType = URLConnection.getFileNameMap().getContentTypeFor(fileName); if(tokens.length>2) version = tokens[2]; //去掉/ File theFile = new File(rootDirectory, fileName.substring(1,fileName.length())); if(theFile.canRead()&&theFile.getCanonicalPath().startsWith(root)){ //为什么要转为Path,path和file区别 byte[] theData = Files.readAllBytes(theFile.toPath()); //发送headers if(version.startsWith("HTTP/")){ //参数提供内容,seadHeader提供格式。 sendHeader(out,"HTTP/1.0 200 ok",contentType,theData.length); } //theData是字节数组 raw.write(theData); raw.flush(); }else { String body = new StringBuilder("<HTML>\r\n"). append("<HEAD><TITLE>File not Found</TITLE>\r\n"). append("</HEAD>\r\n"). append("<BODY>") .append("<H1>HTTP Error 404: File Not Found</H1>\r\n") .append("</BODY></HTML>\r\n").toString(); if(version.startsWith("HTTP/")){ sendHeader(out,"HTTP/1.0 404 File Not Found","text/html;charset=uft-8",body.length()); } out.write(body); out.flush(); } }else{ String body = new StringBuilder("<HTML>\r\n"). append("<HEAD><TITLE>Not Implemented</TITLE>\r\n"). append("</HEAD>\r\n"). append("<BODY>") .append("<H1>HTTP Error 501: Not Implemented</H1>\r\n") .append("</BODY></HTML>\r\n").toString(); if(version.startsWith("HTTP/")){ sendHeader(out,"HTTP/1.0 501 Not Implemented","text/html;charset=uft-8",body.length()); } out.write(body); out.flush(); } }catch(IOException ex){ logger.log(Level.WARNING,"Error talking to "+connection.getRemoteSocketAddress(),ex); }finally { try{ connection.close(); }catch (IOException ex){} } } private void sendHeader(Writer out, String responseCode,String contentType, int length) throws IOException{ out.write(responseCode+"\r\n"); Date now = new Date(); out.write("Date: "+now+"\r\n"); out.write("Server: JHTTP 2.0\r\n"); out.write("Content-length: "+length+"\r\n"); out.write("Content-type: "+contentType+"\r\n\r\n"); out.flush(); } } /***********************************客户端请求*****************************************/ package Ch8; import java.io.*; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; public class TryGet { public static void main(String[] args) { Socket socket = new Socket(); try { SocketAddress address = new InetSocketAddress("localhost", 80); socket.connect(address,1000); InputStream in = socket.getInputStream(); //在读取输入流时,设置字符集,才可以正确解码。 InputStreamReader reader = new InputStreamReader(new BufferedInputStream(in),"UTF-8"); OutputStream out = new BufferedOutputStream(socket.getOutputStream()); out.write("GET /Ch3线程.md HTTP/1.1 \r\n".getBytes("UTF-8")); out.flush(); String result = ""; int tem; while((tem=reader.read())!=-1){ result+=(char)tem; } System.out.println(result); }catch (IOException ex){ System.out.println("err"); } } }
相关文章推荐
- java网络socket编程(五)之Socket扩展2--实现重定向服务器
- Java 网络编程 服务器Socket
- Java网络编程ServerSocket的实现服务器与用户之间的通信的基本步骤
- Java网络编程 服务器Socket
- java网络编程----------Socket实现客户端和服务器的连接
- Java网络编程学习笔记(六)服务器Socket
- java网络socket编程(四)之Socket扩展1--实现单文件服务器
- Java网络编程——9.服务器Socket
- Java网络编程精解之ServerSocket用法详解一2
- 基于Socket的Java网络编程集粹
- 菜猪的JAVA 网络编程学习之Socket用法详解(上)
- Java网络编程从入门到精通(19):套接字(Socket)的异常
- 基于Socket的Java网络编程集粹-Java基础-Java-编程开发
- 利用Socket进行Java网络编程
- 基于Socket的Java网络编程集粹
- 基于Socket的Java网络编程集粹
- Socket的java网络编程原理
- 网络编程--简单实现javaftp服务器
- Java网络编程精解之ServerSocket用法详解三