当前位置 : 主页 > 编程语言 > java >

Java网络编程之服务端中的Socket

来源:互联网 收集:自由互联 发布时间:2022-07-14
Java提供了ServerSocket类代表服务端的socket,服务端的socket主要工作是在某个端口上等待tcp连接。当远程的客户端尝试连接服务端,服务端就会被“唤醒”,然后协调处一条与客户端的连接


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

ServerSocket的使用

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

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

    多线程服务器

    我们上面的服务器是单线程的,如果有多个请求连接进来,操作系统会把这些请求存储到一个先进先出队列中,这个队列的默认大小为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;
    }

    @Override
    public 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

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

    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;
    }

    @Override
    public 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

    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

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

    如果主机有多个网卡或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

    通过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;
    }

    @Override
    public 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 need
    StringBuilder 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 header
    if(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

    只要用户访问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 need
    StringBuilder 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 header
    if (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

    访问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;
    }
    @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();
    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 header
    sendHeader(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
    下一篇:没有了
    网友评论