Create a Shared Peer-to-Peer Clipboard

Create a Shared Peer-to-Peer Clipboard

by Zack Grossbart on November 30, 2007

I often work with two computers simultaneously. Each of those computers runs a different operating system. That work often includes the need send long strings (like long URLs) from one computer to the other. It isn’t easy. I have seen all kinds of solutions to this problem including emailing the strings back and forth or checking them into a version control system. I have even seen people maintain a web server just to post strings to copy.

This application presents a very simple user interface which enables you to copy a string from one machine and paste it to the other. It is called NetPaste. NetPaste is a peer-to-peer application. Most networked applications are client-server applications, which separate the computers in the network into clients and servers. The clients talk directly to the servers and make requests. The servers simply respond to requests from the client. The clients never talk to each other. This is the way web browsing works.

Peer-to-peer (or P2P) lets each computer be both a client and a server. The computers contact each other and make requests going in both directions. Each client can provide services to, or request services from, another client. Napster was a very famous peer-to-peer application.

NetPaste is much simpler than Napster. It also avoids the dubious legal status. NetPaste connects two computers and allows them to paste strings to each other’s clipboards.

This sample is a Java client application written using Java Swing. It should run on Windows, Linux, MacOS, and any other platform where Java is available. This application requires a Java Development Kit version 1.5 or greater. If you don’t have JDK 1.5 you can download it from http://www.javasoft.com.

The code in this application is free and is released under the Apache 2.0 license. That means you are welcome to use, copy, and change this code as much as you would like. If you find any bugs, have any comments, or make any improvements I would love to hear from you. Other programs are needed to run this application. They are all free, but some of them use different licenses. You should make sure to read and understand each license before using a product.

Setup

Get the
source code

You should start by downloading the source code for this sample. It can be found here. Once you have downloaded it you can unzip it to a work directory on your computer.

This program has been setup to be built using Apache Ant. Ant is a very popular tool for building Java projects. Building this project without Ant is possible, but it can be tricky to set up. If you don’t have Ant already you should download and install it.

Once you have installed Ant you can build and run the sample application by following these steps:

  1. Unzip the sample archive to a directory on your machine.
  2. Open a command prompt and change directories to the location you unzipped the sample to.
  3. Execute the command ant (you may need to provide the full path to your Ant installation).
  4. Copy the netpaste.jar file to another machine (you can also run it twice on the same machine).
  5. Execute the command java -jar netpaste.jar <localport> <remote host> <remoteport> to run the sample.

For example, to run two instances on the same machine you could use the following commands:


    java -jar netpaste.jar 8080 localhost 8081
    java -jar netpaste.jar 8081 localhost 8080

This application has a deceptively simple UI. The goal is to make this an unobtrusive as possible. Once you have it running you can test it out by copying a string to your clipboard from another application, switching focus back to the NetPaste window, and pressing Ctrl+V.
netpasteui

Core Technologies

This program will use the following core technologies:

  • Java Swing
  • Java Threading
  • Java IO and Sockets

This sample is a Java Swing application. It uses very basic Swing components to render the user interface. It also acts as a server and creates a separate thread to receive requests from another client. To act as a server it will use Java networking in the form of sockets. Lastly, it will send messages back and forth over those sockets using the Java IO libraries.

How It Works

The concept behind NetPaste is very simple. Alice and Bob each runs a copy of NetPaste. Alice can have her copy listen on port 8080 and connect to Bob’s computer with a command like this:

    java -jar netpaste.jar 8080 BobsComputer 8080

Bob will have his copy connect to Alice’s computer with a command like this:

    java -jar netpaste.jar 8080 AlicesComputer 8080

Once they have each started NetPaste they can paste strings back and forth. When Alice pastes a string her copy of NetPaste will connect to Bob’s machine and send the string to be pasted. Bob’s NetPaste will then copy the string into Bob’s clipboard. In this case Alice’s machine in the client and Bob’s machine is the server. When Bob pastes a string the process will be reversed. In that case Alice’s machine is the server and Bob’s machine is the client.

netpaste

Let’s Look at the Code

The code for this application is all stored in NetPaste.java. All of the code for the UI, the client, the server, and the communications are in this one small file.

The Server

The server listens for connections and receives the pasted string after it is sent. The server is implemented by a private inner class named ServerThread. This class extends java.lang.Thread so it can be run in a separate thread within the Java Virtual Machine. It is very important that the server runs in a background thread so the user interface can run in the foreground thread. The reason for this will be addressed later. The ServerThread class has only one method. It is named run. Here is a simplified version of that method:

public void run()
{
    while (true) {
        if (m_ss.isClosed()) {1
            return;
        }

        Socket sock = m_ss.accept();2

        InputStreamReader in = new InputStreamReader(sock.getInputStream(), "UTF-8");3
        while (true) {4
            char c;
            boolean done = false;
            try {
                c = (char) in.read();5
            } catch (IOException e) {
                System.out.println(e.getMessage());
                done = true;
                c = END;
            }

            if (c == END || c == -1) {
                continue;6
            } else if (c == 65535) {
                break;7
            } else {
                m_string.append(c);8
            }
        }
    }
}

This method will run in a loop for as long as the application is running. The loop will wait for a string to be sent, read that string, and start again. The first step is to make sure the server hasn’t been stopped by another thread1. This will happen when the application is shutting down. The next step is to bind the server socket2. We have already created a java.net.ServerSocket object earlier in the code so all we need to do is tell it to start accepting connections. When it starts accepting connections it will return a java.net.Socket object we can get information from.

