• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

用JavaSocket制作文字聊天小程序-chenchenlili

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

用Java Socket制作文字聊天小程序

1.程序实现的功能

  两个客户端之间,实现在线文字聊天,和接收离线消息。

2.程序总体结构

  程序整体是C/S结构,用javasocket通信建立服务端和客户端之间的UDP连接,消息都通过服务端转发,客户端之间不直接建立连接。

3.服务端介绍

(图3.1 服务端初始界面)

  首先,服务端程序运行后,需要点击启动按钮,基于事件监听机制,建立socket连接和等待接收数据报都在按钮actionPerformed函数里进行:

 

1 BStart.addActionListener(new ActionListener() { //启动按钮
2     
3             @Override
4             public void actionPerformed(ActionEvent e) {
5                 thread t1 = new thread();
6                 t1.start();
7             }
8         });

  

  注意到,按钮里的并没有socket操作,只有一个线程的执行,其实,socket操作就放在这个线程里进行,之所以用到线程,原因在后文会给出。

  通过继承了Thread类的方式实现多线程:

 

1 static class thread extends Thread{
2         public thread(){
3             super();
4         }
5         public void run(){
6 //socket methods...
7 }

 

  第一件事是建立socket,绑定到本机,并显示在左边的系统消息框内: 

 

1 //建立数据报Socket并显示
2   InetAddress addr = InetAddress.getByName("localhost");
3   DatagramSocket ds =new DatagramSocket(PORT,addr);
4   ta.setText("【" + df.format(new Date()) +"】" + "UDP服务器已启动:" + addr.getHostAddress() + addr.getHostName()); 

 

(3.2 服务端启动连接界面)

 

  接下里通过DatagramSocketreceive函数等待数据报的到来:

 

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 = new DatagramPacket(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 = new DatagramPacket(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 客户端初始界面)

 

   客户端的socket操作放在连接按钮的监听函数里,完成消息的获取:

 

 1      //建立数据报socket
 2     addr = InetAddress.getByName("localhost");
 3     datagramSocket = new DatagramSocket(LocalPORT);
 4     datagramSocket.setSoTimeout(3000);    //设置3秒超时
 5     
 6     //发送登录消息给服务器
 7     String str = "Request Connect";
 8     DatagramPacket login = new DatagramPacket(str.getBytes(), str.length(), addr, ServerPORT);
 9     datagramSocket.send(login);
10     
11     //接受服务器的确认数据报
12     byte[] msg = new byte[100];
13     DatagramPacket inDataPacket = new DatagramPacket(msg, msg.length); 
14     datagramSocket.receive(inDataPacket);    

 

  当连接按钮按下后,右侧的好友按钮才会可用:

(图4.2 客户端连接到服务器)

 

  此时,发送消息的准备工作已经完成。点击要对话的好友按钮,发送按钮会变成可用,在下方的文本框里输入要发送的信息,点击发送按钮即可完成:

 

 1 //按下好友按钮,发送按钮可用
 2    B8889.addActionListener(new ActionListener(){
 3         @Override
 4         public void actionPerformed(ActionEvent e) {        
 5         
 6             current = "8889";
 7             BSend.setEnabled(true);
 8         }
 9         
10     });

  其中,current为消息将要到达的目标端口号的标识,点击发送按钮,消息将会被发到current所表示的端口客户端。

  发送按钮里的socket操作:

 

 1   //建立数据报socket
 2     InetAddress addr = InetAddress.getByName("localhost");
 3     DatagramSocket datagramSocket = new DatagramSocket(LocalPORTS);
 4     
 5     //构造数据报并发送
 6     String str = "T0" + current + "FR" + "8888" + ":" + tf.getText();
 7     DatagramPacket out = new DatagramPacket(str.getBytes(), str.getBytes().length, addr, ServerPORT);
 8     datagramSocket.send(out);
 9     
10     //清空发送框
11     tf.setText(null);
12     
13     //显示
14     String history = ta.getText();
15     String now = String.format("%s%n", history) + "【" + df.format(new Date()) +"】" + "消息发送成功!"; 
16     ta.setText(now);
17     //关闭数据报
18     datagramSocket.close();

 

  演示,用客户端8888发送“网络交谈123”给客户端8889

 

(图4.3 客户端8888发送消息成功)

 

(图4.4 客户端8889收到消息)

 

 5.遇到的问题及解决过程

  实践过程中碰到很多问题,持续时间很长,由于基础不牢,有时小问题都会花很长时间,绝大多数时间都花在解决问题上了。下面就过程中碰到的3个问题进行简要说明。

  1)服务端点击启动后窗口无法正常关闭/启动客户端并点击连接按钮,窗口失效。

  最开始是发现当服务端点击启动按钮运行后,窗口就不能点击右上角关闭了,需要用任务管理器才能关掉。之后在java课上,听老师讲到了阻塞的概念,才发现,DatagramSocketreceive函数在接受到数据报前一直处于阻塞状态,就是说,当没有收到消息时程序会停在这里,而这个时候,程序中其他部分就会处于不可用状态。知道了原因,但还是不知道怎样解决。在网上了解一番后,发现,将启动按钮中socket操作放在另外线程里能解决这个问题。就又去学习了线程的知识,才找到解决方案。

  之后发现,启动客户端点击连接按钮后窗口上好友按钮显示可用,但是点击没有反应,界面底部的文本框也不能用,整个窗口像是卡住了一样。很快发应过来,这还是receive函数阻塞的原因,因为连接按钮的监听函数里,放了DatagramSocketreceive函数,需要总是处于等待消息状态。同样,加入线程后就解决了。

 

  2)服务端未启动时,先启动客户端并点击连接按钮,需要将阻塞状态终止,显示连接超时消息。

  很明显这也是receive阻塞,因为客户端的连接按钮的监听函数里通过DatagramSocket给服务器发送了请求连接报文,但是服务器未启动,不能返回确认连接报文,receive阻塞。这个情况也可以通过加入线程和设置定时器的方法可以解决,但是我通过查阅java API文档找到了更好的方法,发现可以给receive函数设置超时限制,既当receive函数在设置的时间里未能收到消息,会抛出SocketTimeoutException异常通过捕获这个异常,就可以中断receive的阻塞,并可以显示超时信息。

