服务器Socket概述与实例

来源:互联网 发布:windows xp硬盘安装版 编辑:程序博客网 时间:2024/06/05 04:54

绝大部分知识与实例来自O’REILLY的《Java网络编程》(Java Network Programming,Fourth Edition,by Elliotte Rusty Harold(O’REILLY))。

ServerSocket简介

ServerSocket类包含了使用Java编写服务器所需的全部内容,其中包括创建新ServerSocket对象的构造函数、在指定端口监听连接的方法、配置各个服务器Socket选项的方法,以及一些常见的方法(如toString)。
在Java中,一个服务器的生命周期如下:

  1. 使用ServerSocket的构造函数在一个特定端口创建一个新的ServerSocket;
  2. ServerSocket使用其accept()方法监听这个端口的入站连接。accept()方法会一直阻塞,直到一个客户端尝试建立连接,此时方法会返回一个连接客户端和服务器的Socket对象;
  3. 根据服务器的类型调用Socket的getInputStream()和getOutputStream()获取输入/输出流;
  4. 服务器和客户端根据协议交互,直到关闭连接;
  5. 服务器或客户端关闭连接;
  6. 服务器回到步骤2,等待下一次连接。

下面看一个简单的服务器,用于获取时间:

实例1:Daytime服务器

public static void createDaytimeServer(){    try(ServerSocket server = new ServerSocket(13)){        while(true){            try(Socket connection = server.accept()){                Writer out = new OutputStreamWriter(                                connection.getOutputStream(),                                "UTF-8");                Date now = new Date();                out.write(now.toString() + "\r\n");                out.flush();            }catch (IOException e) {            }        }    } catch (IOException e) {        System.err.println("服务器意外退出");    }}

启动服务器:

createDaytimeServer();

请求服务器提供服务:

try(Socket socket = new Socket("127.0.0.1", 13)){    BufferedReader in = new BufferedReader(            new InputStreamReader(socket.getInputStream(), "UTF-8"));    System.out.println(in.readLine());} catch (UnknownHostException e) {    e.printStackTrace();} catch (IOException e) {    e.printStackTrace();}输出:Wed Sep 13 09:34:09 CST 2017

注意点有以下几个:

  1. 客户端Socket与服务器Socket使用完毕后都需要调用close()关闭连接,Java 7及之后的版本可以利用try-with-resources实现。
  2. 启动服务器的代码和请求服务器提供服务的代码要分开执行,否则可能得不到输出。
  3. 服务器的代码中有两类异常,一类是某个客户端连接出错时抛出的,一类是服务器出错时抛出的,必须分别处理。
  4. 在同一台机器上测试的话,可以为Socket传入本地回送地址127.0.0.1。

多线程服务器

前面的服务器一次只能处理一个请求,如果有一个很慢的客户端先请求到了服务,会使得后面的客户端阻塞很长时间。一个比较好的方法是为每个连接提供一个线程。

实例2:使用线程池的多线程Daytime服务器

