Java提供了ServerSocket类代表服务端的socket,服务端的socket主要工作是在某个端口上等待tcp连接。当远程的客户端尝试连接服务端,服务端就会被“唤醒”,然后协调处一条与客户端的连接并返回一个代表客户端和服务器之间的套接字的Socket对象。

ServerSocket的使用

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

  1. 使用ServerSocket()构造函数在某个端口上创建新的ServerSocket对象。
  2. ServerSocket通过accept()方法监听在该端口上的连接请求。accept()是个阻塞方法,它会一直阻塞直到有客户端尝试建立连接,然后返回一个连接客户和服务器的Socket对象。
  3. 根据服务器的类型,可以使用Socket的getInputStream()方法,getOutputStream()方法来获取与客户端进行通信的输入和输出流。
  4. 服务器和客户端根据约定的协议进行通信,直到连接关闭。
  5. 服务器和客户端都可以关闭连接。
  6. 服务器返回到步骤2并等待下一次连接。

还记得上一篇文章中用到的daytime服务器吗?下面我们实现自己的daytime服务器:

public class MyDaytiemServer {public final static int PORT = 13;public static void main(String[] args) throws IOException {try (ServerSocket server = new ServerSocket(PORT)) {System.out.println("Listening on port 13");while (true) {try (Socket connection = server.accept()) {System.out.println("A request come in...");Writer out = new OutputStreamWriter(connection.getOutputStream());Date now = new Date();out.write(now.toString() + "\r\n");out.flush();} catch (IOException e) {}}}catch (IOException ex) {System.err.println(ex);}}
}

多线程服务器

我们上面的服务器是单线程的,如果有多个请求连接进来,操作系统会把这些请求存储到一个先进先出队列中,这个队列的默认大小为50。一旦这个队列满了,服务器就会拒绝额外的连接请求直到队列不是满的状态。

因此,合理的做法是把每个连接放到一个独立的线程中去处理:

public class MyDaytiemServer {public final static int PORT = 13;public static void main(String[] args) throws IOException {try (ServerSocket server = new ServerSocket(PORT)) {System.out.println("Listening on port 13");while (true) {try{Socket connection = server.accept();Thread t = new DaytimeThread(connection);t.start();} catch (IOException e) {}}}catch (IOException ex) {System.err.println(ex);}}private static class DaytimeThread extends Thread{private Socket connection;public DaytimeThread(Socket connection){this.connection = connection;}@Overridepublic void run() {try {System.out.println("A request come in...");Writer out = new OutputStreamWriter(connection.getOutputStream());Date now = new Date();out.write(now.toString() + "\r\n");out.flush();}catch (IOException ex){System.out.println(ex);}finally {try{connection.close();}catch (Exception e){}}}}
}

好了,现在是多线程了,可以处理并发请求了。但是,服务器能创建的线程数量是有限的,如果同时涌入了大量的请求,也会造成服务器崩溃。因此,考虑使用固定大小的线程池:

public class MyDaytiemServer {public final static int PORT = 13;public static void main(String[] args) throws IOException {ExecutorService pool = Executors.newFixedThreadPool(50);try (ServerSocket server = new ServerSocket(PORT)) {System.out.println("Listening on port 13");while (true) {try{Socket connection = server.accept();Runnable task = new DaytimeThread(connection);pool.submit(task);} catch (IOException e) {}}}catch (IOException ex) {System.err.println(ex);}}private static class DaytimeThread implements Runnable{private Socket connection;public DaytimeThread(Socket connection){this.connection = connection;}@Overridepublic void run() {try {System.out.println("A request come in...");Writer out = new OutputStreamWriter(connection.getOutputStream());Date now = new Date();out.write(now.toString() + "\r\n");out.flush();}catch (IOException ex){System.out.println(ex);}finally {try{connection.close();}catch (Exception e){}}}}
}

ServerSocket的构造函数

public ServerSocket(int port) throws BindException, IOException
public ServerSocket(int port, int queueLength) throws BindException, IOException
public ServerSocket(int port, int queueLength, InetAddress bindAddress) throws IOException
public ServerSocket() throws IOException

