• 周四. 5月 30th, 2024

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

JAVA网络编程-服务器Socket

admin

11月 28, 2021

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";
    }// 服务端

 

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注