1.程序实现的功能
两个客户端之间,实现在线文字聊天,和接收离线消息。
2.程序总体结构
程序整体是C/S结构,用java中socket通信建立服务端和客户端之间的UDP连接,消息都通过服务端转发,客户端之间不直接建立连接。
3.服务端介绍
(图3.1 服务端初始界面)
首先,服务端程序运行后,需要点击启动按钮,基于事件监听机制,建立socket连接和等待接收数据报都在按钮actionPerformed函数里进行:
1 BStart.addActionListener(new ActionListener() { //启动按钮
2
3 @Override4 public voidactionPerformed(ActionEvent e) {5 thread t1 = newthread();6 t1.start();7 }8 });
注意到,按钮里的并没有socket操作,只有一个线程的执行,其实,socket操作就放在这个线程里进行,之所以用到线程,原因在后文会给出。
通过继承了Thread类的方式实现多线程:
1 static class thread extendsThread{2 publicthread(){3 super();4 }5 public voidrun(){6 //socket methods...
7 }
第一件事是建立socket,绑定到本机,并显示在左边的系统消息框内:
1 //建立数据报Socket并显示
2 InetAddress addr = InetAddress.getByName("localhost");3 DatagramSocket ds =newDatagramSocket(PORT,addr);4 ta.setText("【" + df.format(new Date()) +"】" + "UDP服务器已启动:" + addr.getHostAddress() + addr.getHostName());
(3.2 服务端启动连接界面)
接下里通过DatagramSocket的receive函数等待数据报的到来:
1 while(true){2 ds.receive(inDataPacket);//等待数据报的到来3 //...
4 }
如果收到的是客户端的连接请求,则更新用户列表,返回确认连接报文,并在系统消息框内显示客户端连接的消息:
1 //数据的处理
2 String str = new String(inDataPacket.getData(), 0, inDataPacket.getLength());3 if(str.equals(new String("Request Connect"))){ //连接请求
4 String str2 = inDataPacket.getAddress() + "(" + inDataPacket.getPort() + ")";5 //更新用户列表
6 if(ta2.getText().indexOf(str2) == -1){7 String history2 =ta2.getText();8 String now2 = String.format("%s%n", history2) +str2;9 ta2.setText(now2);10 }11
12 //返回确认连接报文
13 String strRep = "Connection Confirm";14 DatagramPacket outDataPacket = newDatagramPacket(strRep.getBytes(),strRep.length(),15 inDataPacket.getAddress(), inDataPacket.getPort());16 ds.send(outDataPacket);17
18 //更新系统消息列表
19 String history_SM =ta.getText();20 String now_SM = String.format("%s%n", history_SM) + "【" + df.format(new Date()) +"】" +str2 + "成功连接到服务器";21 ta.setText(now_SM);
(图3.3 客户端连接到服务器)
如果不是请求连接报文,则为消息报文。数据报中消息的头部加上了源端口和目的端口,方便服务端进行数据报的封装,如果源端口为8888,目的端口为8889,则消息的头部为:TO8889FR8888。如果用户在线(在当前用户列表上查找,有则判断在线),则封装数据报并投递。如果不在线,则将消息储存,等待此用户上线后再发送该消息。只要收到消息,系统显示框都会有显示。
1 //消息处理
2 String Port = str.substring(2, 6); //提取收信方端口
3 if(ta2.getText().indexOf(Port) != -1){ //用户在线
4 String strRep =str;5 DatagramPacket outDataPacket = newDatagramPacket(strRep.getBytes(),strRep.getBytes().length,6 addr, Integer.parseInt(Port));7 ds.send(outDataPacket);8
9 }else{ //用户不在线
10 if(Port.equals(new String("8888"))){11 S1 =str;12 }else if(Port.equals(new String("8889"))){13 S2 =str;14 }else{15 S3 =str;16 }17 }18 String history =ta.getText();19 String now = String.format("%s%n", history) + "【" + df.format(new Date()) +"】" + str.subSequence(8, 12) + "给" + Port + "发送了一条消息。";20 ta.setText(now);
(图3.4 服务端显示消息发送成功)
4.客户端介绍
客户端和服务端有很多类似,不管是界面还是代码结构。为了演示的需要,共设置三个客户端,由于都在本机运行,地址全为localhost,以端口号区分。
(图4.1 客户端初始界面)<