这些构造函数指定了端口,存储请求的队列大小,本地绑定的地址(对应不同的网卡)

如果主机有多个网卡或IP地址,在默认情况下,服务端socket会在所有的网卡和地址上监听连接请求。如果你想只绑定某个网卡或IP,那么你就可以使用第三个构造函数来指定。

获取ServerSocket的相关信息

public InetAddress getInetAddress()
public int getLocalPort()

如果本机有多个IP地址,getInetAddress会返回其中一个,你无法预测返回的是哪个。

 try {ServerSocket server = new ServerSocket(0);System.out.println("This server runs on port "+ server.getLocalPort());} catch (IOException ex) {System.err.println(ex);}

通过getLocalPort()方法可以得知是在哪个端口上监听。

自己动手实现HTTP服务器

文件服务器

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;}@Overridepublic Void call() throws Exception {try {OutputStream out = new BufferedOutputStream(connection.getOutputStream());InputStream in = new BufferedInputStream(connection.getInputStream());// read the first line only; that's all we needStringBuilder request = new StringBuilder(80);while (true) {int c = in.read();if(c == '\r' || c == '\n' || c == -1) break;;request.append((char)c);}logger.log(Level.INFO,"Request is: " + request);// IF this is HTTP/1.0 or later send a MIME headerif(request.toString().indexOf("HTTP/") != -1) {out.write(header);}out.write(content);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 = 80;String encoding = "UTF-8";String pathStr = "D:\\workspace\\js\\AllBranchs\\5\\ha-web\\coverage\\index.html";try {Path path = Paths.get(pathStr);byte[] data = Files.readAllBytes(path);String contentType = URLConnection.getFileNameMap().getContentTypeFor(pathStr);SingleFileHTTPServer server = new SingleFileHTTPServer(data, encoding,contentType, port);server.start();} catch (IOException ex) {logger.severe(ex.getMessage());}}
}

只要用户访问localhost,就给他返回一个固定的文件。

重定向服务器

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.logging.*;
/*** Create by yinjingwei on 2018/6/22/022.*/
public class Redirector {private static final Logger logger = Logger.getLogger("Redirector");private final int port;private final String newSite;public Redirector(String newSite, int port) {this.port = port;this.newSite = newSite;}public void start() {try (ServerSocket server = new ServerSocket(port)) {logger.info("Redirecting connections on port "+ server.getLocalPort() + " to " + newSite);while (true) {try {Socket s = server.accept();Thread t = new RedirectThread(s);t.start();} catch (IOException ex) {logger.warning("Exception accepting connection");} catch (RuntimeException ex) {logger.log(Level.SEVERE, "Unexpected error", ex);}}} catch (BindException ex) {logger.log(Level.SEVERE, "Could not start server.", ex);} catch (IOException ex) {logger.log(Level.SEVERE, "Error opening server socket", ex);}}private class RedirectThread extends Thread {private final Socket connection;RedirectThread(Socket s) {this.connection = s;}public void run() {try {Writer out = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream(), "US-ASCII"));Reader in = new InputStreamReader(new BufferedInputStream(connection.getInputStream()));// read the first line only; that's all we needStringBuilder request = new StringBuilder(80);while (true) {int c = in.read();if (c == '\r' || c == '\n' || c == -1) break;request.append((char) c);}String get = request.toString();String[] pieces = get.split("\\w*");String theFile = pieces[1];// If this is HTTP/1.0 or later send a MIME headerif (get.indexOf("HTTP") != -1) {out.write("HTTP/1.0 302 FOUND\r\n"); //302重定向Date now = new Date();out.write("Date: " + now + "\r\n");out.write("Server: Redirector 1.1\r\n");out.write("Location: " + newSite + theFile + "\r\n");out.write("Content-type: text/html\r\n\r\n");out.flush();}// Not all browsers support redirection so we need to// produce HTML that says where the document has moved to.out.write("<HTML><HEAD><TITLE>Document moved</TITLE></HEAD>\r\n");out.write("<BODY><H1>Document moved</H1>\r\n");out.write("The document " + theFile+ " has moved to\r\n<A HREF=\"" + newSite + theFile + "\">"+ newSite + theFile+ "</A>.\r\n Please update your bookmarks<P>");out.write("</BODY></HTML>\r\n");out.flush();logger.log(Level.INFO,"Redirected " + connection.getRemoteSocketAddress());} catch(IOException ex) {logger.log(Level.WARNING,"Error talking to " + connection.getRemoteSocketAddress(), ex);} finally {try {connection.close();} catch (IOException ex) {}}}}public static void main(String[] args) {int thePort = 80;String theSite = "http://www.baidu.com";Redirector redirector = new Redirector(theSite, thePort);redirector.start();}
}

访问localhost会重定向到百度。

特定用途的HTTP服务器就举例到这里了,接下来是压轴大戏,一个功能基本完整的HTTP服务器,能访问整个网站目录,包括html,js,css等。

五脏俱全的HTTP服务器

麻雀虽小,五脏俱全。

import java.io.*;
import java.net.*;
import java.nio.file.Files;
import java.util.Date;
import java.util.concurrent.*;
import java.util.logging.*;
/*** Create by yinjingwei on 2018/6/22/022.*/
public class 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 + " dose not exist as a dirctory");}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 = 8888;}catch (RuntimeException ex){port = 8888;}try {JHTTP webserver = new JHTTP(docroot, port);webserver.start();} catch (IOException ex) {logger.log(Level.SEVERE, "Server could not start", ex);}}}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;public RequestProcessor(File rootDirectory,String indexFileName,Socket connection) {if (rootDirectory.isFile()) {throw new IllegalArgumentException("rootDirectory must be a directory, not a file");}try {rootDirectory = rootDirectory.getCanonicalFile();}catch (IOException ex){}this.rootDirectory = rootDirectory;if (indexFileName != null) this.indexFileName = indexFileName;this.connection = connection;}@Overridepublic 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();logger.info(connection.getRemoteSocketAddress() + " " + get);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 (fileName.endsWith(".css")) {contentType = "text/css"; // 修复css无法解析的问题}if (tokens.length >2) {version = tokens[2];}File theFile = new File(rootDirectory,fileName.substring(1,fileName.length()));if (theFile.canRead()// 禁止访问根目录之外的文件&& theFile.getCanonicalPath().startsWith(root)) {byte [] theData = Files.readAllBytes(theFile.toPath());if (version.startsWith("HTTP/")) {sendHeader(out,"HTTP/1.0 200 OK",contentType,theData.length);}// 发送文件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=utf-8", body.length());}out.write(body);out.flush();}} else { // method dose not equal "GET"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/")) { // send a MIME headersendHeader(out, "HTTP/1.0 501 Not Implemented","text/html; charset=utf-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();}
}

