跳转至

15 Networking and Threads

All the low-level networking details are taken care of by classes in the java.net library. One of Java's big benefits is that sending and receiving data over a network is just I/O with a slightly different connection stream at the end of the chain.

1 Socket Connections

Connect:Send:Receive

Make a Network Socket Connection

To connect to another machine, We need a Socket connection. A Socket (java.net.Socket) is an object that represents a network connection between two machines. A connection is a relationship between two machines, where two pieces of software know about each other.

To make a Socket connection, you need to know two things about the server: who it is, and which port it’s running on. In other words, IP address and TCP port number.

// 196.164.1.103 is the IP address for the sever
// 5000 is TCP port number
Socket chatSocket = new Socket("196.164.1.103", 3000);

A TCP port is just a 16-bit number that identifies a specific program on the server.

The TCP port numbers from 0 to 1023 are reserved for well-known services. Don’t use them for your own server programs!

Well-known TCP port numbers for common server applications:

well-known TCP port numbers for common server applications

Reading/Writing From/To a Socket

To read data from a Socket, use a BufferedReader.

To communicate over a Socket connection, you use regular I/O streams. One of the coolest features in Java is that the most of your I/O work won't care what you high-level chain stream is actually connected to.

  • Make a Socket connection to the server
    • Socket chatSocket = new Socket("127.0.0.1", 3000);
  • Make an InputStreamReader chained to the Socket’s low-level (connection) input stream
    • InputStreamReader stream = new InputStreamReader(chatSocket.getInputStream());
  • Make a BufferedReader and read!
    • BufferedReader reader = new BufferedReader(stream);
    • String message = reader.readLine();

read data from a Socket

To write data to a Socket, use a PrintWriter

  • Make a Socket connection to the server
    • Socket chatSocket = new Socket("127.0.0.1", 5000);
  • Make an PrintWriter chained to the Socket’s low-level (connection) input stream
    • PrintWriter writer = new PrintWriter(chatSocket.getOutputStream());
  • Write (print) Something!
    • writer.println("message to send);
    • writer.print("another message");

WRITETOSOCKETS

Writing a client

  • Client connects to the server and gets an input stream from it
    • Socket socket = new Socket("127.0.0.1", 3000)
    • InputStreamReader streamReader = new InputStreamReader(socket.getInputStream());
    • BufferedReader reader = new BufferedReader(streamReader)
  • Client reads a message from the server
    • message = reader.readLine();

A simple demo program, which reads index.html from my personal blog "larryim.cc", is illustrated below. The format of HTTP Request is discussed in CSAPP.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class MyBlog {
    public void http_request(){
        try{
            Socket s = new Socket("192.30.252.153", 80);
            InputStreamReader streamReader = 
                new InputStreamReader(s.getInputStream());
            BufferedReader br = new BufferedReader(streamReader);

            //注意换行符是\r\n
            writer.println("GET /index.html HTTP/1.0 \r\n" 
                    + "Host: larryim.cc  \r\n   \r\n ");

            String line = null;
            while ((line= br.readLine())!= null)
                System.out.println(line);
            br.close();
        } catch (Exception ex){
            ex.printStackTrace();
        }
    }

    public static void main(String[] args){
        MyBlog client = new MyBlog();
        client.http_request();
    }
}

Writing a Simple Server

  • Server application makes a ServerSocket, on a specific port
    • ServerSocket serverSock = new ServerSocket(4242);
  • Client makes a Socket connection to the server application
    • Socket sock = new Socket("190.165.1.103", 4242);
  • Server makes a new Socket to communicate with this client
    • Socket sock = serverSock.accept();

2 Threads

To make a thread, make a Thread. A Thread is a java class that represents a thread. You'll create an instance of class Thread each time you want to start up a new thread of execution.

A thread is a separate thread of execution. That means a separate stack. Remember, Java is just a process running on your underlying OS. The JVM switches between the new thread (user thread) and the original main thread, until both threads complete.

Launching a thread

  • Make a Runnable object (the thread’s job)
    • Runnable threadJob = new MyRunnable();
    • Runnable is an interface you’ll learn about on the next page. You’ll write a class that implements the Runnable interface, and that class is where you’ll define the work that a thread will perform. In other words, the method that will be run from the thread’s new call stack.
  • Make a Thread object(the worker) and give it a Runnable(the job)
    • Thread myThread = new Thread(threadJob);
    • Pass the new Runnable object to the Thread constructor. This tells the new Thread object which method to put on the bottom of the new stack—the Runnable’s run() method.
  • Start the Thread
    • myThread.start();
    • Nothing happens until you call the Thread’s start() method. That’s when you go from having just a Thread instance to having a new thread of execution. When the new thread starts up, it takes the Runnable object’s run() method and puts it on the bottom of the new thread’s stack.

Runnable is to a Thread what a job is to a worker. A Runnable is the job a thread is supposed to run.

Runnable interface

To make a job for your thread, implement the Runnable interface.

// Runnable is in the java.lang package, so you don't need to import it.
public class RunThreads implements Runnable {

    @Override
    // Runnable has only one method to implement: public void run() (with no arguments)
    // This is where you put the JOB the thread is suposed to run.
    // This is the method that goes at the bottom of the new stack()
    public void run() {
        for (int i=0; i<20; i++){
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " is running.");
        }
    }

    public static void main(String[] args){
        RunThreads jobs = new RunThreads();
        // Pass the new Runnable instance into the new Thread constructor.
        // This tells the thread what method to put on the bottom of the new stack.
        // In other words, the first method that the new thread will run.
        Thread threadA = new Thread(jobs);
        Thread threadB = new Thread(jobs);

        threadA.setName("Thread A");
        threadB.setName("Thread B");

        // You won't get a new thread of execution until you call start() on the Thread instance.
        threadA.start();
        threadB.start();

    }
}