1 datagramSocket.setSoTimeout(3000);    //设置3秒超时
 1 Try{
 2     DatagramSocket.receive(inDataPacket);
 3 }catch(SocketTimeoutException e1) {
 4                     String history = ta.getText();
 5                     String now;
 6                     if(!history.equals("")){
 7                         now = String.format("%s%n", history) + "【" + df.format(new Date()) +"】" + "连接超时!"; 
 8                     }else{
 9                         now = "【" + df.format(new Date()) +"】" + "连接超时!";
10                     }
11                     ta.setText(now);
12                     datagramSocket.close();
13 }

  3)中文乱码

开始只能发送英文和数字,发中文就乱码,在网上查说是编码的问题,改了半天才发现我封装数据报的时候,字符串的长度计算不妥:

1 DatagramPacket out = new DatagramPacket(str.getBytes(),str.length(), addr, ServerPORT);    //str为String类型

  如果是英文或数字,str.length()计算的长度没问题,但是如果是汉字,就不对了,汉字不只占一个字节。

  之后改成:

1 DatagramPacket out = new DatagramPacket(str.getBytes(), str.getBytes().length, addr, ServerPORT);

就可以了。

 

6. 存在的不足和接下来的工作

对数据消息的储存没有花太多心思,客户端只能显示最后一条离线消息。

这个只是在本机上模拟多客户端交谈,要想放在不同机器上,需要获得机器的ip地址,将DatagramSocket绑定到此地址即可。

图片的传输可以尝试。

 

