你好,博客园!!第一弹~局域网下的简易聊天室,socket与多线程简结
Posted 枫林晚月
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你好,博客园!!第一弹~局域网下的简易聊天室,socket与多线程简结相关的知识,希望对你有一定的参考价值。
发觉博客园里面关于这些基本知识点的详细内容真是应有尽有,所以这里的随笔就不再重复了,就积累一下简单的用法——
1.Socket
最近学网络编程,也就是Socket,套接字,一个用来建立链接传输数据的工具。
数据传输发生在“客户端”与“服务端”之间,下面是一种建立连接传输数据的简单方法:
(1)客户端
1 try{ 2 //服务端ip 3 String ip = "127.0.0.1"; 4 //服务器端口 5 int port = 5000; 6 7 //1.通过服务器ip和端口,创建Socket对象,连接服务器 8 Socket socket = new Socket(ip, port); 9 //2.获取Socket输出流,并包装为DataOutputStream 10 DataOutputStream output = new DataOutputStream(socket.getOutputStream()); 11 //传输的内容 12 String send = "halo"; 13 //3.加密输出,发送到服务端 14 //将一个字符串转换成字符数组 15 char[] char_arr = send.toCharArray(); 16 //对每个字符·进行加密操作 17 for(int i=0; i<char_arr.length; i++){ 18 output.writeChar(char_arr[i] + 12); 19 } 20 //在输出内容后加一个特殊符号作为结束符 21 output.writeChar(33333); 22 //刷写内容 23 output.flush(); 24 } catch (IOException e) { 25 e.printStackTrace(); 26 }
以上为客户端创建连接,发送数据的简单方法。下面是接收数据的方法,注意如果服务端不发送数据过来,代码会停留在字符读取那里。
1 try{ 2 //4.获取Socket输入流,并包装为DataInputStream 3 DataInputStream input = new DataInputStream(socket.getInputStream()); 4 //5.获取并解密,接收返回结果 5 String rtn = ""; 6 while(true) { 7 //从输出流中读取一个字符,直到结束符 8 int char_src = input.readChar(); 9 if(char_src != 33333){ 10 rtn = rtn + (char)(char_src - 12); 11 }else{ 12 break; 13 } 14 } 15 //读取结束,rtn即是服务端发送过来的信息 16 } catch (IOException e) { 17 e.printStackTrace(); 18 }
使用完Socket,一定要关闭Socket及相应的输入输出流。
1 try {2 if(output != null){output.close();} 3 if(input != null){input.close();} 4 if(socket != null){socket.close();} 5 } catch (IOException e) { 6 e.printStackTrace(); 7 }
(1)服务端
服务端创建连接的方法——
1 //1.创建ServerSocket对象,监听端口5000 2 ServerSocket server_socket = new ServerSocket(5000); 3 //2.调用ServerSocket的accept方法,等待客户端连接(若没有连接,代码停留在这里),成功连接之后,返回Socket对象 4 Socket socket = server_socket.accept();
创建连接后,数据传输方法跟客户端是同样的步骤,获取Socket的输出输入流并包装,然后加密输出,获取解密,这里不重复了。
注意这里用了加密传输,所以服务端客户端的加密方法、解密方法一定要一致。用完ServerSocket,也要关闭。
******************************************************
2.多线程
多线程简单地说,就是为程序建立分支。例如,在聊天室的服务端中,每有一个用户连接进来,就为其建立一个用于接收的分支,这样可以做到同时接受多个用户的数据。
建立多线程的方法
1 //这是主线程的类,包含主方法 2 public class text{ 3 public static void main(String [] args){ 4 //new一个thread类,并调用它的start()方法,就建立了一个线程。这步可以重复,不断地建立分支线程 5 //分支线程建立后,程序一边执行下面的代码,一边执行thread类中的run()方法(分支) 6 new thread().start(); 7 //继续主线程的其他代码 8 System.out.println("主线程"); 9 } 10 } 11 12 //thread类必需继承Thread类,这是建立分支线程的关键 13 class thread extends Thread{ 14 //需要在分支线程中执行的代码放到run()中 15 public void run(){ 16 System.out.println("分支线程"); 17 } 18 }
多线程中各个线程同时运行,其运行没有确定的顺序,并不是先启动的线程一定先执行,当前一刻,谁抢占了 CUP 资源,谁就执行。
所以上面的程序在cmd中会有不同的执行结果,可能是"主线程"在前面,也可能是"分支线程"在前面。
多线程还可以通过实现 Runnable 接口来实现,详见聊天室的代码。
************************************************
3.聊天室
客户端——
1 import java.awt.*; 2 import java.awt.event.*; 3 import javax.swing.*; 4 import java.io.*; 5 import java.net.*; 6 7 public class chat { 8 9 //主方法 10 public static void main(String[] args) { 11 chat ch = new chat(); 12 ch.loginframe(); 13 14 } 15 16 //登录窗口 17 public JFrame lf; 18 public JTextField lmi; 19 public JTextField lii; 20 public int ltz = 1; 21 public String y; 22 public void loginframe(){ 23 lf = new JFrame("创建用户"); 24 lf.setVisible(true); 25 lf.setSize(400, 280); 26 lf.setLocationRelativeTo(null); 27 lf.addWindowListener(new WindowAdapter(){ 28 public void windowClosing(WindowEvent e){ 29 closes(); 30 System.exit(0); 31 } 32 }); 33 34 JPanel op = new JPanel(); 35 lf.add(op); 36 Box lbox = Box.createVerticalBox(); 37 op.add(lbox); 38 39 lbox.add(Box.createVerticalStrut(40)); 40 41 JPanel lfp = new JPanel(); 42 lfp.setOpaque(false); 43 JLabel lft = new JLabel("服务器ip:"); 44 lft.setFont(new Font("华文中宋", Font.PLAIN, 18)); 45 lii = new JTextField("127.0.0.1" ,9); 46 lii.setFont(new Font("微软雅黑", Font.PLAIN, 18)); 47 lfp.add(lft); 48 lfp.add(lii); 49 lbox.add(lfp); 50 51 JPanel lip = new JPanel(); 52 lip.setOpaque(false); 53 JLabel lwt = new JLabel("用户名:"); 54 lwt.setFont(new Font("华文中宋", Font.PLAIN, 18)); 55 lmi = new JTextField(10); 56 lmi.setFont(new Font("微软雅黑", Font.PLAIN, 18)); 57 lmi.requestFocus(); 58 lip.add(lwt); 59 lip.add(lmi); 60 lbox.add(lip); 61 62 lbox.add(Box.createVerticalStrut(20)); 63 64 JPanel obp = new JPanel(); 65 JButton odb = new JButton("加入聊天"); 66 odb.setFont(new Font("微软雅黑", Font.PLAIN, 15)); 67 JLabel obt = new JLabel(" "); 68 obt.setFont(new Font("华文中宋", Font.PLAIN, 15)); 69 JButton oqb = new JButton("清空输入"); 70 oqb.setFont(new Font("微软雅黑", Font.PLAIN, 15)); 71 obp.add(odb); 72 obp.add(obt); 73 obp.add(oqb); 74 lbox.add(obp); 75 76 //按钮监听 77 odb.addActionListener(new ActionListener(){ 78 public void actionPerformed(ActionEvent e) { 79 y = lmi.getText(); 80 if("".equals(y)){ 81 JOptionPane.showMessageDialog(null, 82 "请输入用户名!", "创建用户", JOptionPane.INFORMATION_MESSAGE); 83 lmi.requestFocus(); 84 }else{ 85 ip = lii.getText(); 86 ysend(); 87 lf.dispose(); 88 chatframe(); 89 //开启一条接收信息的线程 90 th.start(); 91 } 92 } 93 }); 94 95 oqb.addActionListener(new ActionListener(){ 96 public void actionPerformed(ActionEvent e) { 97 lmi.setText(""); 98 } 99 }); 100 101 } 102 103 //聊天室 104 public JTextField i; 105 public JTextArea l; 106 public String book = ""; 107 public void chatframe(){ 108 JFrame cf = new JFrame("聊天室 - " + y); 109 cf.setVisible(true); 110 cf.setSize(600, 500); 111 cf.setLocationRelativeTo(null); 112 cf.addWindowListener(new WindowAdapter(){ 113 public void windowClosing(WindowEvent e){ 114 closes(); 115 System.exit(0); 116 } 117 }); 118 119 JPanel sfp = new JPanel(); 120 sfp.setLayout(new BorderLayout()); 121 i = new JTextField("请在此输入聊天内容"); 122 i.setSelectionStart(0); 123 i.setSelectionEnd(9); 124 i.setFont(new Font("微软雅黑", Font.PLAIN, 20)); 125 JButton fsb = new JButton("发 送"); 126 fsb.setFont(new Font("微软雅黑", Font.PLAIN, 15)); 127 sfp.add(i, BorderLayout.CENTER); 128 sfp.add(fsb, BorderLayout.EAST); 129 cf.add(sfp, BorderLayout.SOUTH); 130 131 l = new JTextArea(); 132 l.setFont(new Font("华文中宋", Font.PLAIN, 18)); 133 JScrollPane jt = new JScrollPane(l); 134 l.setText(book); 135 cf.add(jt, BorderLayout.CENTER); 136 137 //按钮监听 138 fsb.addActionListener(new ActionListener(){ 139 public void actionPerformed(ActionEvent e) { 140 if("".equals(i.getText())){ 141 i.setText("不能发送空白信息~"); 142 i.setSelectionStart(0); 143 i.setSelectionEnd(9); 144 i.requestFocus(); 145 }else{ 146 nsend(i.getText()); 147 i.setText(""); 148 i.requestFocus(); 149 } 150 } 151 }); 152 153 } 154 //made by feng(^-^) 155 //创建用户 156 public void ysend(){ 157 try { 158 String r; 159 connect(); 160 //发送到服务端 161 encryptWrite(y, out); 162 r = readDecrypt(in); 163 book = r; 164 String[] str = r.split("进入聊天室~"); 165 if(!(str[0].equals(y))){ 166 JOptionPane.showMessageDialog(null, 167 "用户“"+y+"”已在聊天室中,系统为你改为"+"“"+str[0]+"”", 168 "创建用户", JOptionPane.INFORMATION_MESSAGE); 169 y = str[0]; 170 } 171 } catch (IOException e1) { 172 e1.printStackTrace(); 173 } 174 } 175 176 //创建socket及相关流 177 public String ip; 178 public int port = 6000; 179 public Socket so; 180 public DataOutputStream out; 181 public DataInputStream in; 182 Thread th = new Thread(new Runc()); 183 //连接到服务器 184 public void connect(){ 185 try { 186 so = new Socket(ip, port); 187 out = new DataOutputStream(so.getOutputStream()); 188 in = new DataInputStream(so.getInputStream()); 189 } catch (IOException e) { 190 e.printStackTrace(); 191 } 192 } 193 194 //内容传输 195 public void nsend(String n){ 196 try { 197 //发送到服务端 198 String s = y + ":" + n; 199 encryptWrite(s, out); 200 } catch (IOException e) { 201 e.printStackTrace(); 202 } 203 } 204 205 //关闭流,停止线程运行 206 public boolean bc = true; 207 public void closes(){ 208 try { 209 bc = false; 210 //延迟两秒 211 //Thread.sleep(1000 * 2); 212 if(out != null){out.close();} 213 if(in != null){in.close();} 214 if(so != null){so.close();} 215 } catch (IOException e) { 216 e.printStackTrace(); 217 } 218 } 219 220 //加密输出 221 public void encryptWrite(String src, DataOutputStream output) throws IOException{ 222 char[] char_arr = src.toCharArray(); 223 for(int i=0; i<char_arr.length; i++){ 224 output.writeChar(char_arr[i] + 12); 225 } 226 output.writeChar(33333); 227 output.flush(); 228 } 229 230 //读取解密 231 public String readDecrypt(DataInputStream input) throws IOException{ 232 String rtn = ""; 233 234 while(true) { 235 //从输出流中读取一个字符,直到结束符 236 int char_src = input.readChar(); 237 if(char_src != 33333){ 238 rtn = rtn + (char)(char_src - 12); 239 }else{ 240 break; 241 } 242 } 243 return rtn; 244 } 245 246 /**用于接收消息的线程*/ 247 class Runc implements Runnable{ 248 public void run(){ 249 try { 250 while(bc){ 251 String re = readDecrypt(in); 252 book = book + re; 253 l.setText(book); 254 } 255 } catch (SocketException e) { 256 //System.out.println("socket关闭,停止接受消息"); 257 } catch (EOFException e) { 258 //System.out.println("与服务器断开连接"); 259 } catch (IOException e) { 260 e.printStackTrace(); 261 } 262 } 263 } 264 265 }
服务端——
1 import java.awt.BorderLayout; 2 import java.awt.event.*; 3 import java.io.*; 4 import java.net.*; 5 import javax.swing.*; 6 7 public class chatserver { 8 //聊天记录 9 public static String book = ""; 10 //当前消息 11 public static String nowm; 12 //用户名数组 13 public static String[] name; 14 //Socket数组 15 public static Socket[] so; 16 //每个用户配备一个输出流 17 public static DataOutputStream[] out; 18 //用户数量 19 public static int num = 0; 20 public static ServerSocket ss = null; 21 public static Socket s = null; 22 public static void main(String [] args){ 23 chatserver c = new chatserver(); 24 c.frame(); 25 26 try { 27 //监听6000 28 ss = new ServerSocket(6000); 29 //初始化用户数组 30 name = new String[100]; 31 so = new Socket[100]; 32 out = new DataOutputStream[100]; 33 //System.out.println("OK!"); 34 while(true){ 35 s = ss.accept(); 36 so[num] = s; 37 out[num] = new DataOutputStream(so[num].getOutputStream()); 38 num++; 39 //当有用户接入时,开启一条线程 40 new Thread(new Runs(so[num-1])).start(); 41 } 42 } catch (SocketException e) { 43 //System.out.println("ss已关闭!"); 44 } catch (IOException e) { 45 e.printStackTrace(); 46 c.closes(); 47 } 48 } 49 50 //服务器窗口 51 public JFrame sf; 52 public static JLabel sl; 53 public static JLabel rl; 54 public void frame() { 55 sf = new JFrame("服务端"); 56 sf.setVisible(true); 57 sf.setSize(400, 250); 58 sf.setLocationRelativeTo(null); 59 sf.addWindowListener(new WindowAdapter(){ 60 public void windowClosing(WindowEvent e){ 61 closes(); 62 System.exit(0); 63 } 64 }); 65 66 sl = new JLabel("当前在线人数:"+num); 67 rl = new JLabel("等待用户接入..."); 68 sf.add(sl, BorderLayout.NORTH); 69 sf.add(rl, BorderLayout.CENTER); 70 } 71 //made by feng(+-+) 72 //更新窗口显示 73 public void renew(){ 74 sl.setText("当前在线人数:"+num); 75 if (num == 0) { 76 rl.setText("等待用户接入..."); 77 }else{ 78 String s = ""; 79 for (int n = 0; n <= (num - 1); n++) { 80 s = s + name[n] + ","; 81 } 82 rl.setText("在线用户:"+s); 83 } 84 } 85 86 //用户接入处理 87 public synchronized String addh(String y){ 88 int x = 2; 89 String newy = y; 90 //检测用户名是否已经存在 91 for(int n=0; n<(num-1); n++){ 92 if(newy.equals(name[n])){ 93 //若存在,改为+(n) 94 newy = y + " (" +x+ ")"; 95 x++; 96 n = -1; 97 } 98 } 99 name[num-1] = newy; 100 nowm = newy + "进入聊天室~" + "\n"; 101 book = book + nowm; 102 renew(); 103 sendm(); 104 return newy; 105 } 106 107 //信息处理 108 public synchronized void messageh(String n){ 109 nowm = n + "\n"; 110 book = book + nowm; 111 sendm(); 112 } 113 114 //用户断开处理 115 public synchronized void reduceh(String y) { 116 int m = 0; 117 //查找断开连接的用户 118 for(int n=0; n<=(num-1); n++){ 119 if(y.equals(name[n])){ 120 m = n; 121 break; 122 } 123 } 124 //关闭用户的输出流 125 try { 126 out[m].close(); 127 } catch (IOException e) { 128 e.printStackTrace(); 129 } 130 //数组元素前移 131 for(int n=m; n<=(num-1); n++){ 132 name[n] = name[n+1]; 133 so[n] = so[n+1]; 134 out[n] = out[n+1]; 135 } 136 //用户数量减1 137 num--; 138 renew(); 139 nowm = y + "离开聊天室~" + "\n"; 140 book = book + nowm; 141 sendm(); 142 } 143 144 //消息传输 145 public void sendm(){ 146 try { 147 for(int n=0; n<=(num-1); n++){ 148 encryptWrite(nowm, out[n]); 149 } 150 } catch (IOException e) { 151 e.printStackTrace(); 152 closes(); 153 } 154 } 155 156 //加密输出 157 public void encryptWrite(String src, DataOutputStream output) throws IOException{ 158 char[] char_arr = src.toCharArray(); 159 for(int i=0; i<char_arr.length; i++){ 160 output.writeChar(char_arr[i] + 12); 161 } 162 output.writeChar(33333); 163 output.flush(); 164 } 165 166 //关闭socket及流 167 public void closes() { 168 try{ 169 for(int n=0; n<=(num-1); n++){ 170 out[n].close(); 171 } 172 //System.out.println("outputStream关闭"); 173 if(s != null){s.close();} 174 if(ss != null){ss.close();} 175 //System.out.println("s,ss关闭"); 176 } catch (IOException e1) { 177 e1.printStackTrace(); 178 } 179 } 180 } 181 182 /**接收客户端消息*/ 183 class Runs implements Runnable{ 184 chatserver cs = new chatserver(); 185 186 //创建流 187 public Socket socket; 188 public DataInputStream input; 189 public Runs(Socket s){ 190 socket = s; 191 try { 192 input = new DataInputStream(socket.getInputStream()); 193 } catch (IOException e) { 194 e.printStackTrace(); 195 } 196 } 197 198 public void run(){ 199 String n = null; 200 try { 201 //将输入传到主线程并返回用户名 202 n = cs.addh(readDecrypt(input)); 203 while(true){ 204 cs.messageh(readDecrypt(input)); 205 } 206 } catch (SocketException e) { 207 //System.out.println("socket关闭,停止接受消息"); 208 } catch (EOFException e) { 209 //System.out.println("客户端断开,关闭线程"); 210 //断开连接后移除用户 211 cs.reduceh(n); 212 //System.out.println(n+"退出成功!"); 213 } catch (IOException e) { 214 e.printStackTrace(); 215 } finally{ 216 try { 217 if(input != null){ input.close();} 218 if(socket != null){ socket.close();} 219 } catch (IOException e) { 220 e.printStackTrace(); 221 } 222 } 223 } 224 225 //读取解密 226 public String readDecrypt(DataInputStream input) throws IOException{ 227 String rtn = ""; 228 229 while(true) { 230 //从输出流中读取一个字符,直到结束符 231 int char_src = input.readChar(); 232 if(char_src != 33333){ 233 rtn = rtn + (char)(char_src - 12); 234 }else{ 235 break; 236 } 237 } 238 return rtn; 239 } 240 }
支持多用户,可以在局域网下运行。虽然简单,但也是小弟呕心沥血之作,如果有大佬看见,也希望给点意见啦~
另外,有人知道怎么上传flsah动画吗,好像这个功能不怎么好使。。
第一弹就到这里啦~
以上是关于你好,博客园!!第一弹~局域网下的简易聊天室,socket与多线程简结的主要内容,如果未能解决你的问题,请参考以下文章