zoukankan      html  css  js  c++  java
  • JAVA网络编程-服务器Socket

    ServerSocket

    使用ServerSocket
    处理服务端异常
    阻塞
    服务端队列
    构造但不绑定端口
    随机端口

    Socket选项
    服务器第一版
    服务器第二版(重定向服务器)

    ServerSocket

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

    使用ServerSocket

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

    1 使用一个ServerSocket()构造函数在一个特定端口创建一个新的ServerSocket.

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

    3 调用Socket的getInputStream()和getOutputStream()方法,获得与客户端通信的输入和输出流.

    4 服务器和客户端根据已协商的协议进行交互,直到要关闭连接.

    5 服务器或客户端(或二者)关闭连接.

    6 服务器返回到步骤2,等待下一次连接.

    public static void main(String[] args)throws Exception {
            ServerSocket server = new ServerSocket(13);
            while (true) {
                Socket socket = server.accept();
                OutputStream os = socket.getOutputStream();
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
                bw.append("success");
                bw.newLine();
                bw.flush();
                socket.close();
            }
        }//服务端
    public static void main(String[] args) throws Exception{
            Socket socket = new Socket("127.0.0.1",13);
            InputStream in = socket.getInputStream();
            BufferedReader bw = new BufferedReader(new InputStreamReader(in));
            String s = null;
            while ((s=bw.readLine())!=null){
                System.out.println("接收的数据"+s);
            }
            socket.close();
        }//客户端

    处理服务端异常

    服务端异常处理时,代码会变得有些复杂.有两类异常,一类异常可能关闭服务器并记录一个错误信息,另一类异常只会关闭活动连接,区分这两类异常非常重要.

    public static void main(String[] args) {
            ServerSocket server = null;
            try {
                server = new ServerSocket(13);
                while (true) {
                    Socket socket = null;
                    try {
                        socket = server.accept();
                        OutputStream os = socket.getOutputStream();
                        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
                        bw.append("success");
                        bw.newLine();
                        bw.flush();
                        socket.close();
                    } catch (Exception e) {
                        System.out.println("连接关闭");
                    } finally {
                        if (socket != null) {
                            try {
                                socket.close();
                            } catch (Exception e) {
                            }
                        }
                    }
                }
            } catch (Exception e) {
                System.out.println("服务器关闭了");
            } finally {
                if (server != null) {
                    try {
                        server.close();
                    } catch (Exception e) {
                    }
                }
            }
        }

    阻塞

     1 public static void main(String[] args) {
     2         ServerSocket server = null;
     3         try {
     4             server = new ServerSocket(13);
     5             while (true) {
     6                 Socket socket = null;
     7                 try {
     8                     socket = server.accept();
     9                     OutputStream os = socket.getOutputStream();
    10                     System.out.println("正在发送数据");
    11                     BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
    12                     bw.append("success");
    13                     bw.newLine();
    14                     bw.flush();
    15 
    16                     socket.shutdownOutput();
    17 
    18                     System.out.println("正在接收数据");
    19                     InputStream in = socket.getInputStream();
    20                     BufferedReader br = new BufferedReader(new InputStreamReader(in));
    21                     System.out.println(br.readLine());
    22 
    23                 } catch (Exception e) {
    24                     System.out.println("连接关闭");
    25                 } finally {
    26                     if (socket != null) {
    27                         try {
    28                             socket.close();
    29                         } catch (Exception e) {
    30                         }
    31                     }
    32                 }
    33             }
    34         } catch (Exception e) {
    35             System.out.println("服务器关闭了");
    36         } finally {
    37             if (server != null) {
    38                 try {
    39                     server.close();
    40                 } catch (Exception e) {
    41                 }
    42             }
    43         }
    44     }//服务端
     1 public static void main(String[] args) throws Exception{
     2         Socket socket = new Socket("127.0.0.1",13);
     3         InputStream in = socket.getInputStream();
     4         System.out.println("正在接收数据");
     5         BufferedReader br = new BufferedReader(new InputStreamReader(in));
     6         String s = null;
     7         while ((s=br.readLine())!=null){
     8             System.out.println("接收的数据"+s);
     9         }
    10 
    11 
    12         System.out.println("正在发送数据");
    13         OutputStream out = socket.getOutputStream();
    14         BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
    15         bw.write("ddddddddd");
    16         bw.flush();
    17 
    18         socket.shutdownOutput();
    19     }//客户端

    把服务端第16行注释掉,服务端和服务端的打印结果为

    //这是服务端打印
    正在发送数据 正在接收数据
    //这是客户端打印
    正在接收数据 接收的数据success

    这种结果并不完全反映在打印上,其实此时客户端一直阻塞着,并未结束.原因是服务端写入数据后没有关闭输出流,客户端还在等待读取数据.这种情况会把客户端与服务端的连接一直阻塞.

    把客户端第18行注释掉,服务端和客户端的打印结果为

    //这是服务端打印
    正在发送数据
    正在接收数据
    连接关闭
    //这是客户端打印
    正在接收数据
    接收的数据success
    正在发送数据

    出现这种结果的原因是,服务端写完数据后关闭了写入流,客户端从而读取完毕.接着向服务端写入数据,但写入完毕后并没有关闭写入流,程序直接结束.服务端还在等待读数据,但是客户端已经结束,所以服务端抛出了异常.

    服务端队列

    管理客户连接请求的任务是由操作系统来完成的. 操作系统把这些连接请求存储在一个先进先出的队列中. 许多操作系统限定了队列的最大长度, 一般为 50 . 当队列中的连接请求达到了队列的最大容量时, 服务器进程所在的主机会拒绝新的连接请求. 只有当服务器进程通过 ServerSocket 的 accept() 方法从队列中取出连接请求, 使队列腾出空位时, 队列才能继续加入新的连接请求.

    public static void main(String[] args) {
            ServerSocket server = null;
            try {
                server = new ServerSocket(15,1);
                while (true) {
                    Socket socket = null;
                    try {
                        socket = server.accept();
                        Thread.sleep(1000*10);
                    } catch (Exception e) {
                        System.out.println("连接关闭");
                    } finally {
                        if (socket != null) {
                            try {
                                socket.close();
                            } catch (Exception e) {
                            }
                        }
                    }
                }
            } catch (Exception e) {
                System.out.println("服务器关闭了");
            } finally {
                if (server != null) {
                    try {
                        server.close();
                    } catch (Exception e) {
                    }
                }
            }
        }// 服务端
    public static void main(String[] args) throws Exception {
            Socket socket = new Socket("127.0.0.1", 15);
        }// 客户端

    构造但不绑定端口

    ServerSocket server = new ServerSocket();
    SocketAddress address = new InetSocketAddress(15);
    server.bind(address);

    随机端口

    传入端口号为0,操作系统会为你选择可用端口,在FTP协议中这是很常见的,客户端首先连接到已知的21端口,不过在传输文件时,服务器开始监听任何可用的端口,然后服务器会使用已经在端口21打开的命令连接告诉客户端应当连接到哪一个端口来得到数据。

    public static void main(String[] args) throws Exception{
            ServerSocket server = new ServerSocket(0);
            System.out.println(server.getLocalPort());
            while (true) {
                Socket socket = server.accept();
            }
        }// 服务端

    Socket选项

    Socket选项制定了ServerSocket类所依赖的原生Socket如何发送和接收数据。对于服务器Socket,Java支持三个选项。

    SO_TIMEOUT:SO_TIMEOUT是accept()在抛出java.io.InterruptedIOException异常前等待入站连接的时间,以毫秒为单位。如果SO_TIMEOUT为0,accept()就永远不会超时。这个默认值的作用就是永远不会超时。设置了该值并大于0则在指定的时间内没有客户端连接则抛出异常。

    public static void main(String[] args) throws Exception{
            ServerSocket server = new ServerSocket(15);
            server.setSoTimeout(1000*3);
            System.out.println(server.getSoTimeout());
            while (true) {
                Socket socket = server.accept();
            }
        }// 服务端

    SO_REUSEADDR:服务端的SO_REUSEADDR与客户端的SO_REUSEADDR作用类似,它确定了是否允许一个新的Socket绑定到之前使用过的一个端口,而此时可能还有一些发送到原Socket的数据正在网络上传输。

    public static void main(String[] args) throws Exception{
            ServerSocket server = new ServerSocket(15);
            server.setReuseAddress(true);
            System.out.println(server.getReuseAddress());
            while (true) {
                Socket socket = server.accept();
            }
        }// 服务端

    SO_RCVBUF:SO_RCVBUF服务器Socket接收客户端的缓冲区大小,设置一个服务器的SO_RCVBUF就像在accept()返回的各个Socket上调用setReceiveBufferSize()。如果你设置的缓冲区大于64KB则必须在绑定端口之前设置。

    public static void main(String[] args) throws Exception{
            ServerSocket server = new ServerSocket();
            server.setReceiveBufferSize(1024*1024);
            System.out.println(server.getReceiveBufferSize());
            server.bind(new InetSocketAddress(15));
            while (true) {
                Socket socket = server.accept();
            }
        }// 服务端

    服务器第一版

    以下代码片段为一个简单的HTTP服务器,无论接收到什么内容都返回一个固定的字符串,如果请求内容中包含HTTP则表示客户端可以解析HTTP相应,则返回固定的响应头+固定的字符串。注意,以下示例不能直接用浏览器访问!

    package com.datang.bingxiang.demo;
    
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.nio.charset.Charset;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class 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 Exception {
            this(data.getBytes(encoding), encoding, mimeType, port);
        }
    
        public SingleFileHTTPServer(byte[] data, String encoding, String mimeType, int port) {
            this.content = data;
            this.encoding = encoding;
            this.port = port;
            StringBuilder sb = new StringBuilder();
            sb.append("HTTP/1.0 200 OK
    ");
            sb.append("Server: OneFile 2.0
    ");
            sb.append("Content-length: " + this.content.length + "
    ");
            sb.append("Content-type: " + mimeType + "; charset=" + encoding + "
    
    ");
            this.header = sb.toString().getBytes(Charset.forName("US-ASCII"));
        }
    
        public void start() throws Exception {
            ExecutorService pool = Executors.newFixedThreadPool(100);
            ServerSocket server = new ServerSocket(this.port);
            while (true) {
                Socket connection = server.accept();
                pool.submit(new HTTPHandler(connection));
            }
        }
    
        private class HTTPHandler implements Runnable {
    
            private final Socket connection;
    
            HTTPHandler(Socket connection) {
                this.connection = connection;
            }
    
            @Override
            public void run() {
                try {
                    BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                    StringBuilder request = new StringBuilder(80);
                    String data  = null;
                    while ((data=in.readLine())!=null) {
                        request.append(data);
                    }
                    connection.shutdownInput();
                    System.out.println("------"+request);
                    
                    BufferedWriter out = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
                    //如果是HTTP/1.0或以后的版本,则发送一个MIME首部
                    if (request.toString().indexOf("HTTP/") != -1) {
                        out.append(new String(header,encoding));
                    }
                    
                    out.append(new String(content,encoding));
                    out.flush();
                    connection.shutdownOutput();
                    
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    try {
                        connection.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        
        public static void main(String[] args) throws Exception {
            SingleFileHTTPServer server = new SingleFileHTTPServer("我是服务器", "UTF-8", "text/plain", 80);
            server.start();
        }
    }//服务端
    package com.datang.bingxiang.demo;
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.net.Socket;
    
    public class Client {
        public static void main(String[] args) throws Exception {
            Socket socket = new Socket("127.0.0.1", 80);
            
            OutputStream out = socket.getOutputStream();
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
            bw.append("HTTP/1.1");
            bw.flush();
            socket.shutdownOutput();
            
            InputStream in = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
            socket.shutdownInput();
            
            socket.close();
        }// 客户端
    }

    服务器第二版(重定向服务器) 

    以下DEMO支持GET请求和POST请求(请求体需要换行,看下图)在这里出现问题,不知道Tomcat服务器是如何处理的,无论是用字节流读取判断-1还是字符流读取判断null都无法正确的读取到末尾,所以只能根据不同的请求做不同的处理,如果是GET请求则在空行后直接停止循环,否则不会自己停止,如果是POST请求则需要拿到Content-Length请求头,然后从空行后开始计算Body的长度,注意你的Body一定要自动的加上空行,否则依然无法结束读取。就POST请求来说,我使用Tomcat服务器并不需要特意的指定结束行,这是一个严重的坑!

    不过在浏览器访问该Demo是可以成功重定向的。

    package com.datang.bingxiang.demo;
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Date;
    
    public class Redirector {
        private final int port;
        private final String newSite;
    
        public Redirector(String newSite, int port) {
            this.port = port;
            this.newSite = newSite;
        }
    
        public void start() throws Exception {
            ServerSocket server = new ServerSocket(port);
            while (true) {
                Socket s = server.accept();
                Thread t = new RedirectThread(s);
                t.start();
            }
        }
    
        private class RedirectThread extends Thread {
            private final Socket connection;
    
            RedirectThread(Socket s) {
                this.connection = s;
            }
    
            public void run() {
                try {
                    StringBuilder request = new StringBuilder();
                    InputStream inputStream = connection.getInputStream();
    
                    //字符流读取方式
                    BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
                    
                    String methodLine = in.readLine();
                    String method = "";
                    if (methodLine.startsWith("GET")) {
                        method = "GET";
                    } else if (methodLine.startsWith("POST")) {
                        method = "POST";
                    }
    
                    request.append(methodLine);
                    int contentLength = -1;
                    int bodyLnegth = 0;
                    String line = null;
                    boolean isBody = false;
                    while ((line = in.readLine()) != null) {
                        request.append(line + "
    ");
                        System.out.println("!!!" + line+"--"+(line.equals("")));
                        
    
                        if (line.startsWith("Content-Length")) {
                            contentLength = Integer.parseInt(line.split(":")[1].trim());
                        }
    
                        if (line.equals("")) {
                            if (method.equals("GET")) {
                                break;
                            } else {
                                isBody=true;
                                System.out.println("读到空行了");
                            }
                        }
                        
                        if(isBody) {
                            bodyLnegth += line.length();
                            if(bodyLnegth==contentLength-2) {
                                break;
                            }
                        }
                    }
    
                    System.out.println("----------------------------------------------");
    
                    
                    //字节流读取方式
    //                byte[] b = new byte[1024];
    //                int len = 0;
    //                while ((len = inputStream.read(b)) != -1) {
    //                    String s = new String(b, 0, len);
    //                    System.out.println(s);
    //                    request.append(s);
    //                }
    
                    connection.shutdownInput();
    
                    BufferedWriter out = new BufferedWriter(
                            new OutputStreamWriter(connection.getOutputStream(), "US-ASCII"));
    
                    // 如果是HTTP/1.0或以后版本,则发送一个Mime首部
                    if (request.toString().indexOf("HTTP") != -1) {
                        out.write("HTTP/1.1 302 FOUND
    ");
                        Date now = new Date();
                        out.write("Date: " + now + "
    ");
                        out.write("Server: Redirector 1.1
    ");
                        out.write("Location: " + newSite + "
    ");
                        out.write("Content-type: text/html
    
    ");
                        out.flush();
                    }
    
                    // 并不是所有浏览器都支持重定向,所有我们需要生成HTML指出文档转移到哪里
                    out.write("<HTML><HEAD><TITLE>Document moved</TITLE></HEAD>
    ");
                    out.write("<BODY><H1>Document moved</H1>
    ");
                    out.write("The document " + " has moved to
    <A href="" + newSite + "">" + newSite
                            + "</A>.
     Please update your bookmarks<P>");
                    out.write("</BODY></HTML>
    ");
    
                    out.flush();
                    connection.shutdownOutput();
                } catch (Exception e) {
                    try {
                        connection.close();
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                }
            }
        }
    
        public static void main(String[] args) throws Exception {
            int thePort = 1111;
            String theSite = "http://localhost:8888/test";
            Redirector redirector = new Redirector(theSite, thePort);
            redirector.start();
        }
    }// 服务端
    @RequestMapping(value = "test")
        public String g(HttpServletRequest request) throws IOException {
            System.out.println(request.getRemotePort());
            Enumeration<String> headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String key = headerNames.nextElement();
                Enumeration<String> headers = request.getHeaders(key);
                while (headers.hasMoreElements()) {
                    String value = headers.nextElement();
                    System.out.println(key + "   " + value);
                }
    
            }
            return "success1";
        }// 服务端

     

  • 相关阅读:
    小程序滴滴车主板块的银行卡管理左滑删除编辑
    超好用超短的小程序请求封装
    如何使用css来让图片居中不变形 微信小程序和web端适用
    纯css手写圆角气泡对话框 微信小程序和web都适用
    小程序getUserInfo授权升级更新登录优化
    一起聊聊3个线程依次打印1、2、3...的故事
    influxdb基础那些事儿
    influxdb的命令们
    Linux namespace浅析
    kubernetes基础概念知多少
  • 原文地址:https://www.cnblogs.com/zumengjie/p/15067089.html
Copyright © 2011-2022 走看看