7.完整代码

 

  1 /*服务端*/
  2 import java.awt.Button;
  3 import java.awt.Color;
  4 import java.awt.Label;
  5 import java.awt.TextArea;
  6 import java.awt.event.ActionEvent;
  7 import java.awt.event.ActionListener;
  8 import javax.swing.JFrame;
  9 import java.io.*;
 10 import java.net.*;
 11 import java.text.SimpleDateFormat;
 12 import java.util.Date; 
 13 
 14 public class talkOnline_Server {
 15     public static final int PORT =8081;
 16     private static TextArea ta;
 17     private static TextArea ta2;
 18     static SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
 19     public static void main(String[] args) {
 20         JFrame f = new JFrame();
 21         f.setLayout(null);
 22         
 23         //连接按钮
 24         Button BStart = new Button("启动");
 25         BStart.setBounds(10, 10, 50, 20);
 26         
 27         //系统消息框标签
 28         Label lb1 = new Label("--------系统消息框--------");
 29         lb1.setBounds(10, 55, 300, 20);
 30         
 31         //系统消息框
 32         ta = new TextArea();
 33         ta.setBackground(Color.gray);
 34         ta.setEditable(false);
 35         ta.setBounds(10, 80, 400, 400);
 36         
 37         
 38         //当前用户列表标签
 39         Label lb2 = new Label("--------当前用户列表--------");
 40         lb2.setBounds(450, 55, 300, 20);
 41         
 42         //当前用户列表框
 43         ta2 = new TextArea();
 44         ta2.setBackground(Color.gray);
 45         ta2.setEditable(false);
 46         ta2.setBounds(450, 80, 200, 400);
 47         
 48 
 49         //将组件加入到框架
 50         f.add(BStart);
 51         f.add(lb1);
 52         f.add(ta);
 53         f.add(lb2);
 54         f.add(ta2);    
 55         
 56         f.setSize(700,600);
 57         f.setVisible(true);
 58         f.setResizable(false);
 59         f.setTitle("网络交谈_服务器");
 60         f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 61 
 62         BStart.addActionListener(new ActionListener() { //启动按钮
 63     
 64             @Override
 65             public void actionPerformed(ActionEvent e) {
 66                 thread t1 = new thread();
 67                 t1.start();
 68             }
 69         });
 70 
 71     }
 72     
 73     static class thread extends Thread{
 74         public thread(){
 75             super();
 76         }
 77         public void run(){
 78             InetAddress addr;    
 79             DatagramSocket    ds = null;
 80             String S1 = new String(), S2 = new String (), S3 = new String();
 81             try {
 82                 //建立数据报Socket并显示
 83                 addr = InetAddress.getByName("localhost");
 84                 ds =new DatagramSocket(PORT,addr);
 85                 ta.setText("【" + df.format(new Date()) +"】" + "UDP服务器已启动:" + addr.getHostAddress() + "/" + addr.getHostName()); 
 86                 
 87                 //建立接受数据报
 88                 byte[] buf = new byte[1000];
 89                 DatagramPacket inDataPacket = new DatagramPacket(buf, buf.length);    
 90                 while(true){
 91                     //等待数据报的到来
 92                     ds.receive(inDataPacket);
 93                     //数据的处理
 94                     String str = new String(inDataPacket.getData(), 0, inDataPacket.getLength());        
 95                     if(str.equals(new String("Request Connect"))){   //连接请求
 96                         String str2 = inDataPacket.getAddress() + "(" + inDataPacket.getPort() + ")";
 97                         //更新用户列表
 98                         if(ta2.getText().indexOf(str2) == -1){
 99                             String history2 = ta2.getText();
100                             String now2 = String.format("%s%n", history2) + str2;
101                             ta2.setText(now2);
102                         }
103 
104                         //返回确认连接报文
105                         String strRep = "Connection Confirm";
106                         DatagramPacket outDataPacket = new DatagramPacket(strRep.getBytes(),strRep.length(),
107                                 inDataPacket.getAddress(), inDataPacket.getPort());
108                         ds.send(outDataPacket);
109                         
110                         //更新系统消息列表
111                         String history_SM = ta.getText();
112                         String now_SM = String.format("%s%n", history_SM) + "【" + df.format(new Date()) +"】" +str2 + "成功连接到服务器";
113                         ta.setText(now_SM);
114                         
115                         if(!S1.isEmpty()){    //有8888的消息
116                     
117                             outDataPacket = new DatagramPacket(S1.getBytes(),S1.getBytes().length,
118                                     addr, 8888);
119                             ds.send(outDataPacket);
120                             S1 = new String("");
121                         }
122                         
123                         if(!S2.isEmpty()){    //有8889的消息
124                             outDataPacket = new DatagramPacket(S2.getBytes(),S2.getBytes().length,
125                                     addr, 8889);
126                             ds.send(outDataPacket);
127                             S2 = new String("");
128                         }
129                         if(!S3.isEmpty()){     //有8890的消息
130                             outDataPacket = new DatagramPacket(S3.getBytes(),S3.getBytes().length,
131                                     addr, 8890);
132                             ds.send(outDataPacket);
133                             S3 = new String("");
134                         }
135                         
136                     }
137                     else{   
138                         //消息处理
139                         String Port = str.substring(2, 6);  //收信方端口
140                         if(ta2.getText().indexOf(Port) != -1){  //用户在线
141                             String strRep = str;
142                             DatagramPacket outDataPacket = new DatagramPacket(strRep.getBytes(),strRep.getBytes().length,
143                                     addr, Integer.parseInt(Port));
144                             ds.send(outDataPacket);
145                             
146                         }else{   //用户不在线
147                             if(Port.equals(new String("8888"))){
148                                 S1 = str;
149                             }else if(Port.equals(new String("8889"))){
150                                 S2 = str;
151                             }else{
152                                 S3 = str;
153                             }
154                         }
155                         String history = ta.getText();
156                         String now = String.format("%s%n", history) + "【" + df.format(new Date()) +"】" + str.subSequence(8, 12) + "给" + Port + "发送了一条消息。"; 
157                         ta.setText(now);
158                         
159                     }
160                     
161                     //....
162 
163                 }    
164                     
165             } catch (SocketException e1) {
166             //    System.err.println("cannot open socket");
167                 ta.setText("cannot open socket");
168             //    System.exit(1);
169             } catch (IOException e1) {
170             //    System.err.println("communication error");
171                 ta.setText("communication error");
172                 e1.printStackTrace();
173             }finally{
174                 ds.close();
175             }  
176 
177         }
178     }
179         
180     }
181     
182     

 

  1 /*客户端
                      

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
小程序样式导入问题发布时间:2022-07-18
下一篇:
uniapp调用小白接口教程(开箱即用!以打包成微信小程序为例) ...发布时间:2022-07-18
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap