畅所欲言

一、前言

        微信、QQ早已经融入我们生活的点点滴滴。在学习了Java后,内心不禁想学习做一个简易聊天室,哪怕只是在命令行运行,只要实现聊天功能即可。心动不如行动 :)

 

二、运行环境

我用的系统是win10系统,所用的开发工具是IDEA,创建的是一个maven项目

 

三、大概框架及思路

        说到QQ聊天,首先应该想到它是一款C/S架构的APP,而要实现聊天这个功能,最基础应该想到它是基于Socket编程,客户端与用户端连接。

        知道聊天室是基于Socket来实现的后,首先考虑客户端与服务端分别负责的功能。客户端用于发送数据,而服务端用于与客户建立连接、接收数据、发送数据。

 

四、建立一个基础的单线程版聊天室

1.对于服务端Socket来说,它的工作流程应该是:(想象你打电话,首先电话要有信号,即要有基站来接收信号)

a.建立服务器端Socket,等待客户连接    ------建立基站

       ServerSocket server = new ServerSockrt(port)

b.等待客户端的连接    ---基站建好后要有客户端来使用,则必须先与其进行连接,否则服务端一直等待。。。

        accpet()

c.取得输入输出流   ---服务端与客户端连接成功,客户端想要收发消息服务端必须要取得输入输出流

        getInputStream()  、  getOutputStream()

d.关闭流   ---将基站关闭   

        server.close()

PS: 建立服务端,有几个重要的方法要注意:

public ServerSocket(int port) :在本机根据指定端口号创建服务器

public Socket accept() : 侦听并接收连接到本服务器的客户端连接,此方法会一直阻塞,直到有一个客户端成功连接返回此连接