public static void createPooledDaytimeServer(){    ExecutorService pool = Executors.newFixedThreadPool(50);    try(ServerSocket server = new ServerSocket(13)){        while(true){            try {                Socket connection = server.accept();                pool.execute(new DaytimeTask(connection));            } catch (IOException e) {            }        }    } catch (IOException e) {        System.err.println("服务器意外退出");    } }private static class DaytimeTask implements Runnable{    private Socket connection;    DaytimeTask(Socket connection){        this.connection = connection;    }    @Override    public void run(){        try(Writer out = new OutputStreamWriter(                    connection.getOutputStream(),                    "UTF-8")){            Date now = new Date();            out.write(now.toString() + "\r\n");            out.flush();        } catch (UnsupportedEncodingException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        } finally {            try {                connection.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }}

使用方法和输出结果和实例1基本相同。
注意点有以下几个:

  1. 多线程的情况下,客户端Socket的关闭应当由处理这个连接的线程完成,因为服务器不知道应当什么时候关闭。

日志(暂缺)

ServerSocket的其他构造器

public ServerSocket() throws IOExceptionpublic ServerSocket(int port) throws IOExceptionpublic ServerSocket(int port, int backlog)public ServerSocket(int port, int backlog, InetAddress bindAddr)

port指要绑定的端口;如果传入0,系统会自动选择一个可用的端口;
backlog指等待连接队列的最大长度;
bindAddr指一个特定的本地IP地址。默认情况下,如果一个主机有多个网络接口或IP地址,ServerSocket会在每个接口和IP地址上监听。如果设置了bindAddr,那么就只会监听这个地址上的入站连接。
无参构造器在创建对象时不会绑定端口,可以在之后使用bind()方法进行绑定。

性能优先级

利用setPerformancePreferences(int connectionTime,int latency,int bandwidth)可以设置服务器各项性能指标的优先级。例如:setPerformancePreferences(2,1,3)表示最大带宽是最重要的性能,最小延迟最不重要,连接时间居中。

HTTP服务器

构建一个HTTP服务器的关键在于实现HTTP协议,即根据HTTP协议读取客户端的请求报文,并构造出对应的响应报文。下面是一个简单的单文件服务器,它无论接收到什么请求都返回200 OK,并传输一个指定文件。

实例3:单文件HTTP服务器

public class SingleFileHTTPServer {    private final byte[] content;    private final byte[] header;    private final int port = 80;    private final String encoding = "UTF-8";    public SingleFileHTTPServer(String data,String mimeType) {        this(data.getBytes(), mimeType);    }    public SingleFileHTTPServer(byte[] data, String mimeType){        this.content = data;        //响应报文首部        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(10);        try(ServerSocket server = new ServerSocket(port)){            while(true){                try{                    Socket connection = server.accept();                    pool.execute(new HTTPHandler(connection));                }catch (IOException e) {                    e.printStackTrace();                }catch (RuntimeException e) {                    e.printStackTrace();                }            }        } catch (IOException e) {            e.printStackTrace();        }    }    private class HTTPHandler implements Runnable{        private final Socket connection;        HTTPHandler(Socket connection) {            this.connection = connection;        }        @Override        public void run() {            try{                OutputStream out =                         new BufferedOutputStream(connection.getOutputStream());                InputStream in =                        new BufferedInputStream(connection.getInputStream());                StringBuilder request = new StringBuilder(80);                while(true){                    int c = in.read();                    if(c == '\r' || c == '\n' || c == -1){                        break;                    }                    request.append((char)c);                }                System.out.println(request.toString());                if(request.toString().indexOf("HTTP/") != -1){                    out.write(header);                }                out.write(content);                out.flush();            } catch (IOException e) {                e.printStackTrace();            }finally{                try {                    connection.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }    }    //客户端测试用,会保存服务器发来的文件    public static void test(){        try {            URL url = new URL("http://127.0.0.1");            URLConnection connection = url.openConnection();            InputStream in = new BufferedInputStream(connection.getInputStream());            int total = Integer.parseInt(connection.getHeaderField("Content-length"));                  File file = new File("C:\\Users\\qwer\\Desktop"             + File.separator + "123.pdf");            try(FileOutputStream out = new FileOutputStream(file)){                byte[] bytes = new byte[64 * 1024];                int size;                int len = 0;                while(len < total){                    size = in.read(bytes);                    out.write(bytes, 0, size);                    len += size;                    System.out.println(len);                }                out.flush();                System.out.println("finished");            }        } catch (UnknownHostException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }    }    public static void main(String[] args) {        try {            String filepath = "C:\\Users\\qwer\\Desktop\\osu\\漫画数据库.pdf";            Path path = Paths.get(filepath);            byte[] data = Files.readAllBytes(path);            //用于获取文件资源的MIME类型            String contentType = URLConnection.getFileNameMap().getContentTypeFor(filepath);            SingleFileHTTPServer server = new SingleFileHTTPServer(data, contentType);            server.start();        } catch (IOException e) {            e.printStackTrace();        }//      test();    }}
原创粉丝点击