这个服务器暂时只能处理get请求,但是用作静态网页的服务器已经足够了。
由于css文件getContentTypeFor会解析为text/plain。导致css样式无法解析,可通过下面的代码解析:

 if (fileName.endsWith(".css")) {contentType = "text/css"; // 修复css无法解析的问题}

Java网络编程之服务端中的Socket相关推荐

  1. Qt:Qt实现Winsock网络编程—TCP服务端和客户端通信(多线程)

    Qt实现Winsock网络编程-TCP服务端和客户端通信(多线程) 前言 感觉Winsock网络编程的api其实和Linux下网络编程的api非常像,其实和其他编程语言的网络编程都差不太多.博主用Qt ...

  2. QT网络编程开发服务端

    下一篇: QT网络编程开发客户端 文章目录 基于Qt的网络编程服务端 QTcpServer 配置 listen() close() newConnection() SINGL readyRead() ...

  3. java网络编程作业基于UDP简单聊天窗口,图形化界面,包含客户端和服务端

    //郑州轻工业大学 //题号:实验四 第二题 //题目:使用基于UDP的网络编程方法,完成客户端和服务器间的聊天功能.要求图形界面. java网络编程作业 基于UDP简单聊天窗口,图形化界面,包含客户 ...

  4. java 网络编程(二) tcp传输实现客户端和服务端进行信息交流

    1.使用Tcp从一台电脑往另一台电脑上发送文本数据 客户端: import java.io.*; import java.net.*; /**** 客户端,* 通过查阅socket对象,发现在该对象建 ...

  5. 【Java 网络编程】网络通信原理、TCP、UDP 回显服务

    一.网络发展历史 互联网从何而来? 这要追溯到上个世纪 50 - 60 年代,当时正逢美苏争霸冷战,核武器给战争双方提供了足够的威慑力,想要保全自己,就要保证自己的反制手段是有效的. 如何保证能够反击 ...

  6. 用java网络编程中的TCP方式上传文本文件及出现的小问题

    自己今天刚学java网络编程中的TCP传输,要用TCP传输文件时,自己也是遇到了一些问题,抽空把它整理了一下,供自己以后参考使用. 首先在这个程序中,我用一个客户端,一个服务端,从客户端上传一个文本文 ...

  7. 【Java网络编程与IO流】Java中IO流分为几种?字符流、字节流、缓冲流、输入流、输出流、节点流、处理流

    Java网络编程与IO流目录: [Java网络编程与IO流]Java中IO流分为几种?字符流.字节流.缓冲流.输入流.输出流.节点流.处理流 [Java网络编程与IO流]计算机网络常见面试题高频核心考 ...

  8. java 密钥工厂 desede_20145212 实验五《Java网络编程》

    20145212 实验五<Java网络编程> 一.实验内容1.运行下载的TCP代码,结对进行,一人服务器,一人客户端: 2.利用加解密代码包,编译运行代码,一人加密,一人解密: 3.集成代 ...

  9. Java - 网络编程(NetWork)

    Java - 网络编程(NetWork) 一.java.net包下的 InetAddress 类的使用: > 一个 InetAddress 代表着一个IP地址 > 主要用来查看本机的IP和 ...

  10. 【Java】 Java网络编程总结

     一.网络编程三要素: IP地址:每个设备在网络中的唯一标识. 端口号:每个程序在设备上的唯一标识. 协议:为计算机网络中进行数据交换而建立的规则或约定的集合. UDP: 面向无连接,数据不安全,速度 ...

最新文章

  1. jwt token 附加用户信息_SpringBoot+JWT实现token验证并将用户信息存储到@注解内
  2. windows 程序设计_windows程序设计基础(第二章)——2.5 第一个代码实例
  3. Binary Tree Level Order Traversal
  4. shiro与springMVC整合
  5. 浅析VS2010反汇编 VS 反汇编方法及常用汇编指令介绍 VS2015使用技巧 调试-反汇编 查看C语言代码对应的汇编代码...
  6. Python如何读取文件
  7. java struts2 ajax_在struts2的Action中返回Ajax数据
  8. URL转微信可识别的二维码
  9. mysql jdbc 单利_java单利模式设计
  10. ACR122U写卡软件v3.6绿色版
  11. LeetCode简单题643.子数组的最大平均数I
  12. 激活 出现无法访问制定设备,路径或文件。您可能没有合适的权
  13. linux下iso文件的制做和解压
  14. 【Java中继承总结与练习 】
  15. Linux服务器查看带宽,进程和线程
  16. 06oracle视图与物化视图
  17. 服务器查询IPV6地址
  18. 就业难!如何“解救”2022年的应届毕业生......
  19. 校招linux面试题,2013华为校招机试与面试题整理
  20. 小程序引用阿里iconfont

热门文章

  1. 【mfc】vs2019创建MFC基于对话框的项目没有插入任何代码运行报错:0xC0000005:读取位置 0xFFFFFFFFFFFFFFFF 时发生访问冲突
  2. 总结51单片机避免复位触发的几种方法
  3. BZOJ1938: [CROATIAN2010] ALADIN
  4. 第二阶段冲刺第六天站立会议
  5. AutoMapper不用任何配置就可以从dynamic(动态)对象映射或映射到dynamic对象。
  6. uva 1331 - Minimax Triangulation(dp)
  7. cookie与Session的区别,以及jQuery怎样操作cookie和Session
  8. [C入门 - 游戏编程系列] 贪吃蛇篇(一) - 世界定义
  9. VS+VSS代码管理
  10. 数学趣题——猴子吃桃问题