p1/machine/NetworkLink.java

330 lines
8.8 KiB
Java

// PART OF THE MACHINE SIMULATION. DO NOT CHANGE.
package nachos.machine;
import nachos.security.*;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.net.SocketException;
/**
* A full-duplex network link. Provides ordered, unreliable delivery of
* limited-size packets to other machines on the network. Packets are
* guaranteed to be uncorrupted as well.
*
* <p>
* Recall the general layering of network protocols:
* <ul>
* <li>Session/Transport
* <li>Network
* <li>Link
* <li>Physical
* </ul>
*
* <p>
* The physical layer provides a bit stream interface to the link layer. This
* layer is very hardware-dependent.
*
* <p>
* The link layer uses the physical layer to provide a packet interface to the
* network layer. The link layer generally provides unreliable delivery of
* limited-size packets, but guarantees that packets will not arrive out of
* order. Some links protect against packet corruption as well. The ethernet
* protocol is an example of a link layer.
*
* <p>
* The network layer exists to connect multiple networks together into an
* internet. The network layer provides globally unique addresses. Routers
* (a.k.a. gateways) move packets across networks at this layer. The network
* layer provides unordered, unreliable delivery of limited-size uncorrupted
* packets to any machine on the same internet. The most commonly used network
* layer protocol is IP (Internet Protocol), which is used to connect the
* Internet.
*
* <p>
* The session/transport layer provides a byte-stream interface to the
* application. This means that the transport layer must deliver uncorrupted
* bytes to the application, in the same order they were sent. Byte-streams
* must be connected and disconnected, and exist between ports, not machines.
*
* <p>
* This class provides a link layer abstraction. Since we do not allow
* different Nachos networks to communicate with one another, there is no need
* for a network layer in Nachos. This should simplify your design for the
* session/transport layer, since you can assume packets never arrive out of
* order.
*/
public class NetworkLink {
/**
* Allocate a new network link.
*
* <p>
* <tt>nachos.conf</tt> specifies the reliability of the network. The
* reliability, between 0 and 1, is the probability that any particular
* packet will not get dropped by the network.
*
* @param privilege encapsulates privileged access to the Nachos
* machine.
*/
public NetworkLink(Privilege privilege) {
System.out.print(" network");
this.privilege = privilege;
try {
localHost = InetAddress.getLocalHost();
}
catch (UnknownHostException e) {
localHost = null;
}
Lib.assertTrue(localHost != null);
reliability = Config.getDouble("NetworkLink.reliability");
Lib.assertTrue(reliability > 0 && reliability <= 1.0);
socket = null;
for (linkAddress=0;linkAddress<Packet.linkAddressLimit;linkAddress++) {
try {
socket = new DatagramSocket(portBase + linkAddress, localHost);
break;
}
catch (SocketException e) {
}
}
if (socket == null) {
System.out.println("");
System.out.println("Unable to acquire a link address!");
Lib.assertNotReached();
}
System.out.print("(" + linkAddress + ")");
receiveInterrupt = new Runnable() {
public void run() { receiveInterrupt(); }
};
sendInterrupt = new Runnable() {
public void run() { sendInterrupt(); }
};
scheduleReceiveInterrupt();
Thread receiveThread = new Thread(new Runnable() {
public void run() { receiveLoop(); }
});
receiveThread.start();
}
/**
* Returns the address of this network link.
*
* @return the address of this network link.
*/
public int getLinkAddress() {
return linkAddress;
}
/**
* Set this link's receive and send interrupt handlers.
*
* <p>
* The receive interrupt handler is called every time a packet arrives
* and can be read using <tt>receive()</tt>.
*
* <p>
* The send interrupt handler is called every time a packet sent with
* <tt>send()</tt> is finished being sent. This means that another
* packet can be sent.
*
* @param receiveInterruptHandler the callback to call when a packet
* arrives.
* @param sendInterruptHandler the callback to call when another
* packet can be sent.
*/
public void setInterruptHandlers(Runnable receiveInterruptHandler,
Runnable sendInterruptHandler) {
this.receiveInterruptHandler = receiveInterruptHandler;
this.sendInterruptHandler = sendInterruptHandler;
}
private void scheduleReceiveInterrupt() {
privilege.interrupt.schedule(Stats.NetworkTime, "network recv",
receiveInterrupt);
}
private synchronized void receiveInterrupt() {
Lib.assertTrue(incomingPacket == null);
if (incomingBytes != null) {
if (Machine.autoGrader().canReceivePacket(privilege)) {
try {
incomingPacket = new Packet(incomingBytes);
privilege.stats.numPacketsReceived++;
}
catch (MalformedPacketException e) {
}
}
incomingBytes = null;
notify();
if (incomingPacket == null)
scheduleReceiveInterrupt();
else if (receiveInterruptHandler != null)
receiveInterruptHandler.run();
}
else {
scheduleReceiveInterrupt();
}
}
/**
* Return the next packet received.
*
* @return the next packet received, or <tt>null</tt> if no packet is
* available.
*/
public Packet receive() {
Packet p = incomingPacket;
if (incomingPacket != null) {
incomingPacket = null;
scheduleReceiveInterrupt();
}
return p;
}
private void receiveLoop() {
while (true) {
synchronized(this) {
while (incomingBytes != null) {
try {
wait();
}
catch (InterruptedException e) {
}
}
}
byte[] packetBytes;
try {
byte[] buffer = new byte[Packet.maxPacketLength];
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
socket.receive(dp);
packetBytes = new byte[dp.getLength()];
System.arraycopy(buffer,0, packetBytes,0, packetBytes.length);
}
catch (IOException e) {
return;
}
synchronized(this) {
incomingBytes = packetBytes;
}
}
}
private void scheduleSendInterrupt() {
privilege.interrupt.schedule(Stats.NetworkTime, "network send",
sendInterrupt);
}
private void sendInterrupt() {
Lib.assertTrue(outgoingPacket != null);
// randomly drop packets, according to its reliability
if (Machine.autoGrader().canSendPacket(privilege) &&
Lib.random() <= reliability) {
// ok, no drop
privilege.doPrivileged(new Runnable() {
public void run() { sendPacket(); }
});
}
else {
outgoingPacket = null;
}
if (sendInterruptHandler != null)
sendInterruptHandler.run();
}
private void sendPacket() {
Packet p = outgoingPacket;
outgoingPacket = null;
try {
socket.send(new DatagramPacket(p.packetBytes, p.packetBytes.length,
localHost, portBase+p.dstLink));
privilege.stats.numPacketsSent++;
}
catch (IOException e) {
}
}
/**
* Send another packet. If a packet is already being sent, the result is
* not defined.
*
* @param pkt the packet to send.
*/
public void send(Packet pkt) {
if (outgoingPacket == null)
scheduleSendInterrupt();
outgoingPacket = pkt;
}
private static final int hash;
private static final int portBase;
/**
* The address of the network to which are attached all network links in
* this JVM. This is a hash on the account name of the JVM running this
* Nachos instance. It is used to help prevent packets from other users
* from accidentally interfering with this network.
*/
public static final byte networkID;
static {
hash = System.getProperty("user.name").hashCode();
portBase = 0x4E41 + Math.abs(hash%0x4E41);
networkID = (byte) (hash/0x4E41);
}
private Privilege privilege;
private Runnable receiveInterrupt;
private Runnable sendInterrupt;
private Runnable receiveInterruptHandler = null;
private Runnable sendInterruptHandler = null;
private InetAddress localHost;
private DatagramSocket socket;
private byte linkAddress;
private double reliability;
private byte[] incomingBytes = null;
private Packet incomingPacket = null;
private Packet outgoingPacket = null;
private boolean sendBusy = false;
}