package com.lmd.Single;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class SingleServer {
    public static void main(String[] args) throws IOException {
        ServerSocket server = null;
        PrintStream printStream = null;
        Scanner scanner = null;
        try {
            //1.建立基站
            server = new ServerSocket(8888);
            System.out.println("等待客户连接。。。");
            //2.等待客户连接
            Socket client = server.accept();
            System.out.println("终于等到你还好我没放弃~,你的端口号为:"+client.getPort());
            //3.取得输入输出流
            printStream = new PrintStream(client.getOutputStream(),true,"UTF-8");
            printStream.println("风里雨里一直等你,hello我是服务端");
            scanner =  new Scanner(client.getInputStream());
            if(scanner.hasNext()){
                System.out.println("客户端发来的消息~:"+scanner.nextLine());
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            printStream.close();
            scanner.close();
            server.close();
        }

    }

}

2.对于客户端Socket来说,它的工作流程应该是:(想象你打电话,首先要有信号,即你要与基站建立连接,否则不能使用)

a.连接到指定服务器

    Socket socket = new Socket(ip,port);

b.取得输入输出流

     getInputStream()  、getOutputStream()

c.关闭流

     socket.close()

PS: 建立客户端,有几个重要的方法要注意:

public Socket(String host, int port) :根据指定IP和端口号创建套接字并连接到远程服务器端

public InputStream getInputStream():返回此套接字的输入流

public OutputStream getOutputStream():返回此套接字的输入流

package com.lmd.Single;

import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class SingleClient {
    public static void main(String[] args) throws IOException {
        Socket client = null;
        PrintStream sendmsgtoServer = null;
        Scanner getServerMsg = null;
        try {
            //1.连接到指定服务器
            client = new Socket("127.0.0.1",8888);
            System.out.println("连接服务器ing。。。");
            //2.获得输入输出流
            getServerMsg = new Scanner(client.getInputStream());
            sendmsgtoServer = new PrintStream(client.getOutputStream());
            sendmsgtoServer.println("hello i am Client");
            if(getServerMsg.hasNext()){
                System.out.println("服务端发来的消息为:"+getServerMsg.nextLine());
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //3.关闭流
            client.close();
            getServerMsg.close();
            sendmsgtoServer.close();
        }
    }
}

3.运行程序

注意:1. 一定要先启动服务器端然后启动客户端,如果相反,则会一直运行不成功,服务端接收不到客户端                                             2. 客户端与服务端不能都先收消息再发或者先发再收,类比打电话占线

结果:

        畅所欲言_第1张图片                                                              畅所欲言_第2张图片

 

五、对单线程总结改进

1.总结

       单线程版本的聊天室,现在已经实现了客户端与服务器端的连接,可以成功的进行收发消息,对连接成功与否可以进行有效的判断。但是,QQ聊天不是只有一个人聊天,需要有多个用户同时在线进行聊天,而且不会发送一次消息就再不能发送了。

单线程通信的问题:

    a.造成双方卡死(类比打电话占线)

    b.发送一次数据后,服务器与客户端均退出,不能持久通信

    c.不能同时进行数据的读取与写入(顺序操作)

    d.服务器只能处理一个客户端的连接

2.改进

不能同时进行数据的读取与写入 --> 读写分离,两个不相关的线程读写分离,读作为一个线程,写作为一个线程。二者同时进行

服务器只能处理一个客户端的连接  --> 多线程接收多个客户端的链接请求,使用Map存储所有连接的客户端信息

 

六、多线程版聊天室

1.操作规定:

         操作                   规定        举例
    用户注册         以 ‘userName:’ 开头 userName: lisi   
       私聊     以‘P:’开头,后加用户名-消息     P: lisi - hi
       群聊     以‘G:’开头,后跟要发送的信息     G:hello   
    用户退出     内容包含‘byebye’    byebye   
其他输入消息     除上述关键字以外的消息     g:hi

2.客户端:

为了更好的实现聊天功能,使用读写消息线程分离来实现

//读线程:读取服务器发来信息
class ReadFromServer implements Runnable{
   private Socket client;
    public ReadFromServer(Socket client) {
        this.client = client;
    }
    @Override
    public void run() {
        Scanner readFromSer = null;
        try {
            while (true) {
                readFromSer = new Scanner(client.getInputStream());
                readFromSer.useDelimiter("\n");
                if (readFromSer.hasNext()) {
                    System.out.println(readFromSer.nextLine());
                }
                if(client.isClosed()){
                    System.out.println("客户端关闭");
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            readFromSer.close();
        }

    }
}


//写线程:写信息,将信息传输给服务器
class WriteMagToServer implements Runnable{
    private Socket client;
    public WriteMagToServer(Socket client) {
        this.client = client;
    }

    @Override
    public void run() {
        PrintStream writeMsg = null;
        Scanner in = null;
        try {
            while (true) {
                writeMsg = new PrintStream(client.getOutputStream());
                System.out.println("请输入信息:");
                in = new Scanner(System.in);
                in.useDelimiter("\n");
                if (in.hasNextLine()){
                    writeMsg.println(in.nextLine());
                    if(in.nextLine().equals("byebye")){
                        System.out.println("客户端退出");
                        in.close();
                        writeMsg.close();
                        client.close();
                        break;
                    }
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.服务端:

要能接收多个客户端的链接请求 --> 多线程

如何保存多个客户端的连接 --> Map

public class MultiServer {
    static Map map = new HashMap<>();
    static class ExecuteClient implements Runnable{
        private Socket client;
        public ExecuteClient(Socket client) {
            this.client = client;
        }
        @Override
        public void run() {
            Scanner msgFromClient = null;
            try {
                msgFromClient = new Scanner(client.getInputStream());
                while (true){
                    if(msgFromClient.hasNextLine()){
                        String msg = msgFromClient.nextLine();
                        // windows下将默认换行/r/n中的/r替换为空字符串
                        Pattern pattern = Pattern.compile("\r");
                        Matcher matcher = pattern.matcher(msg);
                        msg = matcher.replaceAll("");
                        if(msg.startsWith("userName:")){
                            //注册
                            continue;
                        }
                        if(msg.startsWith("G:")){
                            //群聊
                            continue;
                        }
                        if(msg.startsWith("P:")){
                            //私聊
                            continue;
                        }
                        if(msg.contains("byebye")){
                            //退出
                           continue;
                        }

                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        ExecutorService execute = Executors.newFixedThreadPool(20);
        try {
            ServerSocket server = new ServerSocket(8888);
            while (true){
                System.out.println("等待客户端连接。。。");
                Socket client = server.accept();
                System.out.println("有新的客户端连接,端口号为:"+client.getPort());
                execute.submit(new ExecuteClient(client));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            execute.shutdown();
        }

    }
}

完整代码:https://github.com/lmdlll/helloworld/tree/master/chat_space

 

七、涉及技术

1.Java接口、类、多线程、I/O操作

2.Socket编程

 

八、测试

对代码编写完成后,首先应该对其进行测试

    I)边界值分析

a.最大消息长度:无限制

b.最短消息长度:1

c.最多在线人数:19

d.最多群聊人数:19

    II)划分等价类

  • 有效等价类:
        消息类型             内容            测试结果
       用户注册      userName:1          注册成功
     userName:2
      userName:3
       群聊消息       G:hello           群发成功
       私聊消息 1发送消息:    P:3-hi           私聊成功
2发送消息:    P:1-wa
        退出 1发送消息:    byebye          退出成功
  • 无效等价类:

a. 发送空消息,服务器不做任何处理

b. 私聊对象不存在,服务器不做任何处理

c. 发送消息中不包含关键字,服务器不做任何处理

    III)异常点测试

a. 连接突然断开:客户端中断无法使用

b.客户端非正常退出:服务端正常工作

c.服务端异常关闭:所有客户端都随之关闭

 

九、待续

上述只是一个最基础的聊天室,可以根据我的代码继续进行一些扩展。

扩展点:

   1.使用I/O对文件进行传输与接收

   2.使用数据库进行用户的信息保存

   3.使用多线程建多个聊天群组

   4.用户注册登录后显示好友及群组

   5.不同局域网用户的划分

   6.已注册用户再次登录时的历史记录

   7.信息传输大小的限制

   8.对于错误命令的判断与提示

   9.创建一个聊天界面而非在命令行进行输入输出

 

 

 

 

 

你可能感兴趣的:(JavaSE,JavaSE)