The three states of a new thread:

TheThreeStatesOfANewThread

Once the thread becomes runnable, it can move back and forth between runnable, running, and an additional state: temporarily not runnable (also known as 'blocked').

Typically, a thread moves back and forth between runnable and running, as the JVM thread scheduler selects a thread to run and then kicks it back out so another thread gets a chance.

Typical runnable:running loopS

A thread scheduler can move a running thread into a blocked state, for a variety of reasons. See Understanding JVM.

Thread Scheduler

The thread scheduler makes all the decisions about who runs and who doesn’t.

Most importantly, there are no guarantees about scheduling! So DO NOT base your program's correctness on the scheduler working in a particular way! You multi-threaded program must work no matter how the thread scheduler behaves.

The thread's sleep() method does come with one guarantee: a sleeping thread will not become the currently-running thread before the length of its sleep time has expired.

Note

Another way of making a thread is to, make a subclass of Thread and override the Thread's run() method, instead of Runnable implementation. However, it's rarely a good idea - not a good OO design.

Lock

The lock works like this:

lock

Use the synchronized keyword to modify a method so that only one thread at a time can access it. The synchronized keyword means that a thread needs a key in order to access the synchronized code.

Every Java object has a lock. A lock has only one key. Most of the time, the lock is unlocked and nobody cares. But if an object has synchronized methods, a thread can enter one of the synchronized methods ONLY if the key for the object’s lock is available. In other words, only if another thread hasn’t already grabbed the one key.

3 Example: A Simple ChatApp

public class ChatClient {
    JFrame frame;
    String out_message;     // message sent to a server
    ScrollPane scroll;
    JTextArea inMessage;      // incoming message from a server
    JTextField outTextField;
    JButton button;         // a button to send message
    PrintWriter writer;
    BufferedReader reader;
    Thread backgroundJob = new Thread(new ReceiveMessage());

    private void establishConnection() {
        try {
            Socket socket = new Socket("127.0.0.1", 3000);
            writer = new PrintWriter(socket.getOutputStream());
            InputStreamReader streamReader = 
                    new InputStreamReader(socket.getInputStream());
            reader = new BufferedReader(streamReader);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private void gui() {
        frame = new JFrame("Ludirously Simple Chat Client");

        inMessage = new JTextArea(7, 14);
        outTextField = new JTextField(18);
        button = new JButton("Send");
        button.setSize(300, 40);
        scroll = new ScrollPane();
        scroll.add(inMessage);
        inMessage.setLineWrap(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.getContentPane().add(BorderLayout.NORTH, scroll);
        frame.getContentPane().add(BorderLayout.WEST, outTextField);
        frame.getContentPane().add(BorderLayout.EAST, button);
        button.addActionListener(new ButtonListener());
        frame.setSize(300, 200);
        frame.setVisible(true);
    }


    class ButtonListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            out_message = outTextField.getText();
            outTextField.setText("");
            outTextField.requestFocus();
            writer.println(out_message);
            writer.flush();
        }
    }


    class ReceiveMessage implements Runnable {

        @Override
        public void run() {
            while (true) {
                String line = null;
                try {
                    while ((line = reader.readLine())!= null) {
                        inMessage.append(line);
                    }
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }// end while
        } // end run
    } // end class

    public void start() {
        gui();
        establishConnection();
        backgroundJob.start();


    }

    public static void main(String[] args){
        ChatClient client = new ChatClient();
        client.start();
    }


}
public class ChatServer{
    ArrayList clientOutputStreams;

    public class ClientHandler implements Runnable {
        BufferedReader reader;
        Socket sock;

        public ClientHandler(Socket clientSocket) {
            try {
                sock = clientSocket;
                InputStreamReader isReader = 
                    new InputStreamReader(sock.getInputStream());
                reader = new BufferedReader(isReader);

            } catch (Exception ex) { ex.printStackTrace(); }
        }

        public void run() {
            String message;
            try {
                while ((message = reader.readLine()) != null) {
                    System.out.println("read " + message);
                    tellEveryone(message);
                }
            } catch (Exception ex) { ex.printStackTrace(); }
        }
    }

    public static void main(String[] args) {
        new ChatServer().go();
    }

    public void go() {
        clientOutputStreams = new ArrayList();
        try {
            ServerSocket serverSock = new ServerSocket(3000);
            while(true) {
                Socket clientSocket = serverSock.accept();
                PrintWriter writer = 
                    new PrintWriter(clientSocket.getOutputStream());
                clientOutputStreams.add(writer);

                Thread t = new Thread(new ClientHandler(clientSocket));
                t.start();
                System.out.println("got a connection");
            }
        } catch (Exception ex) { ex.printStackTrace(); }
    }

    public void tellEveryone(String message) {
        Iterator it = clientOutputStreams.iterator();
        while (it.hasNext()) {
            try {
                PrintWriter writer = (PrintWriter) it.next();
                writer.println(message);
                writer.flush();
            } catch (Exception ex) { ex.printStackTrace(); }
        }
    }
}