NETWORK programming

Hi
In October 2025, @superthin asked, “Cần trợ giúp việc chuyển file không chờ upload xong qua Internet” I thought this would be a simple approach. Transferring files from point A to point B using P2P communication software is the key to accomplishing this task. For example, Linux’s fpt command enables file transfer. However, according to @superthin, the issue is transferring large amounts of data (large files), which can be several GB in size. There are numerous free and paid software programs available that can fulfill this need. I haven’t used any of the available software or Linux FPT. Upon closer inspection, free online file transfer programs limit the data size to 5 GB. Beyond that, you have to pay or subscribe.
Back to the root of the matter. The problems with file transfer limit not only the file size, but also the network packet size (due to the legacy: 4 bytes of int for files and 2 bytes of short int for data packets). This means that files larger than 4 GB require special processing before they can be sent over the network in 64 KB data packets. This physical limitation affects the transfer time. Added to this is the network load, which is usually unpredictable on the internet (regardless of VPN) – since your network is based on leased lines. Furthermore, the internet requires security, which requires additional effort for encryption and decryption and also impacts transfer times. To reduce the number of data packets—and thus the transfer time—most file transfer programs utilize data compression and decompression. This is advantageous for large files of more than a few GB, but not optimal for small files of a few MB.
Nowadays, software developers rarely program in assembly or C, but rather in OOP languages ​​such as C++, JAVA, PYTHON, etc. This is due to the availability of low-level networking APIs such as Socket, UDP, etc. in all OOP languages. These APIs are reliable and significantly reduce development effort. The downside is that some IT developers have less and less knowledge of communication technology. To understand peer-to-peer (P2P), you need to have a thorough understanding of the communication flow between two parties (peers) and the processing order within the communication software and the operating system. It’s all about synchronization. And understanding synchronization is a delicate matter: not only between two peers, but also between executing code, such as threads within the software.
This code snippet illustrates the problems caused by a lack of operating system knowledge: When the SEND button is clicked, the Lambda expression code executes, and the remote server starts an Xfer thread to accept the incoming data using the retrieveFile() method. The Xfer thread runs in a thread pool (ExecutorService).

    SEND.addActionListener(e -> {
      ...
      String fileName = tFile.getText();
      if (fileName.startsWith("$HOME")) fileName = fileName.replace("$HOME", uHome);
      if (!(new File(fileName)).exists()) msg.setText(fileName+" is unknown.");
      else try {
        ...
        SocketChannel soc = SocketChannel.open(new InetSocketAddress(tIP.getText(), rPort));
        soc.socket().setSendBufferSize(65536);
        soc.socket().setTcpNoDelay(true);
        // sendToken + fileName
        byte[] buf = (token[0]+""+ZIP+fileName.substring(fileName.lastIndexOf(File.separator)+1)).getBytes("UTF-8");
        soc.write(ByteBuffer.wrap(buf, 0, buf.length));
        ByteBuffer bbuf = ByteBuffer.allocate(256);
        int n = soc.read(bbuf); // wait for Reply
        sendFile(soc, fileName, zip);
      } catch (Exception ex) {
        ex.printStackTrace();
      }
  });
  ...
  private void sendFile(SocketChannel soc, String fileName, boolean zip) throws Exception {
    ...
  }
  private String retrieveFile(SocketChannel soc, String fileName, boolean zip) throws Exception {
    ...
  }
  ...
  private class Xfer implements Runnable {
    public Xfer(SocketChannel soc) {
      this.soc = soc;
    }
    private SocketChannel soc;
      public void run() {
      try {
        ...
        if (buf[0] == (byte)token[0]) { // send from remote
          soc.write(ByteBuffer.wrap(new byte[] { (byte)0x00 }, 0, 1));
          retrieveFile(soc, fileName, zip);
        } else if (buf[0] == (byte)token[1]) { // retrieve from remote
          if (fileName.startsWith("$HOME")) fileName = fileName.replace("$HOME", uHome);
          if (!(new File(fileName)).exists()) {
            ...
            return;
          }
          sendFile(soc, fileName, zip);
        } else { // Illegal. Do NOTHING 
          ...
          return;
        }
      } catch (Exception ex) { }
    }
  }

This means that the Lambda expression code runs interactively and the Xfer code runs in the background. Interactive tasks always have higher priority than background tasks, and the local-remote code encounters synchronization issues between sendFile() and retrieveFile() on the remote server. The result: unpredictable or corrupted data on the remote site.
Priorities are managed by operating systems designed to prioritize interactive tasks to ensure a responsive user experience, while background tasks are executed with less urgency and do not interfere with foreground activities. This means that to reduce response time, the interactive lambda expression is served preferentially, so some sent data packets are lost to the remote thread running in the background. The synchronization issues can be easily solved if the code within the lambda expression, like the Xfer thread, is also placed in the thread pool to run in the background.

    SEND.addActionListener(e -> {
      ...
      // running in Background in thread pool
      pool.execute(() -> {
        String fileName = tFile.getText();
        if (fileName.startsWith("$HOME")) fileName = fileName.replace("$HOME", uHome);
        if (!(new File(fileName)).exists()) msg.setText(fileName+" is unknown.");
        else try {
          ...
          SocketChannel soc = SocketChannel.open(new InetSocketAddress(tIP.getText(), rPort));
          soc.socket().setSendBufferSize(65536);
          soc.socket().setTcpNoDelay(true);
          // sendToken + fileName
          byte[] buf = (token[0]+""+ZIP+fileName.substring(fileName.lastIndexOf(File.separator)+1)).getBytes("UTF-8");
          soc.write(ByteBuffer.wrap(buf, 0, buf.length));
          ByteBuffer bbuf = ByteBuffer.allocate(256);
          int n = soc.read(bbuf); // wait for Reply
          sendFile(soc, fileName, zip);
       } catch (Exception ex) {
         ex.printStackTrace();
       }
    });
  });
  ...

Encryption/decryption and compression/decompression work similarly. Therefore, I’ll show you how to work with compression/decompression in pure JAVA. If you search the internet for problems sending/receiving compressed/decompressed data over a socket, you won’t find any good advice or solutions (even on Stack Overflow). This JavaTechniques blog “Compressing Data Sent Over a Socket” is good and helpful if you need two generic CompressedBlockOutputStreams and CompressedBlockInputStreams for your own API library. However, I disagree with this statement:

The stream classes in java.util.zip work well for working with compressed files and similarly bounded data. They are, however, less useful for compressing continuous transmissions over a socket connection.

but agree with this

This is unfortunate, as the tradeoff between processor cycles (needed for compressing and decompressing) and bandwidth is often such that compression can have a considerable impact on performance of network applications

