赞
踩
首先明确一下目的:实现一个服务端加多个客户端可用,带有群聊和私聊功能的小项目(通过控制台输入);
服务端起到了转发的作用,一个client通过发送消息给服务端,服务端接受到消息之后判断是要群发还是私发(私发有格式),然后将消息发送给所有在线的客户端;
明确了功能咱们来分析下,服务端是用来群发的,群发给谁?所有在线的client,那么这些client是需要上线就存储,下线就移除的,所以肯定是需要容器的,并且这个容器还是能够支持多线程等功能,我们选择的是CopyOnwriteArrayList那么服务端的功能代码如下:
public class NewServe { static CopyOnWriteArrayList<Channel> array = new CopyOnWriteArrayList(); //一个静态的全局变量,方便使用,用来存放开启的客户端的信息; public static void main(String[] args) throws IOException { ServerSocket server = new ServerSocket(8888); //端口号是8888 System.out.println("----------Server------------"); while(true){ Socket client = server.accept(); Channel channel = new Channel(client); array.add(channel); new Thread(channel).start(); } //阻断式的接受就直接一个永真循环 } } class Channel implements Runnable{ //channel封装了 private String name = null; private boolean flag = true; private DataInputStream dis; private DataOutputStream dos; private Socket client; public Channel(Socket client) { this.client = client; try { dos = new DataOutputStream(client.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } try { dis = new DataInputStream(client.getInputStream()); name = dis.readUTF(); } catch (IOException e) { e.printStackTrace(); } } //接受消息,之所以定义就是封装一下,降低了run()方法里边的代码量; public String receive(){ String msg = ""; try { msg = dis.readUTF(); } catch (IOException e) { //这里边捕获到异常之后做的事情,包括通知所有人还包括把这个关闭流等; sendAll("-----系统消息:"+name+"退出了群聊"); reseve(dis); flag = false; } return msg; } //没用但是适合循序渐进的编程方式,先写出来这个send方法,之后。。。。自己发现了sendAll public void send(String msg){ try { dos.writeUTF(msg); dos.flush(); } catch (IOException e) { e.printStackTrace(); reseve(dos); flag = false; } } //发送给所有人 public void sendAll(String msg){ if(msg.contains("@")) { String[] str1 = msg.split("@"); for(Channel ch:NewServe.array){ if(str1[0].equals(ch.name)) { ch.send("(来自私聊)"+name+":"+str1[1]); }//判断一下是不是用的是私聊格式的; } }else { for(Channel ch:NewServe.array){ if(ch.client!=this.client){ if(!ch.client.isClosed()) { ch.send(name+":"+msg); }else { NewServe.array.remove(ch); //知道有关着的客户端的时候立马把他从容器中移除; } } } } } //就是封装的一个能够关闭流的方法方便调用; public void reseve(Closeable... dis){ for(Closeable d:dis){ try { d.close(); } catch (IOException e) { e.printStackTrace(); } } } //因为是多线程,并且实现了Runnable接口所以必须重写这个方法的; @Override public void run() { while(flag){ String msg = receive(); sendAll(msg); } } }
以上就是服务端的全部代码,总的就是,服务端接受到一个socket 就创建一个channel对象并传入socket,同时容器内加上这个channel对象;channel是一个实现了Runnable接口的类,里边有唯一的socket 作用就是如果接受到这个socket的发送信息就直接调用类里边的sendAll方法发送给所有人(通过判断发送给私人),每一个socket都有一个channel对象与之对应;
在写网络编程的时候我说过,如果想实现收发同步那必须是要用多线程的,所以毫无疑问,不仅是多线程,还有做到收一个线程,发一个线程,以下是封装后的代码:
发送端
public class Send1 implements Runnable{ private String name = ""; private DataOutputStream dos ; private boolean flag = true; private Socket client; static Scanner in = new Scanner(System.in); public Send1(Socket client){ this.client = client; try { dos = new DataOutputStream(client.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { this.name = Thread.currentThread().getName(); try { dos.writeUTF(name); dos.flush(); } catch (IOException e1) { e1.printStackTrace(); } while(flag){ String msg = in.next(); try { dos.writeUTF(msg); dos.flush(); } catch (IOException e) { e.printStackTrace(); flag = false; reseve(dos); } } } public void reseve(Closeable... dis){ for(Closeable d:dis){ try { d.close(); } catch (IOException e) { e.printStackTrace(); } } } }
接受端
public class Receive1 implements Runnable{ private DataInputStream dis ; private boolean flag = true; private Socket client; static Scanner in = new Scanner(System.in); public Receive1(Socket client){ this.client = client; try { dis = new DataInputStream(client.getInputStream()); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { String msg = ""; while(flag){ try { msg = dis.readUTF(); System.out.println(msg); } catch (IOException e) { e.printStackTrace(); reseve(dis); flag = false; } } } public void reseve(Closeable... dis){ for(Closeable d:dis){ try { d.close(); } catch (IOException e) { e.printStackTrace(); } } } }
这两类都是实现了Runnable接口,并且两个类中封装的socket肯定是同一个(参考用客户端的时候怎么写);也就是稍微封装一下,两类的逻辑差不多,都是平平无奇的;
用客户端的时候长这样
public class Newclient {
public static void main(String[] args) throws UnknownHostException, IOException {
System.out.println("--------------Client-------------");
Socket client = new Socket("localhost",8888);
new Thread(new Send1(client),"李世翔").start();
new Thread(new Receive1(client)).start();
}
}
由于个人水平我也就能写到这个地方了,有好多地方能够改的,写出来仅供参考;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。