Now that the socket has been created we must read from it. In order to do that we will create a reader3. It is important that we create a reader rather than an input stream since we want to support pasting in multiple languages. If we just used a stream then this application would only support English characters. We will specify the encoding UTF-8. UTF-8 is included with Java and it supports serializing non-English characters.

Once we have the stream we are ready to read the characters from it. We don’t know how many characters there will be, so we will use a second loop to keep reading until we force it to stop4. And then we wait5. When the thread attempts to read a character from the socket’s reader it will pause until a character is received. This is the reason we need a second thread. If this happened in the UI thread then the UI would stop functioning. Once a character comes in we need to figure out what type of character it is. There are special characters which tell us if the specific string is finished6 or if the stream has been closed from the other computer7. All other characters are part of the pasted string8. You can find the full implementation of this method including error handling in the source code.

The Client

The client portion of this program handles sending the pasted string to the server part of the program running on a different computer. This happens in the doPaste method. Here is a simplified version of that method.

private void doPaste()
{
    String s = getClipboard();1

    if (!m_hasConnected) {
        Socket sock = new Socket(m_host, m_port);2
        m_out = new OutputStreamWriter(sock.getOutputStream(), "UTF-8");3

    }

    m_out.write(s);4
    m_out.write((char) END);5
    m_out.flush();6
}

The paste method has two major functions. It will create the connection to the server, and it will send the string through that connection. The first step is to get the string from the local clipboard1. Once that is done we need to see if we need to create a connection2 or if we can use an existing one. After we have the new connection we can get the writer from that connection3 so we can send the data. We use a writer instead of a stream for the same reasons as in the server portion of this application: we want to support non-English characters. It is very important that we use the same encoding in both the server and client. If we don’t then the characters will be corrupted.

Once we have the connection, sending the string is a simple process. We just need to write out the string4, write out the special character to signify the end of the string5, and flush out the contents of the writer6.

Updating the UI

Now that we know how the data is received on the server and how the data is sent from the client the last step is to update the UI. The server thread runs in the background and will store the data in a local variable. The server thread can’t interact with the user and it can’t put the string in the user’s clipboard. Both of those operations must happen in the UI thread. The mechanism to update the UI is a timer. A timer is a special object that causes an event to be fired at a specific time. The timer in our application will go off every 1.5 seconds. When it goes off it will call the actionPerformed method. Here is a simplified version of that method.

public void actionPerformed(ActionEvent e)
{
    if (m_string.length() == 0) {1
        return;
    }

    String s = null;
    synchronized (m_string) {2
        s = m_string.toString();3
        m_string.delete(0, m_string.length());4
    }

    if (JOptionPane.showConfirmDialog(m_main, "Do you want to copy the string:\n\n" + s) ==
        JOptionPane.YES_OPTION) {5
        setClipboard(s);6
    }

}

When the server receives a string it will hold it in a StringBuffer. The first step it to look in that StringBuffer and see if there is a new string1. Once we know there is a new string we want to copy it out so we can be sure to make space for the next string. To do this we will use a special Java keyword called synchronized2. Once we have locked the variable we will make a copy of it3. This will make sure that only the current thread can access that variable and ensure that the server thread doesn’t start writing a new string before we are done. After we copy the string to a local variable we will clear out the shared variable so it can be used for the next string4.

After we have the string we want to prompt the user to see if they want to keep it5. It could be a bit of a security problem to add a string to the clipboard without asking. If the user does want to keep the string then all we need to do is send it to the clipboard6.

Why This Application Isn’t Completely Thread Safe

When writing code with multiple threads you always need to be careful about thread safety. Thread safety is the practice of making sure that one thread will not overwrite or corrupt the data of another thread. In this case we are using the synchronized keyword to make that happen. However, there is a subtle bug in this code. When the server thread stores the string which has come in it uses a single variable. When it gets the next string it will simply append it to that variable. This means there is a possibility that more than one string will get added before the timer goes off. If this happened the two pasted strings could be concatenated into one.

In practice this isn’t a large concern and we want to keep the code simple for this sample. There is only one client for each server so the load will never be very high. However, considering thread safety is always a good idea and this code is not strictly thread safe. The solution to this problem would be to add a queue of pasted string which could be read from when the timer is called.

Personal Firewalls

If you are running a personal firewall you may need to disable it or change it to allow this specific application. You can adjust your personal firewall settings using the tools that came with your operating system.

Conclusion

This program is a simple peer-to-peer application that serves a useful purpose. It also provides a core understanding of network communications in Java. This application is a framework, which could be expanded upon. This program could be adapted to send files instead of strings. It could provide information about a system running remotely.

The samples in this article are simplified versions of the actual source code. The source code contains examples of Java Swing UI, reading and writing from a clipboard, listening for keyboard events, using mouse movements to move the application window, and using properties to store application state. Please take a look at the source code for more information and more comments.

Part of the image in this document was made using an image by Dr. E.F. Tymac. That image and the resulting image are both licensed under the Creative Commons Attribution-ShareAlike 2.5 License.

  • janiceenberg

    Just saying hello to you all .. Still a bit confused about the goings on here, but I guess I’ll keep poking around.