简单的聊天室制作

简单的聊天室制作

 

一个简单的聊天室,主要是就两个部分,一部分就是我们进行的时候那个聊天窗口,另外一个就是背后的服务器,我们要写一个聊天窗口,也要写一个相对应的服务器。

做一个项目过程中,写一个代码很简单,但是把逻辑分析清楚,将制作的过程中所有的逻辑关系分析清楚是项目的最重要的环节。

下面的一步一步,将这个聊天室的制作过程一步一步制作出来。

第一步:

 

第二步:

第三步:

第四步:

第五步:

第六步:

第七步:

第八步:

第九步:

第十步:

第十一步:

这就是简单的聊天室的制作的过程。这样我们来看一下实现后的代码:

客户端的的代码:

 

  1 import java.awt.BorderLayout;
  2 import java.awt.Frame;
  3 import java.awt.TextArea;
  4 import java.awt.TextField;
  5 import java.awt.event.ActionEvent;
  6 import java.awt.event.ActionListener;
  7 import java.awt.event.WindowAdapter;
  8 import java.awt.event.WindowEvent;
  9 import java.io.DataInputStream;
 10 import java.io.DataOutputStream;
 11 import java.io.EOFException;
 12 import java.io.IOException;
 13 import java.net.Socket;
 14 import java.net.SocketException;
 24 //该类继承了Frame,所以本质就是一个Fame,具有图新界面的一切特征
 25 public class ChatClient extends Frame {
 26     //声明一个文本框,用用于客户的消息输入
 27     TextField tfTxt = new TextField();
 28     //显示客户的输入,包括别的客户端通过服务器转发过来的消息
 29     TextArea taContent = new TextArea();
 30     //声明一个空的socket链接,用于和服务器进行信息交互,下面合适的时候,会进行赋值(变量声明的三个步骤)
 31     Socket soc = null;
 32     //声明一个boolean,判断和服务器端是否链接上了,特指socket
 33     private boolean bConnected = false;
 34     //接受从服务器端发送过来的信息流
 35     DataInputStream dis = null;
 36     //向服务器端发送客户输入消息
 37     DataOutputStream dos = null;
 38     //声明一个线程变量,专门用于接受服务器端发过来的消息.死循环,阻塞接受
 39     Thread tRecv = new Thread(new RecvThread());
 40 
 41     //main程序的入口,用于启动客户端,怎么启动的?静态和成员的区别
 42     public static void main(String[] args) {
 43         new ChatClient().launFrame();
 44     }
 45 
 46     //客户端的启动方法,启动了客户端界面,链接服务器,启动接受线程
 47     public void launFrame() {
 48         //设置界面的位置宽高
 49         setBounds(1000, 300, 300, 300);
 50         //设置界面的布局.
 51         this.add(tfTxt, BorderLayout.SOUTH);
 52         this.add(taContent, BorderLayout.NORTH);
 53         //把Frame里面的控件打包然后按照默认设计好的方式显示
 54         this.pack();
 55         // 窗口关闭按钮,事件监听,用的是匿名内部类,关闭窗口的同时关闭了和服务器端链接,然后退出系统,线程不用管.都关机了
 56         this.addWindowListener(new WindowAdapter() {
 57             public void windowClosing(WindowEvent e) {
 58                 disConnect();
 59                 System.exit(0);
 60             }
 61         });
 62 
 63         // 添加文本框监听事件,是一个成员内部类,目的是为了获得客户输入的消息,默认触发回车触发
 64         tfTxt.addActionListener(new TxtListener());
 65         //让整个界面可见
 66         setVisible(true);
 67         
 68         // 链接服务器方法调用了,怎么就叫链接了.soc,dis,dos都链接上了才叫链接
 69         connect();
 70         //全部链接上以后,才启动消息接收线程
 71         tRecv.start();
 72     }
 73 
 74     // 添加客户端链接服务器的方法
 75     public void connect() {
 76         try {
 77             //给声明好的Socket指定链接的IP和端口,是个成员变量??
 78             soc = new Socket("127.0.0.1", 9999);
 79             //dos通过Socket向服务器端发送消息,用的什么方法?
 80             dos = new DataOutputStream(soc.getOutputStream());
 81             //dis通过Socket接收服务器端发过来的消息,什么方法?
 82             dis = new DataInputStream(soc.getInputStream());
 83             //System.out.println(dis);
 84             //上面都都准备好,才叫连接上,所以,把链接成员变量设置为真
 85             bConnected = true;
 86         } catch (IOException e) {
 87             e.printStackTrace();
 88         }
 89     }
 90     
 91     //断开服务器连接,把打开的那三个链接全部关闭掉
 92     public void disConnect() {
 93         try {
 94             dos.close();
 95             dis.close();
 96             soc.close();
 97         } catch (IOException e) {
 98             e.printStackTrace();
 99         }
100         
101         /*try{
102         bConnected = false;
103         tRecv.join();   //合并线程
104         }catch(InterruptedException e){
105             e.printStackTrace();
106         }finally{
107             try{
108                 dos.close();
109                 dis.close();
110                 soc.close();
111             }catch(IOException e){
112                 e.printStackTrace();
113             }
114     }*/
115     }
116     
117     
118     // 创建一个内部类,实现控件本身的事件监听,不同的控件有自己默认的事件,有三种/四种来设置监听:匿名内部类,成员内部类,外部类
119     private class TxtListener implements ActionListener {
120         @Override
121         public void actionPerformed(ActionEvent e) {//获得触发事件的那个最初控件对象
122             //经过e获得了事件源对象控件,但是我们没用他,因为我们把事件源对象设置成了成员变量,我们内部类可以直接访问成员变量
123             String s = tfTxt.getText().trim();
124             // System.out.println(s);
125             // 把客户往输入文本框的消息,取到然后输出文本显示框,这里也是为了避免和服务器端发送回来的消息叠加,所以注释掉
126             //taContent.setText(s);
127             //同时把输入框中客户输入的值清空.要不然会叠加
128             tfTxt.setText("");
129 
130             // 把数据发送到服务器
131             try {
132                 //DataOutputStream,会把数据的数据类型也保存进去,发送出去,什么样的顺序写,就什么样的顺序.
133                 //也需要按照对应的数据类型来读取
134                 dos.writeUTF(s);
135                 //只要是输出流,都需要flush,要不然会把数据缓存下来,而输出不出去
136                 dos.flush();
137             } catch (IOException e1) {
138                 e1.printStackTrace();
139             }
140         }
141     }
142 
143     //阻塞是读取服务器端发送过来的消息
144     private class RecvThread implements Runnable {
145         //当线程类启动该线程的时候,调用start(),然后jvm默认会调用run()方法开启新线程
146         public void run() {
147             try {
148                 //如果链接全准备好了,我们就不间断的等待读取服务器端发送过来的消息.
149                 while (bConnected) {
150                     //通过dis的readUTF()读取
151                     String str = dis.readUTF();
152                     //然后把获取到的消息,和输出到文本显示框中原本的消息一起,加上换行符
153                     taContent.setText(taContent.getText() + str + '\n');
154                 }
155             } catch (SocketException e) {
156                 System.out.println("客户端已经关闭");
157             }catch (EOFException e) {
158                 System.out.println("读取完毕");
159             }catch (IOException e) {
160                 System.out.println("IO异常");
161             }
162         }
163 
164     }
165 }

