题目描述

利用数据报通信方式编写一程序,该程序生成两个客户端,一个服务器端,两个客户端可以相互进行简短的文字交流。

实现思路

为使客户端能够接收到其他客户端的数据报,客户端在创建时应该指定端口号,开启接收数据的线程和等待发送消息的线程,当接收到数据或发送数据后,重启线程。并且发送方在数据报中应包含接收端的地址,由服务器端对地址进行解析并转达数据包。

实现过程

1. 服务器端:

在Server.java中实现Server类,静态变量指定服务器端口,接收数据报的大小

1
2
3
public class Server {  
static int serverPort = 8000;
static int packetSize = 1024;

进入主函数时,初始化数据报套接字及接收数据报

1
2
3
4
5
public static void main(String[] args) throws Exception {  
DatagramSocket server = new DatagramSocket(serverPort);
byte[] data = new byte[packetSize];
DatagramPacket packet = new DatagramPacket(data, packetSize);
System.out.println("server is ready!!");

使用死循环处理每一个客户端发送的数据报;\
将数据报分割为地址及信息两部分;\
根据地址及信息创建新数据报,并发送到目标客户端,完成对消息的转述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
while (true) {  
server.receive(packet);
System.out.println("server has received data from sender");
String dataStr = new String(data, 0, packet.getLength());
String[] dataSegments = dataStr.split(" ");
if(dataSegments.length >= 2){
InetAddress receiverAddress = InetAddress.getByName(dataSegments[0].split(":")[0]);
int receiverPort = Integer.parseInt(dataSegments[0].split(":")[1]);
DatagramPacket trulyData = new DatagramPacket(dataSegments[1].getBytes(),
dataSegments[1].length(), receiverAddress, receiverPort);
System.out.println("the message: "+dataSegments[1]);
server.send(trulyData);
System.out.println("server has sent data to receiver");
}
}

2. 客户端

客户端在创建时需指定端口号,这里使用命令行参数传入

1
2
3
4
5
6
7
public class Client {  
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
DatagramSocket socket = new DatagramSocket(port);
System.out.println("client is ready!!");

创建接收消息的线程及发送消息的线程等待输入

1
2
3
4
5
6
7
8
9
10
11
            // create a receiver thread  
Thread receiveThread = new Thread(new ReceiverRunnable(socket));
receiveThread.start(); // receive message at any time
// create a sender thread
Thread sendThread = new Thread(new ClientSenderRunnable(socket));
sendThread.start();
}else{
System.out.println("you must indicate the port by adding argument");
}
}
}

(1)接收消息的线程

在Receiver.java中实现一个实现Runnable接口的可执行类\
在构造方法中传入客户端数据报套接字,初始化接收端\
在静态变量中定义数据报大小,以及接收数据报的字符数组

1
2
3
4
5
6
7
8
9
10
public class ReceiverRunnable implements Runnable {  
static int dataSize = 1024;
DatagramPacket receivePacket;
byte[] data;
public DatagramSocket socket;
public ReceiverRunnable(DatagramSocket socket){
this.socket = socket;
data = new byte[dataSize];
receivePacket = new DatagramPacket(data, dataSize);
}

重写run方法,接收数据报并打印消息\
在线程终点启动下一个接收消息的线程

1
2
3
4
5
6
7
8
9
10
11
12
    @Override  
public void run(){
try{
socket.receive(receivePacket);
System.out.println("You have received a message: "+new String(data, dataSize));
Thread thread = new Thread(new ReceiverRunnable(socket));
thread.start();
}catch (IOException e){
e.fillInStackTrace();
}
}
}

(2)发送消息的线程

在ClientSenderRunnable.java中实现一个实现Runnable接口的可执行类\
在构造方法中传入客户端数据报套接字,初始化发送端\
在静态变量中指定服务器地址及端口,用于初始化数据报

1
2
3
4
5
6
7
public class ClientSenderRunnable implements Runnable{  
static String serverName = "localhost";
static int serverPort = 8000;
public DatagramSocket socket;
public ClientSenderRunnable(DatagramSocket socket){
this.socket = socket;
}

重写run方法,读取键盘输入

1
2
3
4
5
6
7
8
9
@Override  
public void run() {
System.out.println("type as (address:port message):");
Scanner scanner = new Scanner(System.in);
String dataStr;
if (scanner.hasNextLine()) {
dataStr = scanner.nextLine();
System.out.println("your header and message: " + dataStr);
byte[] data = dataStr.getBytes();

根据静态变量指定的服务端地址打包数据报并发送

1
2
3
4
5
6
7
8
9
10
11
12
13
// pack up the data  
try {
InetAddress serverAddress = InetAddress.getByName(serverName);
DatagramPacket packet = new DatagramPacket(data, data.length, serverAddress, serverPort);
// create socket and send data packed
socket.send(packet);
System.out.println("client has sent data to server!!");
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

在线程终点启动下一个发送消息的线程

1
2
3
4
        Thread thread = new Thread(new ClientSenderRunnable(socket));  
thread.start();
}
}

运行结果

启动服务端
image.png

启动两个客户端
image.png
image.png

由客户端一(8001端口)向客户端二(8002端口)发送消息
image.png

服务器端成功接收到消息并转发
image.png

客户端二(8002端口)成功监听到接收的消息
image.png

同理,由客户端二向客户端一发送消息也同样得以完成

客户端二:
image.png

服务器端:
image.png

客户端一:
image.png