I’ll show you how to use GZIPInputStream and GZIPOutputStream with socket communication.

  private void sendFile(SocketChannel soc, String fileName, boolean zip) throws Exception {
    byte[] buf = new byte[65536];
    try {
      int p;
      ByteBuffer bbuf = ByteBuffer.allocate(65400);
      FileChannel fc = (new FileInputStream(fileName)).getChannel();
      GZIPOutputStream gzip = new GZIPOutputStream(soc.socket().getOutputStream(), true);
      while ((p = fc.read(bbuf)) > 0) {
        ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
        gzip.write(buf, 0, p); // write to socket
        gzip.flush();
        bbuf.clear();
      }
      gzip.close();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
  ...
  private void retrieveFile(SocketChannel soc, String fileName, boolean zip) throws Exception {
    int p;
    GZIPInputStream gzip = null;
    byte[] buf = new byte[65536];
    GZIPInputStream gzip =  new GZIPInputStream(soc.socket().getInputStream());
    FileChannel fc = (new FileOutputStream(fileName, false)).getChannel();
    while ((p = gzip.read(buf)) > 0) { // read from Socket
      fc.write(ByteBuffer.wrap(buf, 0, p));
    }
    gzip.close();
  }

You can use JAVA Inflater and Deflater directly for decompression and compression. This significantly improves performance compared to using a generic compression/decompression stream. For example:

  private void sendFile(SocketChannel soc, String fileName, boolean zip) throws Exception {
    int p;
    ByteBuffer bbuf = ByteBuffer.allocate(65400);
    FileChannel fc = (new FileInputStream(fileName)).getChannel();
    byte[] deBuf = new byte[65536];
    byte[] buf = new byte[65536];
    Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
    deflater.setStrategy(Deflater.DEFAULT_STRATEGY);
    while ((p = fc.read(bbuf)) > 0) {
      ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
      //compress by deflating the content
      deflater.setInput(buf, 0, p);
      deflater.finish();
      int size = deflater.deflate(deBuf);
      byte[] cBuf = new byte[8+size];
      // compressed Length
      cBuf[0] = (byte)((size >> 24) & 0xFF);
      cBuf[1] = (byte)((size >> 16) & 0xFF);
      cBuf[2] = (byte)((size >>  8) & 0xFF);
      cBuf[3] = (byte)( size        & 0xFF);
      // original Length
      cBuf[4] = (byte)((p >> 24) & 0xFF);
      cBuf[5] = (byte)((p >> 16) & 0xFF);
      cBuf[6] = (byte)((p >>  8) & 0xFF);
      cBuf[7] = (byte)( p        & 0xFF);
      System.arraycopy(deBuf, 0, cBuf, 8, size);
      soc.write(ByteBuffer.wrap(cBuf, 0, cBuf.length));
      deflater.reset();
      bbuf.clear();
    }
    fc.close();
  }
  private void retrieveFile(SocketChannel soc, String fileName, boolean zip) throws Exception {
    int p;
    byte[] dbuf, buf = new byte[65536];
    ByteBuffer bbuf = ByteBuffer.allocate(65536);
    FileChannel fc = (new FileOutputStream(fileName, false)).getChannel();
    Inflater inflater = new Inflater();
    while ((p = soc.read(bbuf)) > 0) {
      ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
      // length of the compressed block
      int iLen = (buf[0] & 0xFF) << 24 |
                 (buf[1] & 0xFF) << 16 |
                 (buf[2] & 0xFF) <<  8 |
                  buf[3] & 0xFF;
      // length of decompress block
      int oLen = (buf[4] & 0xFF) << 24 |
                 (buf[5] & 0xFF) << 16 |
                 (buf[6] & 0xFF) <<  8 |
                  buf[7] & 0xFF;
       dbuf = new byte[oLen];
       //
       inflater.reset();
       inflater.setInput(buf, 8, iLen);
       inflater.inflate(dbuf);
       fc.write(ByteBuffer.wrap(dbuf, 0, oLen));
       bbuf.clear();
    }
    fc.close();
  }

The following image gives an idea of ​​the performance impact between In/Deflater and GZIPIn/GZIPOutputStream:

69.16 milliseconds versus 217.26 milliseconds.


The app is protected by a send/retrieve token between 1 and 255, defined and agreed upon between the owners. Note: It is recommended to use compression mode only if the file is REALLY larger than some MB.
As mentioned in my comment on @supethin’s post “Cần trợ giúp việc chuyển file không chờ upload xong qua Internet” the IPv6 internet address does NOT require port forwarding. The only issue is the long hex value, the IPv6 number that must be specified as the remote IP. If anyone is interested in XFileTransfer and/or ZFileTransfer, please leave a request in my DNH inbox.

3 Likes

How to work with the XFile API?

The API is pure JAVA and based on SocketChannel (client) and ServerSocketChannel (server). XFile is designed for peer-to-peer (P2P) communication. You simply need to develop your own GUI application in JFX or SWING that runs XFile as the network communication engine. If your internet router has an IPv6 address, you don’t need to configure port forwarding. Channel processing is block-oriented, while streams are byte-oriented. For small files, the advantage of Channel is negligible. However, for large files, the advantage is noticeable.

image

The IPv6 internet address is a long hex string of 32 hex digits, grouped into four hex digits separated by a colon (:). Example: 2a02:8071:5302:2160:666d:a420:2efb:9ffa. The leading hex 0 is normally ignored. Example: 02ab becomes 2ab. All four zeros are represented by a single 0.

Instead of encryption/decryption, XFile secures the send/retrieve process by using two different tokens in the range 0 to 255, whereby the send and retrieve tokens must always be different. Both sides (or peers) must use the same send/retrieve token. This saves a lot of encryption/decryption time.

To shorten transfer time, most P2P applications use compression/decompression techniques to reduce data size. However, compression and decompression also take time. A real time saving can only be achieved if the file is larger than several MB.

XFile uses the ZIP compression/decompression technique. GZIP achieves better compression results than ZIP, but also takes longer to execute (see image below).

The XFile API

import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.file.*;
import java.util.zip.*;
import java.nio.channels.*;
import java.util.concurrent.*;
//
import javax.swing.*;
// Joe Schwarz (C)
public class XFile {
  //
  /** 
   * XFile API is a P2P communication based on SocketChannel and ServerSocketChannel.
   * <b>Two servers communicate with each other. One sends, the other listens (or retrieves). Use the
   * <b>isDone() and getMessage() methods to check the processing status and the message returned by
   * <b>send() or retrieve().
   * <b>
   * Constructor and start server for remote requests. Send and retrieve tokens on both sides must match.
   * <b>
   * @param saveDir String, directory where retrieved files are saved.
   * @param token int array contains sendToken (index 0) and retrieveToken (index 1)
   * @param port int, port number for Xfer Server
   * @param poolSize int, size of ExecutorService pool
   * @exception throw if SeverSocketChannel cannot be started due to bad port or other reasons
  */
  public XFile(final String saveDir, final int[] token, final int port, final int poolSize) throws Exception {
    if (port <= 0) throw new Exception("Invalid ServerPort:"+port);
    this.token = token;
    this.saveDir = saveDir;
    int size = poolSize < 64? 64:poolSize;
    uHome = System.getProperty("user.home");
    try {
      pool = Executors.newFixedThreadPool(size);
      server = ServerSocketChannel.open();
      server.socket().bind(new InetSocketAddress(port));
      server.setOption(StandardSocketOptions.SO_RCVBUF, 65536);
      pool.execute(() -> { // waiting for request in ThreadPool
        try {
          while (!end) pool.execute(new Xfer(server.accept()));
        } catch (Exception ex) {
          if (!end) {
            pool.shutdownNow();
            ex.printStackTrace();
          }
        }
        if (server != null) try {
          server.close();
        } catch (Exception ex) { }
      });
    } catch (Exception ex) { server = null; }
    if (server == null) throw new Exception("Unable to start Server @"+port);
  }
  /**
   * setToken for Send and Retrieve. Nothing is set if token array is larger/less than 2 or both elements are equal.
   * @param token int array contains sendToken (index 0) and retrieveToken (index 1)
  */
  public void setToken(int[] token) {
    if (token.length == 2 && token[0] != token[1]) this.token = token;
  }
  /**
   * Clear Send/Retrieve status
  */
  public void clear() {
    msg.clear();
    done.clear();
  }
  /**
   * Close Server and ThreadPool
  */
  public void close() {
    end = true;
    pool.shutdownNow();
    try { // wait for Pool Service
      TimeUnit.MICROSECONDS.sleep(10);
    } catch (Exception ex) { }
  }
  /**
   * isDone() queries for the processing status of send() or retrieve()
   * @param timeStamp long, the timeStamp returned by send() or retrieve()
   * @return boolean true, send() or retrieve() is finished, false: on process or timeStamp is Unknown.
  */
  public boolean isDone(long timeStamp) {
    Boolean b = done.get(timeStamp);
    if (b == null) return false;
    return b;
  }
  /**
   * getMessage() returned by send() or retrieve()
   * @param timeStamp long, the timeStamp returned by send() or retrieve()
   * @return String message or null if timeStamp is unknown.
  */
  public String getMessage(long timeStamp) {
    Boolean B = done.get(timeStamp);
    if (B == null) return null;
    if (!B) while (!(B = done.get(timeStamp))) try {
      TimeUnit.MICROSECONDS.sleep(5);
    } catch (Exception ex) { }
    return msg.get(timeStamp);
  }
  /**
   * Send a file to remote host at IPv6:remotePort
   * @param fileName String
   * @param ipv6 String, IPv6
   * @param remotePort int
   * @param token int array contains sendToken (index 0) and retrieveToken (index 1)
   * @param compressed boolean, true for compressed
   * @return timeStemp a long value. 0 if send failed
  */  
  public long send(final String fileName, final String ipv6, final int remotePort,
                   final int[] token, final boolean compressed) {
    if (remotePort == 0 || !(new File(fileName)).exists()) return 0;
    final long timeStamp = System.currentTimeMillis();
    done.put(timeStamp, false);
    pool.execute(() -> {
      try {
        int stoken = token[0] & 0xFF;
        int zip = compressed? 0x01:0x00;
        SocketChannel soc = SocketChannel.open(new InetSocketAddress(ipv6, remotePort));
        soc.socket().setSendBufferSize(65536);
        soc.socket().setTcpNoDelay(true);
        // sendToken + fileName
        byte[] buf = ((char)stoken+""+(char)zip+fileName.substring(fileName.lastIndexOf(File.separator)+1)).getBytes("UTF-8");
        soc.write(ByteBuffer.wrap(buf, 0, buf.length));
        ByteBuffer bbuf = ByteBuffer.allocate(256);
        soc.read(bbuf); // wait for Reply
        long t0 = System.nanoTime();
        sendFile(soc, fileName, zip);
        msg.put(timeStamp, String.format("Sent %s - Total(milliSec.): %.2f",fileName,
                                        ((float)(System.nanoTime()-t0)/1000000)));
      } catch (Exception ex) {
        msg.put(timeStamp, ex.toString());
      }
      done.replace(timeStamp, true);
    });
    return timeStamp;
  }
  /**
   * retrieve a file from remote host at IPv6:remotePort
   * @param fileName String
   * @param sDir String, directory where the retrieved file is saved
   * @param ipv6 String, IPv6
   * @param remotePort int
   * @param token int array contains sendToken (index 0) and retrieveToken (index 1)
   * @param compressed boolean, true for compressed
   * @return timeStemp a long value. 0 if retrieve failed
  */  
  public long retrieve(final String fileName, String sDir,
                       final String ipv6, final int remotePort,
                       final int[] token, final boolean compressed) {
    if (remotePort == 0) return 0;
    final long timeStamp = System.currentTimeMillis();
    done.put(timeStamp, false);
    pool.execute(() -> {
      try {
        int rtoken = token[1] & 0xFF;
        int zip = compressed? 0x01:0x00;
        SocketChannel soc = SocketChannel.open(new InetSocketAddress(ipv6, remotePort));
        soc.socket().setSendBufferSize(65536);
        soc.socket().setTcpNoDelay(true);
        // retrieveToken + fileName
        byte[] buf = ((char)rtoken+""+(char)zip+fileName).getBytes();
        soc.write(ByteBuffer.wrap(buf, 0, buf.length));
        long t0 = System.nanoTime();
        String fn = retrieveFile(soc, fileName, sDir, zip);
        msg.put(timeStamp, fn == null? fileName+" is unknown @"+ipv6+":"+remotePort:
                String.format("Saved %s - Total(milliSec.): %.2f",fn, ((float)(System.nanoTime()-t0)/1000000)));
      } catch (Exception ex) {
        msg.put(timeStamp, ex.toString());
      }
      done.replace(timeStamp, true);
    });
    return timeStamp;
  }
  //
  private void sendFile(SocketChannel soc, String fileName, int zip) throws Exception {
    byte[] buf = new byte[65536];
    try {
      int p;
      ByteBuffer bbuf = ByteBuffer.allocate(65400);
      FileChannel fc = (new FileInputStream(fileName)).getChannel();
      if (zip == 1) { // Compressed
        byte[] deBuf = new byte[65536];
        Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
        deflater.setStrategy(Deflater.DEFAULT_STRATEGY);
        while ((p = fc.read(bbuf)) > 0) {
          ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
          //compress by deflating the content
          deflater.setInput(buf, 0, p);
          deflater.finish();
          int size = deflater.deflate(deBuf);
          byte[] cBuf = new byte[8+size];
          // compressed Length
          cBuf[0] = (byte)((size >> 24) & 0xFF);
          cBuf[1] = (byte)((size >> 16) & 0xFF);
          cBuf[2] = (byte)((size >>  8) & 0xFF);
          cBuf[3] = (byte)( size        & 0xFF);
          // original Length
          cBuf[4] = (byte)((p >> 24) & 0xFF);
          cBuf[5] = (byte)((p >> 16) & 0xFF);
          cBuf[6] = (byte)((p >>  8) & 0xFF);
          cBuf[7] = (byte)( p        & 0xFF);
          System.arraycopy(deBuf, 0, cBuf, 8, size);
          soc.write(ByteBuffer.wrap(cBuf, 0, cBuf.length));
          deflater.reset();
          bbuf.clear();
        }
      } else { // Normal
        while ((p = fc.read(bbuf)) > 0) {
          ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
          soc.write(ByteBuffer.wrap(buf, 0, p));
          bbuf.clear();
        }
        fc.close();
      }
    } catch (Exception ex) {
      try { 
        buf = ("Unknown file: "+fileName).getBytes();
        soc.write(ByteBuffer.wrap(buf, 0, buf.length));
        throw new Exception(ex.toString());
      } catch (Exception e) { }
    }
    soc.close();
  }
  //
  private String retrieveFile(SocketChannel soc, String fileName, String saveDir, int zip) throws Exception {
    byte[] buf = new byte[65536];
    ByteBuffer bbuf = ByteBuffer.allocate(65536);
    int  p = soc.read(bbuf);
    ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
    //
    if ("Unknown file: ".equals(new String(buf, 0, 14, "UTF-8"))) {
      soc.close();
      return null;
    }
    bbuf.clear();
    int n = fileName.lastIndexOf(File.separator)+1;
    fileName = saveDir+fileName.substring(n);
    if (fileName.startsWith("$HOME")) fileName = fileName.replace("$HOME", uHome);
    FileChannel fc = (new FileOutputStream(fileName, false)).getChannel();
    if (zip == 1) { // Compress
      Inflater inflater = new Inflater();
      while (true) {
        inflater.reset();
        // length of compress block
        int iLen = (buf[0] & 0xFF) << 24 |
                   (buf[1] & 0xFF) << 16 |
                   (buf[2] & 0xFF) <<  8 |
                    buf[3] & 0xFF;
        // length of decompress block
        int oLen = (buf[4] & 0xFF) << 24 |
                   (buf[5] & 0xFF) << 16 |
                   (buf[6] & 0xFF) <<  8 |
                    buf[7] & 0xFF;
        byte[] dBuf = new byte[oLen];
        if ((p-8) > iLen) inflater.setInput(buf, 8, iLen);
        else inflater.setInput(buf, 8, p-8);
        inflater.inflate(dBuf);
        fc.write(ByteBuffer.wrap(dBuf, 0, oLen));
        //
        bbuf.clear();
        if ((p = soc.read(bbuf)) <= 0) break;
        ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
      }
    } else { // normal
      fc.write(ByteBuffer.wrap(buf, 0, p));
      while ((p = soc.read(bbuf)) > 0) {
        ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
        fc.write(ByteBuffer.wrap(buf, 0, p));
        bbuf.clear();      
      }
    }
    fc.close();
    soc.close();
    return fileName;
  }
  // Service for FileTransfer
  private class Xfer implements Runnable {
    public Xfer(SocketChannel soc) {
      this.soc = soc;
    }
    private SocketChannel soc;
    public void run() {
      try {
        soc.socket().setTcpNoDelay(true);
        soc.socket().setSendBufferSize(65536);
        soc.socket().setReceiveBufferSize(65536);
        ByteBuffer bbuf = ByteBuffer.allocate(1024);
        //
        int p = soc.read(bbuf);
        byte[] buf = new byte[p];
        ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
        String fileName = new String(buf, 2, p-2, "UTF-8");
        int zip = buf[1] & 0xFF; // Normal, Compress, GZIP: 0, 1, 2
        //
        if (buf[0] == (byte)token[0]) { // same sendToken from remote
          soc.write(ByteBuffer.wrap(new byte[] { (byte)0x00 }, 0, 1)); 
          retrieveFile(soc, fileName, saveDir, zip);
        } else if (buf[0] == (byte)token[1]) { // retrieveToken from remote
          if (fileName.startsWith("$HOME")) fileName = fileName.replace("$HOME", uHome);
          if (!(new File(fileName)).exists()) {
            buf = ("Unknown file: "+fileName).getBytes();
            soc.write(ByteBuffer.wrap(buf, 0, buf.length));
            soc.close();
            return;
          }
          sendFile(soc, fileName, zip);
        } else { // Illegal. Do NOTHING 
          soc.close();
          return;
        }
      } catch (Exception ex) { }
    }
  }
  //
  private int[] token;
  private boolean end = false;
  private ExecutorService pool;
  private String saveDir, uHome;
  private ServerSocketChannel server = null;
  private ConcurrentHashMap<Long, String> msg =  new ConcurrentHashMap<>(256);
  private ConcurrentHashMap<Long, Boolean> done =  new ConcurrentHashMap<>(256);
}

An example in SWING using XFile API

import java.io.*;
import java.net.*;
import java.util.*;
import java.nio.file.*;
import java.awt.event.*;
//
import javax.swing.*;
// Joe Schwarz (C)
public class FileTransfer extends JFrame {
  // XFile API
  private XFile xfile;
  //
  private JTextField tFile, tIP, tXs, tXr, tPort, tSave, tHome;
  private JCheckBox ZIP;
  private String uHome;
  private JLabel msg;
  // java FileTransfer localPort or java FileTransfer
  public static void main(String... argv) throws Exception {
    UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
    new FileTransfer(argv);
  }
  // Constructor
  public FileTransfer(String... argv) throws Exception {
    setTitle("FileTransfer based on XFile API");
    setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
    //
    uHome = System.getProperty("user.home");
    String fName = "$HOME"+File.separator+"AnyName";
    //
    msg = new JLabel("FileTransfer");
    JLabel lHome = new JLabel("HomePort:");
    JLabel lSave = new JLabel("Save Dir: ");
    JLabel lFile = new JLabel("FileName");
    JLabel lIP = new JLabel("remoteIP");
    JLabel lport = new JLabel("Port");
    JLabel lXs = new JLabel("SendToken (1...255)");
    JLabel lXr = new JLabel("RetrieveToken (1...255)");
    //
    tXs = new JTextField("1", 3);
    tXr = new JTextField("2", 3);
    tSave = new JTextField("$HOME"+File.separator+"Downloads"+File.separator, 32);
    tFile = new JTextField(fName, 32);
    tIP = new JTextField(IPv6(), 32);
    tPort = new JTextField("0", 5);
    //
    tPort.addKeyListener((KeyListener)new OnlyNummeric(tPort));
    if (argv.length == 1) {
      tHome = new JTextField(argv[0], 5);
    } else tHome = new JTextField("0", 5);
    tHome.addKeyListener((KeyListener)new OnlyNummeric(tHome));
    tXs.addKeyListener((KeyListener)new OnlyNummeric(tXs));
    tXs.addActionListener(e -> {
      tXr.requestFocusInWindow();
    });
    tXr.addKeyListener((KeyListener)new OnlyNummeric(tXr));
    tFile.addActionListener(e -> {
      tIP.requestFocusInWindow();
    });
    tIP.addActionListener(e -> {
      tPort.requestFocusInWindow();
    });
    JButton SEND = new JButton("SEND");
    JButton RECEIVE = new JButton("RETRIEVE");
    JButton QUIT = new JButton("QUIT");
    //    
    SEND.addActionListener(e -> {
      int rPort = getPort();
      if (rPort == 0) return;
      String fileName = tFile.getText();
      if (fileName.startsWith("$HOME")) fileName = fileName.replace("$HOME", uHome);
      if (!(new File(fileName)).exists()) {
        msg.setText(fileName+" is unknown.");
        return;
      }
      try {
        if (!init()) return;
        int[] token = getToken();
        // XFile.send() and Xfile.getMessage()
        long timeStamp = xfile.send(fileName, tIP.getText(), rPort, token, ZIP.isSelected());
        msg.setText(xfile.getMessage(timeStamp));
      } catch (Exception ex) {
        ex.printStackTrace();
        msg.setText("Failed to connect to Server:"+tPort.getText());
     }
    });
    RECEIVE.addActionListener(e -> {
      int rPort = getPort();
      if (rPort == 0) return;
      try {
        if (!init()) return;
        int[] token = getToken();
        // XFile.retrieve and Xfile.getMessage()
        long timeStamp = xfile.retrieve(tFile.getText(), tSave.getText(),
                         tIP.getText(), rPort, token, ZIP.isSelected());
        msg.setText(xfile.getMessage(timeStamp));
      } catch (Exception ex) {
        ex.printStackTrace();
        msg.setText("Failed to connect to Server:"+tPort.getText());
     }
    });
    QUIT.addActionListener(e -> {
      // XFile.close()
      if (xfile != null) xfile.close();
      System.exit(0);
    });
    ZIP = new JCheckBox("Compressed ?");
    JPanel cm = new JPanel(); cm.add(msg);
    JPanel ch = new JPanel(); ch.add(lHome); ch.add(tHome);
    JPanel ct = new JPanel(); ct.add(lXs); ct.add(tXs); ct.add(lXr); ct.add(tXr);
    JPanel cs = new JPanel(); cs.add(lSave); cs.add(tSave);
    JPanel c0 = new JPanel(); c0.add(lFile); c0.add(tFile);
    JPanel c1 = new JPanel(); c1.add(lIP); c1.add(tIP); c1.add(lport); c1.add(tPort);
    JPanel c2 = new JPanel(); c2.add(ZIP); c2.add(SEND); c2.add(RECEIVE); c2.add(QUIT);
    //
    JPanel center = new JPanel(new java.awt.GridLayout(7, 0));
    center.add(cm); center.add(ch); center.add(ct); center.add(cs);
    center.add(c0); center.add(c1); center.add(c2);
    add(center);
    //
    setSize(600,320);
    setVisible(true);
    init();
  }
  //
  private int getPort( ) {
    int port = 0;
    try {
      port = Integer.parseInt(tPort.getText());
    } catch (Exception ex) { port = 0; }
    if (port == 0) {
      msg.setText("ERROR: Invalid RemotePort: 0");
      tPort.requestFocusInWindow();
    }
    return port;
  }
  //
  private int[] getToken() {
    int[] token = new int[2];
    try {
      token[0] = Integer.parseInt(tXs.getText()) & 0xFF;
    } catch (Exception ex) { token[0] = 0x01; }
    try {
      token[1] = Integer.parseInt(tXr.getText()) & 0xFF;
    } catch (Exception ex) { token[1] = 0x02; }
    if (token[1] == token[0]) {
      if (token[0] == 0xFF) token[1] = 0x01;
      else token[1] = token[0] + 1;
    }
    return token;
  }
  //
  private boolean init() throws Exception {
    if (xfile != null) return true;
    String tmp = tHome.getText();
    int port = 0;
    try {
      port = Integer.parseInt(tmp);
      tXs.requestFocusInWindow();
    } catch (Exception ex) {
      port = 0;
    }
    if (port == 0) {
      msg.setText("ERROR: Invalid HomePort: "+tmp);
      tHome.requestFocusInWindow();
      tHome.setText("0");
      return false;
    }
    // instantiate XFile API
    xfile = new XFile(tSave.getText(), getToken(), port, 64);
    return true;
  }
  // get IPv6 of this localhost
  private String IPv6() {
    try {
      Enumeration nicEnum = NetworkInterface.getNetworkInterfaces();
      while(nicEnum.hasMoreElements()) {
        NetworkInterface ni=(NetworkInterface) nicEnum.nextElement();
        Enumeration addrEnum = ni.getInetAddresses();
        while(addrEnum.hasMoreElements()) {
          InetAddress ia = (InetAddress) addrEnum.nextElement();
          String ipv6 = ia.getHostAddress();
          int p = ipv6.indexOf("%");
          if (ipv6.indexOf(":0:") < 0 && p > 0) return ipv6.substring(0, p);
        }
      }
    } catch(Exception _e) { }
    return "localhost";
  }
  // Only Nummeric for JTextField
  private class OnlyNummeric implements KeyListener {
    public OnlyNummeric(JTextField tf) {
      this.tf = tf;
    }
    private char b;
    private JTextField tf;
    public void keyTyped(KeyEvent e) { }
    public void keyPressed(KeyEvent e) { }
    public void keyReleased(KeyEvent e) {
      String val = tf.getText();
      char a = e.getKeyChar();
      if ((int)a < 0x7F) {
        if (a < '0' || a > '9') tf.setText(val.replace(""+a, ""));
        else if ((int)b > 0x7F && a >= '0' && a <= '9') {
          val = val + a;
          tf.setText(val);
        }
        if (val.length() > 1 && val.charAt(0) == '0') tf.setText(val.substring(1));
      } else tf.setText("0"); // special character -> clear all
      b = a;
    }
  }
}

Have fun!

UPDATE: If you prefer GZIP over ZIP, replace this the Deflater/Inflater with these snippets:

// sendFile
      if (zip == 1) { // GZIP
        GZIPOutputStream gzip = new GZIPOutputStream(soc.socket().getOutputStream(), true);
        while ((p = fc.read(bbuf)) > 0) {
          ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
          gzip.write(buf, 0, p);
          gzip.flush();
          bbuf.clear();
        }
        gzip.close();
      } else { // normal
        ...

// retrieveFile

    int p;
    GZIPInputStream gzip = null;
    byte[] buf = new byte[65536];
    ByteBuffer bbuf = ByteBuffer.allocate(65536);
    if (zip == 1) {
      gzip = new GZIPInputStream(soc.socket().getInputStream());
      p = gzip.read(buf);
    } else {
      p = soc.read(bbuf);
      ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
    }
    if ("Unknown file: ".equals(new String(buf, 0, 14, "UTF-8"))) {
      soc.close();
      return null;
    }
    bbuf.clear();
    int n = fileName.lastIndexOf(File.separator)+1;
    fileName = tSave.getText()+fileName.substring(n);
    if (fileName.startsWith("$HOME")) fileName = fileName.replace("$HOME", uHome);
    FileChannel fc = (new FileOutputStream(fileName, false)).getChannel();
    if (zip == 1) { // GZIP
      fc.write(ByteBuffer.wrap(buf, 0, p));
      while ((p = gzip.read(buf)) > 0) {
        fc.write(ByteBuffer.wrap(buf, 0, p));
      }
      gzip.close();
    } else { // normal
      ....

As mentioned above, GZIP takes longer to compress data than ZIP. Furthermore, communication is based on byte streams, and therefore the transfer time is longer than with ZIP using block transfer.

2 Likes

For the sake of completeness, I’ll show you how to run Multicast IPv6 as your own publish-subscribe application with the Internet instead of a Java Message Service (JMS) server (more about JMS here) . Similar to how the XFile API is a typical peer-to-peer (P2P) PubSub API uses Multicast IPv6 and a port to implement the One-to-Many (similar to publisher-subscribers) communication.

What is Multicast IPv6?

In short, multicast is a form of broadcast communication, similar to television broadcasts. Anyone with a television can receive any channel. Multicast with IPv6 allows you to set up your own group that uses the same IPv6 address and port. That’s all. More about IPv4 and IPv6 multicast: click HERE.

Once instantiated, the PubSub API is a self-starting thread and requires two parameters: an IPv6 address (e.g., ff02::2) and a port number (e.g., 12345). The example One2Many in Java Swing illustrates the use of the PubSub API.

PubSub API. The implemented “queue” is here the Collections.synchronizedList.

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
/**
@author Joe T. Schwarz
*/
public class PubSub extends Thread {
  /**
  Publish-Subscribe networking. A self-starting Thread
  @param IPv6 string IPv6 Multicast see HERE: https://www.catchpoint.com/benefits-of-ipv6/ipv6-multicast-address
  @param port int, Internet port
  @exception throw if MulticastSocket cannot be started due to bad port or other reasons
  */
  public PubSub(String IPv6, int port) throws Exception {
    this.port = port;
    mcs = new MulticastSocket(port);
    group = InetAddress.getByName(IPv6);
    mcs.joinGroup(group);
    running = true;
    this.start();
  }
  //
  public void run() {
    try {
      byte[] buf = new byte[65400];
      while (running) {
        DatagramPacket packet = new DatagramPacket(buf, 65400);
        mcs.receive(packet); // wait for the incoming msg
        msgList.add(new String(packet.getData(), 0, packet.getLength(), "UTF-8"));
      }
    } catch (Exception ex) {
      if (running) ex.printStackTrace();
    }
  }
  /**
   close PubSub
  */
  public void close() {
    running = false;
    if (mcs != null) try {
      mcs.close();
    } catch (Exception ex) { }
  }
  /**
   isOpen
   @return PubSub is running
  */
  public boolean isOpen() {
    return running;
  }
  /**
   get number of messages
   @return int number of message
  */
  public int numberOfMessages() {
    return msgList.size();
  }
  /**
   clear all messages
  */
  public void clear() {
    msgList.clear();
  }
  /**
   publish a message
   @param msg String
   @exception throw if MulticastSocket is NOT running or other reasons
  */
  public void publish(String msg) throws Exception {
    if (!running) throw new Exception("PubSub is not running.");
    mcs.send(new DatagramPacket(msg.getBytes(), msg.length(), group, port));
  }
  /**
   delete a message
   @param idx int Index
   @return String deleted message
   @exception throw if MulticastSocket is NOT running or other reasons
  */
  public String deleteMessage(int idx) throws Exception {
    if (!running) throw new Exception("PubSub is not running.");
    int s = msgList.size();
    if (s == 0 || s < idx || idx < 0) return null;
    return msgList.remove(idx);
  }
  /**
   fetch a message from received List
   @param idx int Index
   @return String null if no message is found
   @exception throw if MulticastSocket is NOT running or other reasons
  */
  public String getMessage(int idx) throws Exception {
    if (!running) throw new Exception("PubSub is not running.");
    int s = msgList.size();
    if (s == 0 || s < idx || idx < 0) return null;
    return msgList.get(idx);
  }
  /**
   fetch the first message from received List
   @return String null if list is empty
   @exception throw if MulticastSocket is NOT running or other reasons
  */
  public String getFirstMessage() throws Exception {
    if (!running) throw new Exception("PubSub is not running.");
    int s = msgList.size();
    if (s == 0) return null;
    return msgList.get(0);
  }
  /**
   fetch the last message from received List
   @return String null if list is empty
   @exception throw if MulticastSocket is NOT running or other reasons
  */
  public String getLastMessage() throws Exception {
    if (!running) throw new Exception("PubSub is not running.");
    int s = msgList.size();
    if (s == 0) return null;
    return msgList.get(s-1);
  }
  /**
   fetch the received List
   @return ArrayList of String
   @exception throw if MulticastSocket is NOT running or other reasons
  */
  public ArrayList<String> getMessageList() throws Exception {
    if (!running) throw new Exception("PubSub is not running.");
    return new ArrayList<>(msgList);
  }
  //
  private int port;
  private InetAddress group;
  private MulticastSocket mcs;
  private boolean running = false;
  private List<String> msgList = Collections.synchronizedList(new ArrayList<>(256));
}

One2Many example

import java.util.*;
import java.awt.event.*;
import java.util.concurrent.*;
//
import javax.swing.*;
// Joe Schwarz (C)
public class One2Many extends JFrame {
  // PubSub API
  private PubSub pubsub;
  //
  private JTextArea tArea;
  private JTextField tMsg, tIPv6, tPort;
  // java One2Many [port]
  public static void main(String... argv) throws Exception {
    UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
    new One2Many(argv);
  }
  // Constructor
  public One2Many(String... argv) throws Exception {
    setTitle("Publish-Subscribe based on PubSub API");
    setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
    //
    tMsg = new JTextField(32);
    tIPv6 = new JTextField("ff02::2", 16);
    tPort = new JTextField("1234",4);
    tArea = new JTextArea("Report Area", 10, 45);
    JScrollPane jsp = new JScrollPane(tArea);
    tPort.addKeyListener((KeyListener)new OnlyNummeric(tPort));
    if (argv.length == 1) {
      tIPv6.setText(argv[0]);
    }
    tMsg.addActionListener(e -> {
      try {
        if (!init()) return;
          pubsub.publish(tMsg.getText());
          TimeUnit.MICROSECONDS.sleep(10);
          tArea.append("\nPublished: "+pubsub.getLastMessage());
          tIPv6.setEnabled(false);
          tPort.setEnabled(false);
      } catch (Exception ex) {
        ex.printStackTrace();
        tArea.append("\nFailed to publish :"+tMsg.getText());
      }
    });
    JComboBox<String> CBOX = new JComboBox<>("FistMessage!LastMessage!@Index!MessageList".split("!"));
    CBOX.addActionListener(e -> {
      try {
        if (!init()) return;
        int idx = CBOX.getSelectedIndex();
        if (idx == 0) {
          String choice = pubsub.getFirstMessage();
          tArea.append("\n1st. Message: "+(choice == null? "No Message.":choice));
        } else if (idx == 1) {
          String choice = pubsub.getLastMessage();
          tArea.append("\nLast Message:"+(choice == null? "No Message.":choice));
       } else if (idx == 3) {
          ArrayList<String> lst = pubsub.getMessageList();
          if (lst.size() > 0) {
            tArea.append("\nList of Messages:");
            for (int n = 0; n < lst.size(); ++n) tArea.append("\nIndex_"+n+": "+lst.get(n));
          } else tArea.append("\nNo Message.");
        } else {
          String choice = JOptionPane.showInputDialog(this, "Index");
          idx = Integer.parseInt(choice);
          String msg = pubsub.getMessage(idx);
          tArea.append("\nMessage("+idx+"): "+(msg == null? "No Message @"+idx:msg));
        }
      } catch (Exception ex) {
        ex.printStackTrace();
        tArea.append("\nFailed to getMessage.");
      }
    });
    JButton EXIT = new JButton("EXIT");
    EXIT.addActionListener(e -> {
      if (pubsub != null) pubsub.close();
      System.exit(0);
    });
    JPanel c0 = new JPanel(); c0.add(new JLabel("IPv6")); c0.add(tIPv6); c0.add(new JLabel("Port")); c0.add(tPort);
    JPanel c1 = new JPanel(); c1.add(new JLabel("Message")); c1.add(tMsg); c1.add(new JLabel("press ENTER to Publish"));
    JPanel c2 = new JPanel(); c2.add(new JLabel("Your Choice")); c2.add(CBOX); c2.add(EXIT);
    JPanel c3 = new JPanel(); c3.add(jsp);
    //
    JPanel top = new JPanel(new java.awt.GridLayout(2, 0));
    top.add(c0); top.add(c1);
    //
    add("North", top);
    add("Center", c3);
    add("South", c2);
    //
    pack();
    setVisible(true);
    init();
  }
  //
  private boolean init() throws Exception {
    if (pubsub != null) return true;
    String tmp = tPort.getText();
    int port = 0;
    try {
      port = Integer.parseInt(tmp);
      tMsg.requestFocusInWindow();
    } catch (Exception ex) {
      port = 0;
    }
    if (port == 0) {
      tArea.append("\nERROR: Invalid Port: "+tmp);
      tPort.requestFocusInWindow();
      tPort.setText("1234");
      return false;
    }
    // instantiate XFile API
    pubsub = new PubSub(tIPv6.getText(), port);
    return true;
  }
  // Only Nummeric for JTextField
  private class OnlyNummeric implements KeyListener {
    public OnlyNummeric(JTextField tf) {
      this.tf = tf;
    }
    private char b;
    private JTextField tf;
    public void keyTyped(KeyEvent e) { }
    public void keyPressed(KeyEvent e) { }
    public void keyReleased(KeyEvent e) {
      String val = tf.getText();
      char a = e.getKeyChar();
      if ((int)a < 0x7F) {
        if (a < '0' || a > '9') tf.setText(val.replace(""+a, ""));
        else if ((int)b > 0x7F && a >= '0' && a <= '9') {
          val = val + a;
          tf.setText(val);
        }
        if (val.length() > 1 && val.charAt(0) == '0') tf.setText(val.substring(1));
      } else tf.setText("0"); // special character -> clear all
      b = a;
    }
  }
}

Enhanced XFile API

import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.file.*;
import java.util.zip.*;
import java.nio.channels.*;
import java.util.concurrent.*;
//
import javax.swing.*;
/**
@author Joe T. Schwarz
*/
public class XFile {
  //
  /** 
   * XFile API is a P2P communication based on SocketChannel and ServerSocketChannel.
   * <b>Two servers communicate with each other. One sends, the other listens (or retrieves). Use the
   * <b>isDone() and getMessage() methods to check the processing status and the message returned by
   * <b>send() or retrieve().
   * <b>
   * Constructor and start server for remote requests. Send and retrieve tokens on both sides must match.
   * <b>
   * @param saveDir String, directory where retrieved files are saved.
   * @param token int array contains sendToken (index 0) and retrieveToken (index 1)
   * @param port int, port number for Xfer Server
   * @param poolSize int, size of ExecutorService pool
   * @exception throw if SeverSocketChannel cannot be started due to bad port or other reasons
  */
  public XFile(final String saveDir, final int[] token, final int port, final int poolSize) throws Exception {
    if (port <= 0) throw new Exception("Invalid ServerPort:"+port);
    this.token = token;
    this.saveDir = saveDir;
    int size = poolSize < 64? 64:poolSize;
    uHome = System.getProperty("user.home");
    try {
      pool = Executors.newFixedThreadPool(size);
      server = ServerSocketChannel.open();
      server.socket().bind(new InetSocketAddress(port));
      server.setOption(StandardSocketOptions.SO_RCVBUF, 65536);
      pool.execute(() -> { // waiting for request in ThreadPool
        try {
          while (!end) pool.execute(new Xfer(server.accept()));
        } catch (Exception ex) {
          if (!end) {
            pool.shutdownNow();
            ex.printStackTrace();
          }
        }
        if (server != null) try {
          server.close();
        } catch (Exception ex) { }
      });
      TimeUnit.MICROSECONDS.sleep(10);
    } catch (Exception ex) { server = null; }
    if (server == null) throw new Exception("Unable to start Server @"+port);
  }
  /**
   * setToken for Send and Retrieve. Nothing is set if token array is larger/less than 2 or both elements are equal.
   * @param token int array contains sendToken (index 0) and retrieveToken (index 1)
  */
  public void setToken(int[] token) {
    if (token.length == 2 && token[0] != token[1]) this.token = token;
  }
  /**
   * Clear Send/Retrieve status
  */
  public void clear() {
    msg.clear();
    done.clear();
  }
  /**
   * Close Server and ThreadPool
  */
  public void close() {
    end = true;
    pool.shutdownNow();
    try { // wait for Pool Service
      TimeUnit.MICROSECONDS.sleep(10);
    } catch (Exception ex) { }
  }
  /**
   * isDone() queries for the processing status of send() or retrieve()
   * @param timeStamp long, the timeStamp returned by send() or retrieve()
   * @return boolean true, send() or retrieve() is finished, false: on process or timeStamp is Unknown.
  */
  public boolean isDone(long timeStamp) {
    Boolean b = done.get(timeStamp);
    if (b == null) return false;
    return b;
  }
  /**
   * getMessage() returned by send() or retrieve()
   * @param timeStamp long, the timeStamp returned by send() or retrieve()
   * @return String message or null if timeStamp is unknown.
  */
  public String getMessage(long timeStamp) {
    Boolean B = done.get(timeStamp);
    if (B == null) return null;
    if (!B) while (!(B = done.get(timeStamp))) try {
      TimeUnit.MICROSECONDS.sleep(5);
    } catch (Exception ex) { }
    return msg.get(timeStamp);
  }
  /**
   * Send a file to remote host at IPv6:remotePort
   * @param fileName String
   * @param ipv6 String, IPv6
   * @param remotePort int
   * @param token int array contains sendToken (index 0) and retrieveToken (index 1)
   * @param compressed boolean, true for compressed
   * @return timeStemp a long value. 0 if send failed
  */  
  public long send(final String fileName, final String ipv6, final int remotePort,
                   final int[] token, final boolean compressed) {
    if (remotePort == 0 || !(new File(fileName)).exists()) return 0;
    final long timeStamp = System.currentTimeMillis();
    done.put(timeStamp, false);
    if (!(new File(fileName)).exists()) {
      msg.put(timeStamp, "Unknown :"+fileName);
      done.put(timeStamp, true);
      return timeStamp;
    }
    pool.submit(() -> {
      try {
        byte[] buf = new byte[65536];
        int zip = compressed? 0x01:0x00;
        SocketChannel soc = SocketChannel.open(new InetSocketAddress(ipv6, remotePort));
        soc.socket().setSendBufferSize(65536);
        soc.socket().setTcpNoDelay(true);
        // sendToken + fileName
        int len = fileName.length();
        buf[0] = (byte)(token[0] & 0xFF); buf[1] = (byte)zip;
        System.arraycopy(fileName.getBytes(), 0, buf, 2, len);
        soc.write(ByteBuffer.wrap(buf, 0, len+2));
        ByteBuffer bbuf = ByteBuffer.allocate(8);
        soc.read(bbuf); // wait for Reply
        long t0 = System.nanoTime();
        if (sendFile(soc, buf, fileName, zip))
          msg.put(timeStamp, String.format("Sent %s - Total(milliSec.): %.2f",fileName,
                                          ((float)(System.nanoTime()-t0)/1000000)));
        else msg.put(timeStamp, "Problem with: "+fileName);
      } catch (Exception ex) {
        ex.printStackTrace();
        msg.put(timeStamp, ex.toString());
      }
      done.replace(timeStamp, true);
    });
    return timeStamp;
  }
  /**
   * retrieve a file from remote host at IPv6:remotePort
   * @param fileName String
   * @param sDir String, directory where the retrieved file is saved
   * @param ipv6 String, IPv6
   * @param remotePort int
   * @param token int array contains sendToken (index 0) and retrieveToken (index 1)
   * @param compressed boolean, true for compressed
   * @return timeStemp a long value. 0 if retrieve failed
  */  
  public long retrieve(final String fileName, String sDir,
                       final String ipv6, final int remotePort,
                       final int[] token, final boolean compressed) {
    if (remotePort == 0) return 0;
    final long timeStamp = System.currentTimeMillis();
    done.put(timeStamp, false);
    pool.submit(() -> {
      try {
        byte[] buf = new byte[65536];
        int zip = compressed? 0x01:0x00;
        SocketChannel soc = SocketChannel.open(new InetSocketAddress(ipv6, remotePort));
        soc.socket().setSendBufferSize(65536);
        soc.socket().setTcpNoDelay(true);
        // retrieveToken + fileName
        int len = fileName.length();
        buf[0] = (byte)(token[1] & 0xFF); buf[1] = (byte)zip;
        System.arraycopy(fileName.getBytes(), 0, buf, 2, len);
        soc.write(ByteBuffer.wrap(buf, 0, len+2));
        String fName = sDir+fileName.substring(fileName.lastIndexOf(File.separator)+1);
        if (fName.startsWith("$HOME")) fName = fName.replace("$HOME", uHome);
        long t0 = System.nanoTime();
        String fn = retrieveFile(soc, buf, fName, zip);
        msg.put(timeStamp, fn == null? fName+" is unknown @"+ipv6+":"+remotePort:
                String.format("Saved %s - Total(milliSec.): %.2f",fn, ((float)(System.nanoTime()-t0)/1000000)));
      } catch (Exception ex) {
        ex.printStackTrace();
        msg.put(timeStamp, ex.toString());
      }
      done.replace(timeStamp, true);
    });
    return timeStamp;
  }
  //
  private boolean sendFile(SocketChannel soc, byte[] buf,
                           String fileName, int zip) throws Exception {
    int p;
    ByteBuffer bbuf = ByteBuffer.allocate(64000);
    FileChannel fc = (new FileInputStream(fileName)).getChannel();
    if (zip == 1) { // Compressed
      byte[] dBuf = new byte[65536];
      Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
      deflater.setStrategy(Deflater.DEFAULT_STRATEGY);
      while ((p = fc.read(bbuf)) > 0) {
        ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
        deflater.reset();
        deflater.setInput(buf, 0, p);
        // original or uncompressed Length
        dBuf[0] = (byte)0x00;
        dBuf[1] = (byte)0x00;
        dBuf[2] = (byte)((p >> 8) & 0xFF);
        dBuf[3] = (byte)( p       & 0xFF);
        deflater.finish();
        p = deflater.deflate(dBuf, 4, 65532);
        soc.write(ByteBuffer.wrap(dBuf, 0, p+4));
        bbuf.clear();
      }
    } else { // Normal
      while ((p = fc.read(bbuf)) > 0) {
        ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
        soc.write(ByteBuffer.wrap(buf, 0, p));
        bbuf.clear();
      }
    }
    fc.close();
    soc.close();
    return true;
  }
  //
  private String retrieveFile(SocketChannel soc, byte[] buf, 
                              String fileName, int zip) throws Exception {
    ByteBuffer bbuf = ByteBuffer.allocate(65536);
    int p = soc.read(bbuf);
    ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
    if (buf[0] == (byte)0xFF && buf[1] == (byte)0xFF) { // unknown
      soc.close();
      return null;
    }
    FileChannel fc = (new FileOutputStream(fileName, false)).getChannel();
    if (zip == 1) { // Compress
      Inflater inflater = new Inflater();
      while (true) {
        // valid compressed block?
        if (buf[0] == (byte)0x00 && buf[1] == (byte)0x00) {
          int oLen = ((buf[2] & 0xFF) << 8) | (buf[3] & 0xFF);
          byte[] dBuf = new byte[oLen];
          // decompress the data
          inflater.reset();
          inflater.setInput(buf, 4, p-4);
          inflater.inflate(dBuf);
          fc.write(ByteBuffer.wrap(dBuf, 0, oLen));
        }
        bbuf.clear();
        p = soc.read(bbuf);
        if (p > 0) ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
        else break;
      }
    } else { // normal
      bbuf.clear();
      fc.write(ByteBuffer.wrap(buf, 0, p));
      while ((p = soc.read(bbuf)) > 0) {
        ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
        fc.write(ByteBuffer.wrap(buf, 0, p));
        bbuf.clear();      
      }
    }
    fc.close();
    soc.close();
    return fileName;
  }
  // Service for FileTransfer
  private class Xfer implements Runnable {
    public Xfer(SocketChannel soc) {
      this.soc = soc;
    }
    private SocketChannel soc;
    public void run() {
      try {
        soc.socket().setTcpNoDelay(true);
        soc.socket().setSendBufferSize(65536);
        soc.socket().setReceiveBufferSize(65536);
        ByteBuffer bbuf = ByteBuffer.allocate(1024);
        //
        int p = soc.read(bbuf);
        byte[] buf = new byte[65536];
        ((ByteBuffer)bbuf.flip()).get(buf, 0, p);
        String fileName = new String(buf, 2, p-2, "UTF-8");
        int zip = buf[1] & 0xFF; // Normal, Compress: 0, 1
        if (buf[0] == (byte)token[0]) { // same sendToken from remote
          fileName = saveDir+fileName.substring(fileName.lastIndexOf(File.separator)+1);
          if (fileName.startsWith("$HOME")) fileName = fileName.replace("$HOME", uHome);
          soc.write(ByteBuffer.wrap(new byte[] { (byte)0x00 }, 0, 1));
          retrieveFile(soc, buf, fileName, zip);
        } else if (buf[0] == (byte)token[1]) { // retrieveToken from remote
          if (fileName.startsWith("$HOME")) fileName = fileName.replace("$HOME", uHome);
          if (!(new File(fileName)).exists()) { // report Unknown file
            soc.write(ByteBuffer.wrap(new byte[] { (byte)0xFF, (byte)0xFF }, 0, 2));
            soc.close();
            return;
          }
          sendFile(soc, buf, fileName, zip);
        } else { // Illegal. Do NOTHING 
          soc.close();
          return;
        }
      } catch (Exception ex) { }
    }
  }
  //
  private int[] token;
  private boolean end = false;
  private ExecutorService pool;
  private String saveDir, uHome;
  private ServerSocketChannel server = null;
  private ConcurrentHashMap<Long, String> msg =  new ConcurrentHashMap<>(256);
  private ConcurrentHashMap<Long, Boolean> done =  new ConcurrentHashMap<>(256);
}
1 Like

A bonus.

Talking about network programming without mentioning web servers is somehow incomplete. A web server is usually a large and complex undertaking, which often deters individual developers. Nginx and Apache, for example, are the best-known and most widely used web servers. However, developing a simple web server is actually easier than most people think. The only prerequisites are basic knowledge of HTML and network communication.

  • HTML for website development.
  • Network programming in Java/C++/Python/etc. for a web server.

Below, I’ll show you a Web Service API and its application as a web server. As you can see, it’s not so complicated that you should hesitate…To run the web server, you need to create an HTML folder containing all the web pages you want to display on the web (or in a browser). Here is the folder joe with two HTML pages:

  • Auerhahn.html (with Auerhahn.png)
  • cnn.html (a copy of the CNN.com web page from October 31, 2025)

Note: If you are using an IPv6 address instead of localhost, you must enclose the IPv6 address in square brackets, followed by a colon and the port. Example:

[2a02:8071:5302:2160:6f3e:a097:1262:f25d]:22222

WebService API

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
//
import java.nio.*;
import java.nio.file.*;
import java.nio.channels.*;
/**
@author Joe Schwarz (C)
WebService with SocketChannel and ServerSocketChannel.<br>
Note: WebService self starts after instantiation and if WebService is run on LINUX, files/directories<br>
are case-sensitive.<br>
*/
public class WebService extends Thread {
  /**
  contructor. 
  @param host String, e.g. localhost or IPv4 or IPv6
  @param port int, Service port number,
  @exception Exception thrown by JAVA
  */
  public WebService(String logDir, String host, int port) throws Exception {   
    this.host = host; 
    this.port = port;
    this.logDir = logDir;
    pool = Executors.newFixedThreadPool(1024);
    // hook on for monitoring exit
    Runtime.getRuntime().addShutdownHook(new Thread() {
      public void run() {
        closeAll();
      }
    });
    this.start(); // self-start
  }
  /**
  setLog
  @param on boolean, true: logging, false: no logging
  @exception IOException thrown by Java
  */
  public synchronized void setLog(boolean on) throws IOException {
    if (!on) { // no logging
      if (logging) {
        writeLog("WebService is closed");
        log.flush();
        log.close();
      }
      logging = false;
      return;
    }
    if (logging) return;
    File file = new File(logDir);
    if (!file.exists()) file.mkdir();
    log = new FileOutputStream(logDir+File.separator+"log_"+
                               String.format("%6X", System.currentTimeMillis())+".txt");
    writeLog("Start Logging for WebService");
    logging = true;
  }
  /**
  Thread run()
  */
  public void run() {
    isOpen = true;
    try {
      server = ServerSocketChannel.open();
      server.socket().bind(new InetSocketAddress(host, port));
      // waiting for requests from Browsers / Clients
      while(isOpen) {
        pool.execute((new Worker(server.accept())));
      }
    } catch (Exception ex) {
      if (isOpen) writeLog("Cannot start WebServer "+host+":"+port);
    }
    closeAll();
  }
  /**
  closeAll.
  */
  public void closeAll() {
    isOpen = false;
    try {
      server.close();
      if (logging) {
        writeLog("WebService is closed");
        log.flush();
        log.close();
      }
    } catch (Exception ex) { }
    if (pool != null) pool.shutdownNow();
  }
  //--------------------------------------------------------------------
  private class Worker implements Runnable {
    public Worker(SocketChannel socket) {
      this.socket = socket;
    }
    //
    private SocketChannel socket;
    //
    public void run() {
      try {
        Socket soc = socket.socket();
        soc.setSendBufferSize(65536);
        InetAddress ia = soc.getInetAddress();
        ByteArrayOutputStream bao = new ByteArrayOutputStream(65536);
        ByteBuffer rbuf = ByteBuffer.allocate(1024);
        soc.setTcpNoDelay(true);
        //
        int n = socket.read(rbuf);
        byte[] buf = new byte[n];
        ((ByteBuffer)rbuf.flip()).get(buf, 0, n);
        String req = new String(buf).trim();
        String IP = ia.getHostAddress();
        //
        byte[] CRLF = {'\r','\n' };
        ByteArrayOutputStream bas = new ByteArrayOutputStream(65536);
        String BWR = "Chrome";
        String OS = "(Unknown OS";
        n = req.indexOf("User-Agent: ");
        if (n > 0) OS = req.substring(req.indexOf("(", n), req.indexOf(")", n));
        OS += "; IP: "+IP+")";
        //
        if (req.indexOf("OPR/", n) > 0) BWR = "Opera";
        else if (req.indexOf("Edg/", n) > 0) BWR = "MS-Edge";
        else if (req.indexOf("Firefox/", n) > 0) BWR = "Firefox";
        
        n = req.indexOf(" /") + 2;
        String fName = req.substring(n, req.indexOf(" ", n));
        writeLog(BWR+" "+OS+" requests "+fName);
        File file = new File(fName);
        if (!file.exists()) {
          bas.write(("WebPage: \""+fName+"\" not found.\r\n").getBytes());
          writeLog("....unknown \"/"+fName+"\"");
          req = "text/html\r\n";
        } else {
          req = fName.toLowerCase();
          bas.write(Files.readAllBytes(file.toPath()));
          if (req.endsWith(".jpg") || req.endsWith(".png") || req.endsWith(".gif") ||
              req.endsWith(".jpeg") || req.endsWith(".bmp")) req = "image/*\r\n";
          else req = "text/html\r\n";
        }
        bao.write(("HTTP/1.1 200 OK\r\n"+
                   "Connection: keep-alive\r\n"+
                   "Cache-Control: no-cache\r\n"+
                   "Cache-Control: no-store\r\n"+
                   "Content-Type: "+req+
                   "Content-Length: "+bas.size()+"\r\n\r\n"                     
                  ).getBytes()
                 );
        bao.write(bas.toByteArray());
        bao.write(CRLF);
        bao.flush();
        buf = bao.toByteArray();
        socket.write(ByteBuffer.wrap(buf, 0, buf.length));
        writeLog(BWR+" "+OS+" quits");
      } catch (Exception ex) { }
      try {
        socket.close();
      } catch (Exception e) { }
    }
  }
  //
  private synchronized void writeLog(String msg) {
    if (logging) try {
      log.write((msg+System.lineSeparator()).getBytes());
      log.flush();
    } catch (Exception e) {  }
  }
  //--------------------------------------------------------------------------
  private int port;
  private String logDir, host;
  private ExecutorService pool;
  private FileOutputStream log;
  private ServerSocketChannel server;
  private boolean logging, isOpen = false;
}

Example WebServer

import javax.swing.*;
import java.util.concurrent.*;
//
import java.net.*;
import java.util.*;
import java.nio.file.*;
import java.awt.event.*;
/** 
 @author Joe Schwarz (C)
 Example of WebService as WebServer
 Note: if WebServer is run on LINUX, files/directories are case-sensitive. 
*/

public class WebServer extends JFrame {
  // java WebServer logDie IPv6 port
  public static void main(String... argv) throws Exception {
    UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
    new WebServer(argv);
  }
  // Constructor
  public WebServer(String... argv) throws Exception {
    setTitle("WebServer");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setIconImage((new ImageIcon(javax.imageio.ImageIO.read(WebServer.class.
                  getResourceAsStream("icons/theWeb.png")))).getImage());
    
    //
    JTextField tLog  = new JTextField(argv.length > 0? argv[0]:"$HOME"+java.io.File.separator+"log", 32);
    JTextField tIPv6 = new JTextField(argv.length > 1? argv[1]:IPv6(), 32);
    JTextField tPort = new JTextField(argv.length > 2? argv[2]:"22222", 5);
    tPort.addKeyListener((KeyListener)new OnlyNummeric(tPort));
    //
    JPanel pn = new JPanel(); pn.add(new JLabel("Logging Dir")); pn.add(tLog);
    JPanel pc = new JPanel(); pc.add( new JLabel("IPv6")); pc.add(tIPv6); pc.add(new JLabel("Port")); pc.add(tPort);
    //
    JButton LOG = new JButton("NO LOG");
    LOG.setEnabled(false);
    LOG.setForeground(java.awt.Color.BLUE);
    LOG.setBackground(java.awt.Color.GRAY);
    LOG.addActionListener(a -> {
      boolean log = "NO LOG".equals(LOG.getText());
      try {
        if (!log) {
          service.setLog(false);
          LOG.setText("NO LOG");
          LOG.setBackground(java.awt.Color.GRAY);
        } else {
          LOG.setText("LOG");
          service.setLog(true);
          LOG.setBackground(java.awt.Color.RED);
        }
      } catch (Exception ex) {
        ex.printStackTrace();
        JOptionPane.showMessageDialog(this,"Unable to set LOG("+log+").\nReason: "+ex.toString());
      }
    });
    JButton GO = new JButton("START");
    GO.addActionListener(e -> {
      try {
        service = new WebService(tLog.getText().replace("$HOME", System.getProperty("user.home")), 
                                 tIPv6.getText(), 
                                 Integer.parseInt(tPort.getText())
                                );
        tLog.setEnabled(false); tIPv6.setEnabled(false); tPort.setEnabled(false);
        GO.setBackground(java.awt.Color.GREEN);
        LOG.setEnabled(true);
      } catch (Exception ex) {
        ex.printStackTrace();
        System.exit(0);
      }
    });
    JButton EXIT = new JButton("EXIT");
    EXIT.setBackground(java.awt.Color.RED);
    EXIT.addActionListener(e -> {
      if (service != null) service.closeAll();
      System.exit(0);
    });
    JPanel ps = new JPanel(); ps.add(GO); ps.add(LOG); ps.add(EXIT);
    //
    add("North", pn);
    add("Center", pc);
    add("South", ps);
    //
    pack();
    setVisible(true);
  }
  // get IPv6 of this localhost
  private String IPv6() {
    try {
      Enumeration nicEnum = NetworkInterface.getNetworkInterfaces();
      while(nicEnum.hasMoreElements()) {
        NetworkInterface ni=(NetworkInterface) nicEnum.nextElement();
        Enumeration addrEnum = ni.getInetAddresses();
        while(addrEnum.hasMoreElements()) {
          InetAddress ia = (InetAddress) addrEnum.nextElement();
          String ipv6 = ia.getHostAddress();
          int p = ipv6.indexOf("%");
          if (ipv6.indexOf(":0:") < 0 && p > 0) return ipv6.substring(0, p);
        }
      }
    } catch(Exception _e) { }
    return "localhost";
  }
  // Only Nummeric for JTextField
  private class OnlyNummeric implements KeyListener {
    public OnlyNummeric(JTextField tf) {
      this.tf = tf;
    }
    private char b;
    private JTextField tf;
    public void keyTyped(KeyEvent e) { }
    public void keyPressed(KeyEvent e) { }
    public void keyReleased(KeyEvent e) {
      String val = tf.getText();
      char a = e.getKeyChar();
      if ((int)a < 0x7F) {
        if (a < '0' || a > '9') tf.setText(val.replace(""+a, ""));
        else if ((int)b > 0x7F && a >= '0' && a <= '9') {
          val = val + a;
          tf.setText(val);
        }
        if (val.length() > 1 && val.charAt(0) == '0') tf.setText(val.substring(1));
      } else tf.setText("0"); // special character -> clear all
      b = a;
    }
  }
  private WebService service = null;
  private ExecutorService pool;
}

Auerhahn.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Auerhahn or Capercaillie</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="Webcam" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  </head>
  <body>

      <style>
          img,body {
              padding: 0px;
              margin: 0px;
          }
      </style>

      <img id="img" src="http://localhost:22222/joe/Auerhahn.jpg">
  </body>
</html>

(on Chrome)

(on FireFox)

and the Auerhahn.jpg

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?