服务器端代码:

 

  1 import java.io.DataInputStream;
  2 import java.io.DataOutputStream;
  3 import java.io.EOFException;
  4 import java.io.IOException;
  5 import java.net.ServerSocket;
  6 import java.net.Socket;
  7 import java.util.ArrayList;
  8 import java.util.Iterator;
  9 import java.util.List;
 10 
 11 import javax.xml.stream.events.StartDocument;
 12 
 13 /**
 14  * 思路: 1 开启服务器,接受客户端链接,接受客户端数据 start():方法,负责服务器的开启 client():只负责管道的链接
 15  * run():负责数据的读取
 16  * 
 17  * @author Admin
 18  *
 19  */
 20 
 21 public class ChatServer {
 22     // 判断服务器是否启动,启动以后就不间断的接受客户端链接,就是while(true)接受链接,接受链接,不是消息
 23     boolean started = false;
 24     //声明一个ServerSocket变量,用于转载启动服务器对象
 25     ServerSocket ss = null;
 26     //声明一个Client集合,用于装在所有连接到服务器的客户端
 27     List<Client> clients = new ArrayList<>();
 28 
 29     //main()方法,调用方法启动服务器,start(),自定义的
 30     public static void main(String[] args) {
 31         new ChatServer().start();
 32     }
 33 
 34     // 负责启动服务器方法声明
 35     public void start() {
 36         try {
 37             //实例化一个服务器对象,然后赋值给上面的服务器变量
 38             ss = new ServerSocket(9999);
 39             //启动以后赋值给判断变量
 40             started = true;
 41             //如果    启动了,就不间断声明socket接收客户端的链接
 42             while (started) {
 43                 // 声明socket
 44                 Socket soc = ss.accept();
 45                 //每个Socket对应一个Client对象,然后把Socket传递到对象的内部,Socket和客户端就一一对应了
 46                 Client c = new Client(soc);
 47                 //然后根据客户端开启这个客户端对应的消息收发线程.这样线程,客户端,Socket就一一对应了.
 48                 new Thread(c).start();
 49                 //然后把客户端的引用保存到集合中,这样,客户单就有了顺序,相当于有了一个唯一的识别符号
 50                 clients.add(c);
 51                 // 上一行如果没有链接上会报错,到达这里就证明链接上了
 52 
 53             }
 54         } catch (IOException e) {
 55             // e.printStackTrace();
 56             System.out.println("客户端关闭,服务器端也会关闭");
 57         } finally {
 58             try {
 59                 if (ss != null)
 60                     ss.close();
 61             } catch (Exception e2) {
 62                 e2.printStackTrace();
 63             }
 64         }
 65     }
 66 
 67     class Client implements Runnable {
 68         //每个客户端对象,都肯定具备以下属性
 69         //对应的Socket
 70         private Socket soc;
 71         //对应的输入属性
 72         private DataInputStream dis = null;
 73         //对应的输出
 74         private DataOutputStream dos = null;
 75         //对应的链接属性,是否链接好了
 76         private boolean bConnected = false;
 77 
 78         // 构造方法,只是成功的创建这个类,构造方法用于创建这个类
 79         //同时需要输入对应的Socket,还同时实例化输入和输出的对应的流及其方法,和Socket绑定
 80         public Client(Socket s) {
 81             this.soc = s;
 82             try {
 83                 dis = new DataInputStream(s.getInputStream());//s是绑定
 84                 dos = new DataOutputStream(s.getOutputStream());
 85             } catch (IOException e) {
 86                 e.printStackTrace();
 87             }
 88             bConnected = true;
 89         }
 90 
 91         @Override
 92         public void run() {
 93             try {
 94                 //如果链接成功的话,这个线程就不间断的读取客户端的消息
 95                 while (bConnected) {
 96                     //读取客户端发过来的消息
 97                     String str = dis.readUTF();
 98                     System.out.println(str);
 99                     //同时遍历集合中的每一个对象,然后挨个发送收到消息
100                     for (int i = 0; i < clients.size(); ++i) {
101                         //i是Client,Socket,对应的下标
102                         Client c = clients.get(i);
103                         //发送方法
104                         c.send(str);
105                     }
106                 }
107             } catch (EOFException e) {
108                 //如果客户端断开,就需要移除这个客户端.输入移除和输出移除都一样.因为都是不间断的监听
109                 clients.remove(this);
110                 System.out.println("客户端已经断开连接,所以从集合中移除该客户端");
111             } catch (IOException e) {
112                 e.printStackTrace();
113             } finally {
114                 try {
115                     if (dis != null) {
116                         dis.close();
117                     }
118                     if (dos != null) {
119                         dos.close();
120                     }
121                     if (soc != null) {
122                         soc.close();
123                     }
124                 } catch (IOException e2) {
125                     e2.printStackTrace();
126                 }
127             }
128         }
129         public void send(String str) {
130             try {
131                 //真正向客户端发送消息的方法
132                 dos.writeUTF(str);
133             } catch (IOException e) {
134                 e.printStackTrace();
135             }
136         }
137     }
138 
139 }

 

代码完成了,让我们来看看我们的聊天室做好以后的效果:

在我敲击回车后,观看一下结果:

 

 我们这个小的聊天室就完成了!

 

 

 

posted @ 2015-09-21 15:12  ~铁臂阿童木~  阅读(883)  评论(0编辑  收藏  举报