View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2024 Christian Kohlschütter
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.newsclub.net.unix.demo.server;
19  
20  import java.io.BufferedOutputStream;
21  import java.io.ByteArrayOutputStream;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.net.Socket;
25  import java.net.SocketAddress;
26  import java.net.SocketException;
27  
28  /**
29   * A multi-threaded unix socket server that implements a TCP-style character generator compliant
30   * with RFC864.
31   *
32   * @author Christian Kohlschütter
33   */
34  public final class ChargenServer extends DemoServerBase {
35    private static final Chargen SIMPLE_CHARGEN = new SimpleChargen();
36    private final boolean fast;
37    private FastChargen cachedChargen = null;
38  
39    /**
40     * Defines a TCP-style character generator compliant with RFC864.
41     *
42     * @see <a href="https://tools.ietf.org/html/rfc864">RFC864</a>
43     */
44    private interface Chargen {
45      void write(Socket socket) throws IOException;
46    }
47  
48    public ChargenServer(SocketAddress listenAddress) {
49      this(listenAddress, true);
50    }
51  
52    public ChargenServer(SocketAddress listenAddress, boolean fast) {
53      super(listenAddress);
54      this.fast = fast;
55    }
56  
57    @Override
58    protected void onServerStarting() {
59      super.onServerStarting();
60      System.out.println("- Fast chargen: " + fast);
61    }
62  
63    @Override
64    protected void doServeSocket(Socket socket) throws IOException {
65      try (OutputStream os = socket.getOutputStream()) {
66        getChargen(socket).write(socket);
67      }
68    }
69  
70    private synchronized Chargen getChargen(Socket socket) throws SocketException {
71      if (!fast) {
72        return SIMPLE_CHARGEN;
73      }
74  
75      int bufferSize = socket.getSendBufferSize();
76      FastChargen chargen = cachedChargen;
77      if (chargen == null || chargen.cacheSize != bufferSize) {
78        chargen = new FastChargen(bufferSize);
79        cachedChargen = chargen;
80      }
81      return chargen;
82    }
83  
84    /**
85     * A simple chargen implementation.
86     *
87     * Even though this looks straightforward, it's not the fastest implementation.
88     *
89     * @see FastChargen
90     */
91    private static final class SimpleChargen implements Chargen {
92      @Override
93      public void write(Socket socket) throws IOException {
94        try (BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream(), socket
95            .getSendBufferSize())) {
96          while (true) {
97            int offset = 1;
98            for (int row = 0; row < 95; row++) {
99              for (int i = 0; i < 72; i++) {
100               int asciiChar = (32 + (offset + i) % 95);
101               bos.write(asciiChar);
102             }
103             bos.write(13); // CR
104             bos.write(10); // LF
105 
106             offset++;
107           }
108         }
109       }
110     }
111   }
112 
113   /**
114    * A fast chargen implementation, using a pre-built data buffer that is just large enough to
115    * always send a full array of bytes matching the socket's send buffer capacity.
116    *
117    * @see SimpleChargen
118    */
119   private static final class FastChargen implements Chargen {
120     /**
121      * Size of the ever-repeating chargen pattern.
122      */
123     private final int ourDataSize;
124 
125     /**
126      * Size of the cache that contains the ever-repeating chargen pattern data, and some more of it.
127      */
128     private final int cacheSize;
129 
130     /**
131      * The cache.
132      */
133     private final byte[] cache;
134 
135     FastChargen(int sendBufferSize) {
136       this.cacheSize = sendBufferSize;
137 
138       final int lineWidth = 72;
139       final int firstPrintableCharacter = 32; // ASCII printable start
140       final int lastPrintableCharacter = 126; // ASCII printable end
141 
142       final int numPrintableCharacters = (lastPrintableCharacter + 1) - firstPrintableCharacter;
143       final int linefeedLen = 2; // CR+LF
144 
145       this.ourDataSize = numPrintableCharacters * (lineWidth + linefeedLen);
146       final int bufLen = sendBufferSize + ourDataSize;
147 
148       ByteArrayOutputStream bos = new ByteArrayOutputStream(bufLen);
149       int nWritten = 0;
150 
151       bigLoop : while (nWritten < bufLen) {
152         int offset = 1;
153         for (int row = 0; row < numPrintableCharacters; row++) {
154           for (int i = 0; i < lineWidth; i++) {
155             int asciiChar = (32 + (offset + i) % numPrintableCharacters);
156             bos.write(asciiChar);
157             if (++nWritten == bufLen) {
158               break bigLoop;
159             }
160           }
161           bos.write(13); // CR
162           bos.write(10); // LF
163           if (++nWritten == bufLen) {
164             break bigLoop;
165           }
166           offset++;
167         }
168       }
169 
170       this.cache = bos.toByteArray();
171     }
172 
173     @Override
174     public void write(Socket socket) throws IOException {
175       try (OutputStream os = socket.getOutputStream()) {
176         int offset = 0;
177         while (true) {
178           os.write(cache, offset, cacheSize);
179           offset = (offset + cacheSize) % ourDataSize;
180         }
181       }
182     }
183   }
184 }