栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

2021-09-28 Socket聊天室模拟服务器讲解:

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

2021-09-28 Socket聊天室模拟服务器讲解:

Socket实现攻略: 一、创建两个类  1、用户使用的,名为:Client ①定义私有成员属性:Socket socket;

java.net.Socket:套接字
Socket封装了TCP协议的通讯细节,使得我们使用它可以与服务端建立网络连接,并通过它获取两个流(一个输入流,一个输出流),然后使用这两个流的读写操作完成与服务端的数据交互。

②定义无参构造函数:

在构造函数中实例化对象,socket = new Socket(参数1,参数2);
实例化Socket时通常需要传入两个参数:
参数1:服务端的地址信息(IP地址,如果连接本机可以用localhost)。
参数2:服务端打开的服务端口,即服务端ServerSocket申请的端口。

    private Socket socket;
    public Client(){
        try {
            System.out.println("正在连接服务器……");
            socket = new Socket("192.168.43.215",2222);
            System.out.println("服务器连接成功!"+socket.getInetAddress());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 2、服务器的,名为:Server ①定义私有成员属性:ServerSocket serverSocket

运行在服务器端的ServerSocket主要有两个作用:
    1、向系统申请服务端口,客户端的Socket就是通过这个端口与服务器建立连接的。
    2、监听服务端口,一旦一个客户通过该端口建立连接会自动创建一个Socket,服务端就可以通过这个Socket与客户端交互了。如果我们把Socket比喻为电话,那么ServerSocket相当于是某客服中心的总机。

②定义一个集合,用来存储可能开启的很多个客户端。

private List c = Collections.synchronizedList(new ArrayList<>());创建一个线程安全的集合,其中add,remove等方法上都直接使用了synchronized修饰, 锁个线程是不能同事调用一个集合的这些方法的,保证同步执行。

③ 定义无参构造函数:

在构造函数中实例化对象serverSocket = new ServerSocket(222);设置端口号。
实例化时需要指定服务器端口,如果该端口被当前系统其他应用程序占据时,会抛出异常。

 private ServerSocket serverSocket;
private List c = Collections.synchronizedList(new ArrayList<>());
    public Server() {
        try {
            System.out.println("正在启动服务端……");
            serverSocket = new ServerSocket(222);
            System.out.println("服务端连接成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

二、服务器端需要做的事:

服务器端需要:接收客户端发送的信息,和将汇总的信息发回给客户端,实现通信功能。

1、实现客户端与服务器的互通:accept ① 不确定客户端数量,所以可以用死循环,保证一直能有新用户连接 ② 客户端与服务器连接并创建启动线程,使用ServerSocket提供的方法: [Socket] accept():

这是一个阻塞方法,调用后程序进入阻塞状态,直到一个客户端实例化Socket与当前服务端建立连接,此时accept方法会立即返回一个Socket实例,服务端通过它就可以与客户端交互了。可以理解为这个动作相当于是总机的“接电话”操作。
注意:设置线程这里也要try-catch,否则会报错

      int i = 1;
            while (true) {
                System.out.println("等待客户端连接……");
                Socket socket = serverSocket.accept();
                System.out.println("第" + i + "个客户端连接成功!" + socket.getInetAddress());
                i++;
                //启动一个线程用来与客户端交互:
                //1、创建线程任务:
                ClientHandler handler = new ClientHandler(socket);
                //2、创建一个线程执行该任务
                Thread t = new Thread(handler);
                //3、启动线程
                t.start();
            }
2、定义一个属于类发送信息的方法,将服务器收到的信息发给客户端
    public void sendMessage(String message){
        c.forEach(pw -> pw.println(message));
    }
3、创建一个线程任务类ClientHandler implements Runnable,用于与指定的客户端进行交互。

每个连接服务器的客户端都是通过线程进行交互的。 即:一个客户端靠一个线程进行交互

① 私有化内部类的属性:private Socket socket ② 有参的构造方法:传递一个socket给当前赋值
private class ClientHandler implements Runnable {
        private Socket socket;
        private String address;//处理当前线程处理的客户端地址
        public ClientHandler(Socket socket) {
            this.socket = socket;
            //通过socket获取远端计算机地址信息(对于服务器而言,远端就是客户端)
            this.address = socket.getInetAddress().getHostAddress();
        }
④ 定义线程任务,重写run: Socket提供的方法:[InputStream] getInputStream():

通过该方法获取的字节输入流取的时远端计算机发送过来的数据。这里相当于是读取当前服务端中这个Socket对应的远端(客户端)那边Socket获取的输出流写出的字节数据。

⑤ 使用流完成信息的写入写出:
        @Override
        public void run() {
            PrintWriter pw =null;
            try {
                pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8")), true);
                c.add(pw);
                System.out.println(address+"上线了,当前在线人数:"+c.size());
                //广播上线通知:
                sendMessage(address+"上线了,当前在线人数:"+c.size());
                BufferedReader bw = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
                String line;
                while ((line = bw.readLine()) != null) {
                    System.out.println(address + "说:" + line);
                    //将消息回复给所有客户端
                    sendMessage(address + "说:" + line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //下线时:移除对象并且广播
                try {
                    c.remove(pw);
                    sendMessage(address+"下线了,当前在线人数:"+c.size());
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
三、客户端要做的事: 1、定义一个start方法,接收用户从控制台输入的信息 ① 使用流连接:Socket提供的方法:[OutputStream] getOutputStream()

该方法会获取一个字节输出流,通过这个输出流写出的字节数据会通过网络发送给对方。设定结束词语。

public void start(){
    try(OutputStream out = socket.getOutputStream();
    PrintWriter pw =new PrintWriter(new BufferedWriter(new OutputStreamWriter(out,"UTF-8")),true);) {
         Scanner sc = new Scanner(System.in);
         System.out.println("开始聊天吧!单独输入88|拜拜则退出聊天室~");
         while (true){
             String line = sc.next();
             if(line.equals("88")|line.equals("拜拜")){
                 System.out.println("本次聊天已结束,感谢使用聊天室~");
                 break;
              }
              pw.println(line);
          }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
2、定义线程任务ServerHandler implements Runnable ①设置run方法,Socket提供的方法:[InputStream] getInputStream():

通过该方法获取的字节输入流取的时远端计算机发送过来的数据。 这里相当于是读取当前服务端中这个Socket对应的远端(客户端)那边Socket获取的输出流写出的字节数据。

设置字节输出流,读取其他客户端或者自己发送给服务器后,被服务器返回的数据,如果只是使用输入流,则看不到别人的信息,功能不健全。输入自己说的,读出大家说的,这样可以实现互通。

    private class ServerHandler implements Runnable{
        @Override
        public void run() {
            try (
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
            ){
                String message;
                while ((message= br.readLine())!=null){
                    System.out.println(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/274727.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号