简单聊天室

1.0
实现功能:只有一个客户端,实现可以进行多次输入,返回输入内容

public class server {
    public static void main(String[] args) throws IOException {
    
        ServerSocket serverSocket=new ServerSocket(8888);
        Socket client=serverSocket.accept();
        //这里这个数据流是可以实时同步的
        DataInputStream dis=new DataInputStream(client.getInputStream());
        DataOutputStream dos=new DataOutputStream(client.getOutputStream());

        boolean isRunning=true;
        while(isRunning){
            String msg=dis.readUTF();
            dos.writeUTF(msg);
            dos.flush();
        }
        dos.close();
        dis.close();
        client.close();
    }
}
public class Client {
    public static void main(String[] args) throws IOException {
        Socket client=new Socket("localhost",8888);
        DataOutputStream dos=new DataOutputStream(client.getOutputStream());
        DataInputStream dis=new DataInputStream(client.getInputStream());
        boolean isRunning=true;
        while(isRunning){
            BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
            String str=br.readLine();
            dos.writeUTF(str);
            String response=dis.readUTF();
            System.out.println(response);
        }
    }
}

现在想要运行多个客户端,但是服务器只能接收一次,所以要加入多线程
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------

2.0 加入多线程,实现多个客户端与服务器连接,此时客户端不发生改变。

定义服务器端放在最外面,其他内容丢入死循环当中,作用是让多个客户端可以进入,一次循环完成对应一个客户端的进入。
里面多线程包装,作用是避免出现客户端等待前面客户端的情况。

