首先明确一下目的:实现一个服务端加多个客户端可用,带有群聊和私聊功能的小项目(通过控制台输入);
服务端
服务端起到了转发的作用,一个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();
}
}
由于个人水平我也就能写到这个地方了,有好多地方能够改的,写出来仅供参考;