依据通信协议设计一个简单的聊天对话
1、通信首先要有服务端和客户端,并且服务端和客户端要分开设计(一般用是一个工程用于设计服务器,
另建一个工程设计客户端)。运行时先运行服务器,在运行客户端。
2、通信中两个重要的内容:Socket 和 ServerSocket 。
Socket:在客户端通过建立Socket对象,并根据ip和端口连接服务器,还可以通过Socket获取输入输
出流对象,用于读取从服务器发送过来的消息和往服务器发送消息。
ServerSocket:在服务端用于监听服务器设置的端口,等待接受客户端Socket对象的连接。
这两个内容把服务端和客户端连接起来
3、通信中两个主要的内容:发送消息和接收消息。
无论是服务端还是客户端,都是通过Socket对象获取输入输出流对象。并且都是通过字节的形式
发送和接受消息的,发送前先把发送的内容转换成字节的形式,然后再发送,接受到的发送的内容是字
节的形式,接收后要转换成自己需要的形式。
发送消息:OutputStream,它有write写入的方法。
接收消息:InputStream,它有read读取的方法。
4、主要实现的功能:注册账号(注册已存在的账号时,会提示此账号已存在)、登录(不可同时登录两
个相同的账号)、聊天(群聊和点对点(在线的用户可进行的功能))。
5、服务端:
用于创建ServerSocket对象和接受Socket的访问的类:MyServer
package com.fuwuduan; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; public class MyServer { //创建一个队列用于存储每个客户端访问时创建的线程 public static ArrayList<ServerThread> list = new ArrayList<ServerThread>(); public void setServer(int port){ try { //创建ServerSocket套接字对象用于监听port端口 ServerSocket ss = new ServerSocket(port); while(true){ System.out.println("服务器等待客户端的访问"); //循环等待接受客户端的访问,如果没有客户端的访问,循环就在这停止等待客户端的访问 Socket socket = ss.accept(); System.out.println("已连有客户接服务器"); //创建ServerThread线程对象,把客户端的连接放到线程里处理,并启动线程 ServerThread st = new ServerThread(socket); st.start(); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { new MyServer().setServer(8888); } }
用于处理客户端的连接的线程类:ServerThread
package com.fuwuduan; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.HashMap; import java.util.Iterator; import java.util.Set; public class ServerThread extends Thread { private String userName; private InputStream input; private OutputStream output; private Socket socket; //把socket对象通过构造方法传过来 public static HashMap<String, String> map = new HashMap<String, String>();//哈希表用于存储账号和密码 /** * 构造方法把socket传过来 * @param socket */ public ServerThread(Socket socket){ this.socket = socket; } /** * 读文件,把文件中的用户和密码读出来,并放到哈希表中 */ public void readFile(){ map.clear(); // 往哈希表中村用户和密码前,先清空哈希表 int count = 0; // 用于计数多少用户 try { //由于往文件中写入账号和密码后用\n结尾,可以根据\n先读取文件中有多少用户 FileInputStream fis1 = new FileInputStream("D:/Users/Adminstrator/workspace/通信服务器0826/src/com/fuwuduan/cunchu"); int n = fis1.read(); while(n != -1){ if(n == '\n'){ count++; } n = fis1.read(); } fis1.close(); //读完文件直接关闭文件输入流 //在次读取文件,根据写入的方式读出用户和密码 FileInputStream fis2 = new FileInputStream("D:/Users/Adminstrator/workspace/通信服务器0826/src/com/fuwuduan/cunchu"); for(int i=0; i<count; i++){ readFileLine(fis2); String userName = readFileLine(fis2); //读取用户名 readFileLine(fis2); readFileLine(fis2); String passWord = readFileLine(fis2); //读取密码 map.put(userName, passWord); //把用户和密码存到哈希表中 } fis2.close(); //读完文件直接关闭文件输入流 } catch (Exception e) { e.printStackTrace(); } } public void run(){ try { //得到网络连接的输入输出流对象 input = socket.getInputStream(); output = socket.getOutputStream(); //从客户端读取是注册还是确定 String cmd = readLine(input); if("注册".equals(cmd)){ while(true){ //读文件里的账号和密码 readFile(); //读取注册的账号 String suer = readLine(input); // if("注册窗口已经关闭".equals(suer)){ // break; // } //读取注册的密码 String spass = readLine(input); //初始化一个布尔类型的变量,用于标示注册的用户是否存在,true表示注册的用户不存在,false表示注册的用户存在 boolean Flag = true; Set<String> set = map.keySet(); //创建Set对象,并获取哈希表中的用户名集合,Set中存的内容是无序的且不可重复的 Iterator<String> iter = set.iterator(); // 创建迭代对象, while(iter.hasNext()){ String key = iter.next(); if(suer!=null&&suer.equals(key)){ //往客户端写入内容 String msg = "此账号已存在,请重新注册#"; System.out.println(msg); output.write(msg.getBytes()); Flag = false; break; } } if(Flag == true){ //往客户端写入内容 String msg = "注册成功#"; output.write(msg.getBytes()); //往文件里写注册的账号和密码 FileOutputStream fos = new FileOutputStream("D:/Users/Adminstrator/workspace/通信服务器0826/src/com/fuwuduan/cunchu",true); //往文件里写账号和密码 fos.write("账号: ".getBytes()); fos.write((suer+" , ").getBytes()); fos.write("密码: ".getBytes()); fos.write((spass+"\n ").getBytes()); fos.close(); } } } if("确定".equals(cmd)){ //读文件里的账号和密码 readFile(); //往客户端写入内容 String msg = "请输入账号 #"; output.write(msg.getBytes()); //从客户端读取账号 userName = readLine(input); //往客户端写入内容 msg = "请输入密码 #"; output.write(msg.getBytes()); //从客户端读取密码 String passWord = readLine(input)+"\n"; //判断账号和密码是否正确 if(passWord.equals(map.get(userName))&&eqUser()){ //把每个客户访问创建的线程存储到队列中 MyServer.list.add(this); msg = "服务器登录成功#"; System.out.println(msg); output.write(msg.getBytes()); while(true){ msg = readLine(input); //从客户端读取一行 while(true){ if((1+"").equals(msg)){ //群发消息 msg = readLine(input); //从客户端读取一行 while(!(2+"").equals(msg)){ String msg1 = userName+"说:\n "+msg+"#"; sendAllMsg(msg1); msg = readLine(input); } } if((2+"").equals(msg)){ //点对点发消息 msg = readLine(input); //从客户端读取一行 while(!(1+"").equals(msg)){ for(int i=0; i<MyServer.list.size(); i++){ if(MyServer.list.get(i).getUserName().equals(msg)){ String msg1 = userName+"说:\n "+readLine(input)+"#"; sendMsg(msg1); //往发出信息的客户端发送这条信息 ServerThread sst = MyServer.list.get(i); sst.sendMsg(msg1); //往要接受的用户发送信息 } } msg = readLine(input); } }else{ break; } } if(!(1+"").equals(msg)&&!(2+"").equals(msg)){ String strname = "请点击群聊还是点对点#"; output.write(strname.getBytes()); } } }else if(passWord.equals(map.get(userName))&&!eqUser()){ msg = "账号已经登录#"; System.out.println(msg); output.write(msg.getBytes()); }else{ msg = "账号或密码输入不正确,请重新输入 #"; output.write(msg.getBytes()); System.out.println(msg); } } } catch (IOException e) { e.printStackTrace(); }finally{ //关闭流和socket try { if(socket != null){ socket.close(); } if(input != null){ input.close(); } if(output != null){ output.close(); } } catch (IOException e) { e.printStackTrace(); } } } /** * 发送一条消息 */ public void sendMsg(String msg){ try { output.write(msg.getBytes()); } catch (IOException e) { e.printStackTrace(); } } /** * 群发消息 * @param msg */ public void sendAllMsg(String msg){ for(int i=0; i<MyServer.list.size(); i++){ ServerThread st = MyServer.list.get(i); st.sendMsg(msg); } } /** * 每次读取一行 * @param input 输入流对象 * @return * @throws IOException */ public String readLine(InputStream input) throws IOException{ //创建一个队列 ByteArrayOutputStream intent = new ByteArrayOutputStream(); while(true){ //每次读一个字节 int n = input.read(); if(n == '#'){ break; } //把每次读取的字节存到队列中 intent.write(n); } //把队列转换成字节数组 byte[] bytes = intent.toByteArray(); //把字节转换成字符串 String str = new String(bytes, "GB2312"); return str; } /** * 读取文件时每次读取一行 * @param input 输入流对象 * @return * @throws IOException */ public static String readFileLine(FileInputStream input) throws IOException{ //创建一个队列 ByteArrayOutputStream intent = new ByteArrayOutputStream(); while(true){ //每次读一个字节 int n = input.read(); if(n == ' '){ break; } //把每次读取的字节存到队列中 intent.write(n); } //把队列转换成字节数组 byte[] bytes = intent.toByteArray(); //把字节转换成字符串 String str = new String(bytes, "GB2312"); return str; } /** * 获取用户名 * @return */ public String getUserName() { return userName; } /** * 比较此用户是否登录 * @return */ public boolean eqUser(){ for(int i=0; i<MyServer.list.size(); i++){ ServerThread st = MyServer.list.get(i); if(st.getUserName().equals(userName)){ return false; } } return true; } }
6、客户端:
登录界面:Cilent
package com.kehuiduan; import java.awt.Dimension; import java.awt.FlowLayout; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPasswordField; public class Cilent { /** * 开始登录界面 */ public void getUI(){ JFrame jf = new JFrame("登录界面"); jf.setLayout(new FlowLayout()); JLabel jb1 = new JLabel("账号"); jf.add(jb1); //实例化一个JComboBox类的对象 JComboBox jcbName = new JComboBox(); //设置下拉框对象可以编辑 jcbName.setEditable(true); //设置jcbName的大小 jcbName.setPreferredSize(new Dimension(220,25)); jf.add(jcbName); JLabel jb2 = new JLabel("密码"); jf.add(jb2); //实例化一个JPasswordField类的对象 JPasswordField jpaPwd = new JPasswordField(); //设置jpaPwd的大小 jpaPwd.setPreferredSize(new Dimension(220,25)); jf.add(jpaPwd); JButton jb3 = new JButton("确定"); jf.add(jb3); JButton jb4 = new JButton("注册"); jf.add(jb4); // jf.setUndecorated(true); jf.setSize(300,290); //给窗体设置大小 jf.setResizable(false); //设置窗体的大小是否改变 jf.setLocationRelativeTo(null); //居中 jf.setDefaultCloseOperation(3); //关闭窗体时,关闭程序 jf.setVisible(true); //显示窗体 //创建事件监听器对象,给按钮添加监听器 ClientAction cal = new ClientAction(jf,jcbName,jpaPwd); jb3.addActionListener(cal); jb4.addActionListener(cal); } public static void main(String[] args) { new Cilent().getUI(); } }
登录界面的事件监听器类:ClientAction
package com.kehuiduan; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPasswordField; import 对话界面.MyDialog; import 注册.ZhuCe; public class ClientAction implements ActionListener{ private JComboBox jcbName; private JPasswordField jpaPwd; private JFrame jf; private InputStream input; private OutputStream output; private Socket client; public ClientAction(JFrame jf, JComboBox jcbName, JPasswordField jpaPwd){ this.jf = jf; this.jcbName = jcbName; this.jpaPwd = jpaPwd; } public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); try { //连接服务器 client = new Socket("192.168.0.133",8888); //得到输入输出流对象 input = client.getInputStream(); output = client.getOutputStream(); if("确定".equals(cmd)){ //往服务器发一条消息 output.write(("确定"+"#").getBytes()); //得到下拉框的内容 String userName = (String) jcbName.getSelectedItem(); //得到密码框的内容 char[] charpwd = jpaPwd.getPassword(); String passWord = new String(charpwd); //从服务器读取一条信息 String msg = readLine(input); System.out.println(msg); //往服务器书写账号 System.out.println("userName :"+userName); output.write((userName+"#").getBytes()); //从服务器读取一条信息 msg = readLine(input); System.out.println(msg); //往服务器书写密码 System.out.println("passWord :"+passWord); output.write((passWord+"#").getBytes()); //从服务器读取一条信息 msg = readLine(input); System.out.println(msg); if(msg.equals("服务器登录成功")){ //登录成功,关闭登录界面 jf.dispose(); MyDialog st = new MyDialog(input, output, userName); st.mgUI(); }else if(msg.equals("账号已经登录")){ JOptionPane.showMessageDialog(null, msg); }else{ //登录不成功,弹出一个对话框 System.out.println(msg); JOptionPane.showMessageDialog(null, msg); } } if("注册".equals(cmd)){ //往服务器发一条消息 output.write(("注册"+"#").getBytes()); ZhuCe zhuce = new ZhuCe(input,output); zhuce.cell(); } } catch (Exception e1) { e1.printStackTrace(); //运行报错时关闭Socket和输入输出流 try { if(client != null){ client.close(); } if(input != null){ input.close(); } if(output != null){ output.close(); } } catch (IOException e2) { e2.printStackTrace(); } } } /** * 每次读取一行 * @param input 输入流对象 * @return * @throws IOException */ public String readLine(InputStream input) throws IOException{ //创建一个队列 ByteArrayOutputStream intent = new ByteArrayOutputStream(); while(true){ //每次读一个字节 int n = input.read(); if(n == '#'){ break; } //把每次读取的字节存到队列中 intent.write(n); } //把队列转换成字节数组 byte[] bytes = intent.toByteArray(); //把字节转换成字符串 String str = new String(bytes, "GB2312"); return str; } }
注册界面: ZhuCe
package 注册; import java.awt.Dimension; import java.awt.FlowLayout; import java.io.InputStream; import java.io.OutputStream; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPasswordField; import javax.swing.JTextField; public class ZhuCe { private InputStream input; private OutputStream output; public ZhuCe(InputStream input, OutputStream output) { this.input = input; this.output = output; } public void cell(){ JFrame jf = new JFrame(); jf.setTitle("注册界面"); jf.setLayout(new FlowLayout()); JLabel jb1 = new JLabel("账号"); jf.add(jb1); //实例化一个JTextField类的对象 JTextField jcbName = new JTextField(); //设置jcbName的大小 jcbName.setPreferredSize(new Dimension(220,25)); jf.add(jcbName); JLabel jb2 = new JLabel("密码"); jf.add(jb2); //实例化一个JPasswordField类的对象 JPasswordField jpaPwd = new JPasswordField(); //设置jpaPwd的大小 jpaPwd.setPreferredSize(new Dimension(220,25)); jf.add(jpaPwd); JButton jb3 = new JButton("确定"); jf.add(jb3); jf.setSize(300,290); //给窗体设置大小 jf.setLocationRelativeTo(null); //居中 jf.setDefaultCloseOperation(3); //关闭窗体时,关闭程序 jf.setVisible(true); //显示窗体 //创建事件监听器对象,给按钮添加监听器 ZhuCeAction dat = new ZhuCeAction(input, output, jf, jcbName, jpaPwd); jb3.addActionListener(dat); } }
注册界面的事件监听器类:
package 注册; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPasswordField; import javax.swing.JTextField; public class ZhuCeAction implements ActionListener{ private JTextField jcbName; private JPasswordField jpaPwd; private JFrame jf; private InputStream input; private OutputStream output; public ZhuCeAction(InputStream input, OutputStream output, JFrame jf, JTextField jcbName, JPasswordField jpaPwd){ this.input = input; this.output = output; this.jf = jf; this.jcbName = jcbName; this.jpaPwd = jpaPwd; } /** * */ public void actionPerformed(ActionEvent e) { String userName = jcbName.getText(); //得到文本框输入的内容 char[] ch = jpaPwd.getPassword(); //得到密码框输入的内容 String passWord = new String(ch); //把从密码框得到的内容转换成字符串 try { output.write((userName+"#").getBytes()); // 把账号发给服务器 output.write((passWord+"#").getBytes()); // 把密码发给服务器 String msg = readLine(input); //读取从服务器发送过来的信息 System.out.println(msg); if("此账号已存在,请重新注册".equals(msg)){ //注册不成功,弹出一个对话框 System.out.println(msg); JOptionPane.showMessageDialog(null, msg); } if("注册成功".equals(msg)){ // String ss = "注册窗口已经关闭#"; // output.write(ss.getBytes()); jf.dispose(); } } catch (IOException e2) { e2.printStackTrace(); } } /** * 每次读取一行 * @param input 输入流对象 * @return * @throws IOException */ public String readLine(InputStream input) throws IOException{ //创建一个队列 ByteArrayOutputStream intent = new ByteArrayOutputStream(); while(true){ //每次读一个字节 int n = input.read(); if(n == '#'){ break; } //把每次读取的字节存到队列中 intent.write(n); } //把队列转换成字节数组 byte[] bytes = intent.toByteArray(); //把字节转换成字符串 String str = new String(bytes, "GB2312"); return str; } }
聊天界面:MyDialog
package 对话界面; import java.awt.FlowLayout; import java.io.InputStream; import java.io.OutputStream; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.JTextArea; public class MyDialog { private InputStream input ; private OutputStream output ; private String userName; public MyDialog (InputStream input, OutputStream output,String userName){ this.input = input; this.output = output; this.userName = userName; } /** * 对话框界面 */ public void mgUI(){ JFrame jf = new JFrame(userName+"--对话界面"); jf.setLayout(new FlowLayout()); //设置一个文本区设置行和列,然后添加到窗体上 JTextArea jtf = new JTextArea(17,50); jtf.setEditable(false); //文本区不可直接往上面写内容 //jtf.setPreferredSize(new Dimension(550,350)); JScrollPane jspf = new JScrollPane(jtf); //内容超过文本区设置的行和列时,自动生成滚动条 jf.add(jspf); JButton jb1 = new JButton("群聊"); jf.add(jb1); JButton jb2 = new JButton("点对点"); jf.add(jb2); JLabel jl = new JLabel("对话输入框:"); jf.add(jl); JTextArea jtf1 = new JTextArea(10,50); //jtf1.setPreferredSize(new Dimension(550,100)); JScrollPane jspf1 = new JScrollPane(jtf1); jf.add(jspf1); JButton jb3 = new JButton("发送"); jf.add(jb3); jf.setSize(620,600); //给窗体设置大小 jf.setLocationRelativeTo(null); //居中 jf.setDefaultCloseOperation(3); //关闭窗体时,关闭程序 jf.setVisible(true); //显示窗体 DialogThread dt = new DialogThread(input, jtf); dt.start(); //创建事件监听器对象,给按钮添加监听器 DialogAction dat = new DialogAction(output,jtf1); jb1.addActionListener(dat); jb2.addActionListener(dat); jb3.addActionListener(dat); } }
聊天界面接受消息的线程类:DialogThread
package 对话界面; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import javax.swing.JOptionPane; import javax.swing.JTextArea; public class DialogThread extends Thread{ private InputStream input ; private JTextArea jtf; public DialogThread(InputStream input, JTextArea jtf) { this.input = input; this.jtf = jtf; } public void run(){ while(true){ try{ //从服务器读取一条信息 String msg = readLine(input); //如果没有点击是群聊还是点对点,弹出一个提示框 if("请点击群聊还是点对点".equals(msg)){ JOptionPane.showConfirmDialog(null, msg); continue; } //将读取的信息在第一个文本框中显示出来 jtf.append(msg+"\n"); }catch (Exception e) { e.printStackTrace(); } } } /** * 每次读取一行 * @param input 输入流对象 * @return * @throws IOException */ public String readLine(InputStream input) throws IOException{ //创建一个队列 ByteArrayOutputStream intent = new ByteArrayOutputStream(); while(true){ //每次读一个字节 int n = input.read(); if(n == '#'){ break; } //把每次读取的字节存到队列中 intent.write(n); } //把队列转换成字节数组 byte[] bytes = intent.toByteArray(); //把字节转换成字符串 String str = new String(bytes, "GB2312"); return str; } }
聊天界面的事件监听器类:
package 对话界面; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.io.OutputStream; import javax.swing.JTextArea; public class DialogAction implements ActionListener{ private OutputStream output ; private JTextArea jtf1; public DialogAction(OutputStream output, JTextArea jtf1) { this.output = output; this.jtf1 = jtf1; } public void actionPerformed(ActionEvent e) { //获取按钮上的字符串 String str = e.getActionCommand(); //得到在第二个文本框中输入的内容 String cmd = jtf1.getText(); try { if("群聊".equals(str)){ String a = 1+"#"; output.write(a.getBytes()); //往服务器发送一条信息 } if("点对点".equals(str)){ String b = 2+"#"; output.write(b.getBytes()); //往服务器发送一条信息 } if("发送".equals(str)){ output.write((cmd+"#").getBytes()); //往服务器发送一条信息 } } catch (IOException e1) { e1.printStackTrace(); } } }