public class server {
    public static void main(String[] args) throws IOException {

        //服务器应该是可以和多个客户端进行连接的
        ServerSocket serverSocket = new ServerSocket(8888);
        while(true){
            Socket client = serverSocket.accept();

            new Thread(()->{
                DataInputStream dis = null;
                DataOutputStream dos=null;
                try {
                    dis = new DataInputStream(client.getInputStream());
                   dos= new DataOutputStream(client.getOutputStream());
                } catch (IOException e) {
                    e.printStackTrace();
                }


                boolean isRunning = true;
                while (isRunning) {
                    String msg = null;
                    try {
                        msg = dis.readUTF();
                        dos.writeUTF(msg);
                        dos.flush();
                    } catch (IOException e) {
                        e.printStackTrace();
                        isRunning=false;
                    }
                }
                try {
                    if(dos!=null){
                        dos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if(dis!=null){
                        dis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if(client!=null){
                        client.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();

        }
    }
}

用兰巴德套了这么多内容,会使代码不好维护,所以要封装一下,封装不对功能做出改动。

-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
主要是两个方面的改动,
特殊的,用一个工具类单独实现释放资源,两个方面都可以公用

public class Utils {
    public static void close(Closeable... targets) {
        for (Closeable target : targets) {
            if (target != null) {
                try {
                    target.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

服务器方面,用类实现多线程,用方法封装各个功能,
这里再说一下服务器多线程的原理,服务器的端口是不用进入循环的,接收端口数据需要进入循环,而且一般服务器都是无限循环,工作也不会停止,会有很多端口同时进行连接,所以要循环接收多个端口,一次成功的接受就表示一个端口连接成功,每个端口都开一个线程,因为这里的Channel对象每次都会new一次,所以不用担心锁的问题,这里没有资源共享,只是用了同样的方法。然后Channel类里面就封装了2.0服务器的所有操作,提高程序可维护性。

public class server {
    public static void main(String[] args) throws IOException {
        //服务器应该是可以和多个客户端进行连接的
        ServerSocket serverSocket = new ServerSocket(8888);
        while(true){
            Socket client = serverSocket.accept();
            new Thread(new Channel(client)).start();
        }
    }
}


//一个客户代表一个channel
class Channel implements Runnable{
    private DataInputStream dis;
    private DataOutputStream dos;
    private Socket client;
    private boolean isRunning=true;

    public Channel( Socket client) {
        this.client = client;
        try {
            dis = new DataInputStream(client.getInputStream());
            dos= new DataOutputStream(client.getOutputStream());
        } catch (IOException e) {
            release();
        }
    }

    //接收消息
    private  String receive()  {
        String msg="";
        try {
            msg=dis.readUTF();
        } catch (IOException e) {
            release();
        }
        return msg;

    }
    //发送消息
    private void send(String msg){
        try {
            dos.writeUTF(msg);
        } catch (IOException e) {
            release();
        }
    }
    //释放资源
    private void release(){
        this.isRunning=false;
        Utils.close(dis,dos,client);
    }

    @Override
    public void run() {
        while(isRunning){
            String msg=receive();
            if(!msg.equals("")){
                send(msg);
            }
        }
    }
}

Client方面
相对于2.0,在客户端方面主要实现了发送和接收消息的分离,之前受限于程序的连续性,必须发送消息之后才会收到消息,而正常的聊天室应该是可以任意收发的。这里就要用多进程将两个功能分开。

public class Client {
    public static void main(String[] args) throws IOException {
        Socket client=new Socket("localhost",8888);
        new Thread(new Send(client)).start();
        new Thread(new Receive(client)).start();
    }
}
public class Send implements Runnable {

    private boolean isRunning=true;
    private Socket client;
    private BufferedReader br;
    private DataOutputStream dos=null;
    public Send(Socket client) {
        this.client=client;
        br=new BufferedReader(new InputStreamReader(System.in));
        try {
            dos=new DataOutputStream(client.getOutputStream());
        } catch (IOException e) {
            this.release();
        }
    }



    public void release(){
        this.isRunning=false;
        Utils.close(dos,client);
    }
    public void send(String str){
        try {
            dos.writeUTF(str);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private String getMsg(){
        try {
            return br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";

    }


    @Override
    public void run() {
        while(isRunning){
            String msg=getMsg();
            if(!msg.equals("")){
                send(msg);
            }
        }
        release();
    }

}
public class Receive implements Runnable{
    private boolean isRunning=true;
    private Socket client=null;
    private BufferedReader br=null;
    private DataInputStream dis=null;

    public Receive(Socket client) {
        this.client = client;
        try {
            dis=new DataInputStream(client.getInputStream());
        } catch (IOException e) {
            System.out.println("111");
            release();
        }
    }

    public String  receive(){
        String response= null;
        try {
            response = dis.readUTF();
            return response;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }
    public void release(){
        this.isRunning=false;
        Utils.close(dis,client);
    }

    @Override
    public void run() {
        while(isRunning){
            String  msg=receive();
            if(!msg.equals("")){
                System.out.println(msg);
            }
        }
        release();
    }
}

3.0版本仍然没有实现群聊和私聊的具体功能,不同的客户端不能看到对方的消息。
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
聊天室4.0
服务器端,
在上面的基础上,加入了容器,将所有连接的客户端都放在这个容器里面,使得能够共享信息。
sendOthers在send的基础之上建立,每一个客户端在自己的线程当中发消息过来服务器,服务器都将消息传给除了它本身的所有客户端,实现消息的同步。具体的操作上呢,只不过原来是返回给本身线程,现在变成了将信息返回给其他的所有线程,这里就要使用这个容器来进行遍历,一个sendOthers方法和容器的使用就可以实现这个功能

CopyOnWriteArrayList可以实现多线程的快速同步

除此之外,还加入了群聊成员身份的标记,用户名作为客户端群聊第一条信息,并用字符串类型保留成员的名字,在操作开始,循环开始之前就进行一次sendOthers,让其他成员知道该成员到来,而且提醒该成员已经进入群聊。在run方法开始的时候实现成员进入提醒,在返回字符串时加上成员称谓。

public class server {
     static CopyOnWriteArrayList<Channel> all=new CopyOnWriteArrayList<>();
    public static void main(String[] args) throws IOException {
        //服务器应该是可以和多个客户端进行连接的
        ServerSocket serverSocket = new ServerSocket(8888);
        while(true){
            Socket client = serverSocket.accept();
            Channel c=new Channel(client);
            all.add(c);//管理所有成员
            new Thread(c).start();
        }
    }
}


//一个客户代表一个channel
class Channel implements Runnable{
    private DataInputStream dis;
    private DataOutputStream dos;
    private Socket client;
    private boolean isRunning=true;
    private String name;

    public Channel( Socket client) {
        this.client = client;
        try {
            dis = new DataInputStream(client.getInputStream());
            dos= new DataOutputStream(client.getOutputStream());
        } catch (IOException e) {
            release();
        }
    }

    //接收消息
    private  String receive()  {
        String msg="";
        try {
            msg=dis.readUTF();
        } catch (IOException e) {
            release();
        }
        return msg;

    }
    //发送消息  获取自己的消息发给其他人
    private void send(String msg){
        try {
            dos.writeUTF(msg);
        } catch (IOException e) {
            release();
        }
    }

    private void sendOthers(String msg){
        for (Channel other:all
             ) {
            if(other==this){
                continue;
            }else{
                other.send(msg);
            }
        }
    }


    //释放资源
    private void release(){
        this.isRunning=false;
        Utils.close(dis,dos,client);
    }

    @Override
    public void run() {
        name=receive();
        this.send("你进入群聊");
        sendOthers(name+"进入群聊");
        while(isRunning){
            String msg=receive();
            if(!msg.equals("")){
                sendOthers(name+":"+msg);
            }
        }
    }
}


客户端
再循环之外进行一次输入用户名操作,并将其作为线程名字,将其传入Send当中,然后发送第一条信息就是用户名。

public class Client {

    public static void main(String[] args) throws IOException {
         BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
         System.out.println("请输入用户名:");
         String uname=br.readLine();
        Socket client=new Socket("localhost",8888);
        new Thread(new Send(client),uname).start();
        new Thread(new Receive(client)).start();
    }

}
public class Send implements Runnable {

    private boolean isRunning=true;
    private Socket client;
    private BufferedReader br;
    private DataOutputStream dos=null;
    public Send(Socket client) {
        this.client=client;
        br=new BufferedReader(new InputStreamReader(System.in));
        try {
            dos=new DataOutputStream(client.getOutputStream());
        } catch (IOException e) {
            this.release();
        }
    }



    public void release(){
        this.isRunning=false;
        Utils.close(dos,client);
    }
    public void send(String str){
        try {
            dos.writeUTF(str);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private String getMsg(){
        try {
            return br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";

    }


    @Override
    public void run() {
    //4.0只有这里变化了//4.0只有这里变化了//4.0只有这里变化了
    //4.0只有这里变化了//4.0只有这里变化了//4.0只有这里变化了
    //4.0只有这里变化了//4.0只有这里变化了//4.0只有这里变化了
        try {
        dos.writeUTF(Thread.currentThread().getName());
    } catch (IOException e) {
        e.printStackTrace();
    }
        while(isRunning){

            String msg=getMsg();
            if(!msg.equals("")){
                send(msg);
            }
        }
        release();
    }

}

receive没有改变,参考3.0代码。
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
-----------------------------------------分割线-----------------------------------------------
聊天室5.0
实现私聊功能

我这里设置私聊的格式是@+名字+?+内容
在服务器sendOthers当中进行信息的筛选,内容中有@的和没有的分别用if
else,有@时将数据解析,得到被@的用户名,和传输的内容,然后单独调用send方法进行数据返回

这里面要注意的是用户名和字符串比较混乱,进入服务器内容进入,进入sendOthers是sendOthers(name+":"+msg);所以在实现5.0版本时将上面4.0也做了一点小改动,将用户名单独拎出来,作为Channel的属性,方便私聊的有关实现。(上面4.0的代码已经改了)
所以私聊的时候进入send的字符串是 本客户端用户名+":"+@+接收人名字+?+内容
在sendOthers中筛选时,就要筛选出接收者名字 和 内容,将接收者名字一一与容器中的客户端比较,名字相同时发出,用接收者的send方法,
other.send(this.name+":"+msg);反馈给接收者,这样就是单项发送。

代码只改变server

public class server {
     static CopyOnWriteArrayList<Channel> all=new CopyOnWriteArrayList<>();
    public static void main(String[] args) throws IOException {
        //服务器应该是可以和多个客户端进行连接的
        ServerSocket serverSocket = new ServerSocket(8888);
        while(true){
            Socket client = serverSocket.accept();
            Channel c=new Channel(client);
            all.add(c);//管理所有成员
            new Thread(c).start();
        }
    }
}


//一个客户代表一个channel
class Channel implements Runnable{
    private DataInputStream dis;
    private DataOutputStream dos;
    private Socket client;
    private boolean isRunning=true;
    private String name;

    public Channel( Socket client) {
        this.client = client;
        try {
            dis = new DataInputStream(client.getInputStream());
            dos= new DataOutputStream(client.getOutputStream());
        } catch (IOException e) {
            release();
        }
    }

    //接收消息
    private  String receive()  {
        String msg="";
        try {
            msg=dis.readUTF();
        } catch (IOException e) {
            release();
        }
        return msg;

    }
    //发送消息  获取自己的消息发给其他人
    private void send(String msg){
        try {
            dos.writeUTF(msg);
        } catch (IOException e) {
            release();
        }
    }

    private void sendOthers(String msg){
        System.out.println(msg);
        boolean isPrivate=msg.contains("@");
        if(isPrivate){
            int temp1=msg.indexOf("@");
            System.out.println(temp1);
            int temp2=msg.indexOf("?");
            System.out.println(temp2);
            String targetName=msg.substring(temp1+1,temp2);
            System.out.println(targetName);
            msg=msg.substring(temp2+1);
            for (Channel other:all
                 ) {
                if(other.name.equals(targetName)){
                    other.send(this.name+":"+msg);
                }
            }
        }else{
            for (Channel other:all
            ) {
                if(other==this){
                    continue;
                }else{
                    other.send(msg);
                }
            }
        }


    }


    //释放资源
    private void release(){
        this.isRunning=false;
        Utils.close(dis,dos,client);
    }

    @Override
    public void run() {
        this.name=receive();
        this.send("你进入群聊");
        sendOthers(name+"进入群聊");

        while(isRunning){
            String msg=receive();
            if(!msg.equals("")){
                sendOthers(name+":"+msg);
            }
        }
    }
}


你可能感兴趣的:(javaSE)