IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Java Socket编程(二)从单线程到多线程,实现聊天小程序 -> 正文阅读

[移动开发]Java Socket编程(二)从单线程到多线程,实现聊天小程序

单线程示例

实现功能

服务端与客户端1对1聊天通信,需要先运行服务器端,后运行客户端,用户输入bye时,关闭socket,终止聊天。

具体运行过程

  1. 启动服务器端,创建ServerSocket,并阻塞于accept方法,等待客户端连接。
  2. 启动客户端,连接服务器。
  3. 服务器端接受连接,得到ServerSocket.accept( )返回的socket对象。
  4. 服务器端和客户端分别创建各自监听消息的线程,之后可以通过socket的输入输出流进行信息交互。
  5. 当某一方输入bye时,socket关闭,断开连接
  6. 关闭相关资源,通信结束

代码

服务器端

//ServerDemo.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * @author Pure_vv
 * @date 2022/1/29
 */
public class ServerDemo {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(SocketUtils.PORT);
            Socket socket = serverSocket.accept();

            //开启接收消息线程
            new ReceiveMsg(socket).start();

            //主线程开始发送消息
            SocketUtils.sendMsg(socket, socket.getOutputStream());
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端

//ClientDemo.java
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
/**
 * @author Pure_vv
 * @date 2022/1/29
 */
public class ClientDemo {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket(InetAddress.getLocalHost(),SocketUtils.PORT);
            //接收消息线程开启
            new ReceiveMsg(socket).start();

            //主线程开始发送消息
            SocketUtils.sendMsg(socket, socket.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

封装的工具类

SocketUtils.java :封装了发送用户控制台输入消息的方法,以及定义了端口常量。

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
 * @author Pure_vv
 * @date 2022/2/4
 */
public class SocketUtils {
    public static final int PORT = 8888;
    public static void sendMsg(Socket socket, OutputStream os){
        PrintWriter pw = new PrintWriter(os);
        Scanner sc = new Scanner(System.in);
        String msg;
        while (!socket.isClosed() && (msg = sc.next())!=null){
            // BufferedReader.readLine()方法直到遇到\n或者\r才会终止读取,是阻塞式方法
            //手动封装消息,增加\n
            pw.write(msg+'\n');
            pw.flush();
            if ("bye".equals(msg)){
                break;
            }
        }
        System.out.println("消息传输完毕,程序结束");
        try {
            os.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ReceiveMsg.java:创建新线程监听信息,并打印信息到控制台

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.SocketException;
/**
 * @author Pure_vv
 * @date 2022/1/29
 */
public class ReceiveMsg extends Thread {
    InputStream inputStream;
    Socket socket;
    public ReceiveMsg(Socket socket) {
        this.socket = socket;
        try {
            this.inputStream = socket.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
        System.out.println("线程被启动");
        //字节流转字符流
        InputStreamReader isr = new InputStreamReader(inputStream);
        //添加缓冲读数据
        BufferedReader br = new BufferedReader(isr);
        try {
            String msg;
            //当收到消息时把消息打印到控制台
            while ((msg=br.readLine())!=null) {
                System.out.println("msg:"+msg);
                if ("bye".equals(msg)){
                    System.out.println("接收消息socket关闭");
                    socket.close();
                    break;
                }
            }
        } catch (IOException e) {
            if (!(e instanceof SocketException)){
                e.printStackTrace();
            }
        }
    }
}

多线程示例

使server可以支持多个client连接并聊天

只需要增加一个线程类,稍微改动一下ServerDemo.java

//ServerDemo.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * @author Pure_vv
 * @date 2022/1/29
 */
public class ServerDemo {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(SocketUtils.PORT);
            while (true){
                Socket socket = serverSocket.accept();
                // 开启新线程 循环监听连接请求
                new ServerThread(socket).start();
            }
//            new ReceiveMsg(socket).start();
//            SocketUtils.sendMsg(socket, socket.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

增加的线程类ServerThread.java

import java.io.IOException;
import java.net.Socket;
/**
 * @author Pure_vv
 * @date 2022/2/5
 */
public class ServerThread extends Thread{
    Socket socket;
    public ServerThread(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        super.run();
        //开启接收消息线程
        new ReceiveMsg(socket).start();
        //主线程开始发送消息
        try {
            SocketUtils.sendMsg(socket, socket.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

思考

不太明白的地方:

  • 使用PrintWriter传输消息(例如从server向client传输消息),client的socket关闭之后,server发消息也不会抛出异常,不求甚解…

  • 解决方法:server接收到bye后关闭socket,并在发送消息判断socket.isClosed()

但同时这又会带来一个问题,单线程进行聊天时,可以注意发送消息的这一段代码

while (!socket.isClosed() && (msg = sc.next())!=null){
	pw.write(msg+'\n');
	pw.flush();
	if ("bye".equals(msg)){
	    break;
	}
}

当server启动之后,会阻塞于accept方法,client启动后,双方的代码都会阻塞在sc.next( )socket.isClosed() 返回的是false。这时,任意一方输入bye关闭socket,另外一方还需要输入一行才能彻底结束。

  • 可能可行的解决方法:通过注册–通知机制实现事件监听(观察者模式),达到实时监听socket是否关闭,还有待实现和验证…

此外,还存在一个问题,当使用多线程时,会造成聊天混乱的情况,两个连接client的线程并发运行,会导致server需要交替输入。

参考资料:

Scanner 类抛出java.util.NoSuchElementException

java 线程监听事件_java中的事件监听是怎样实现随时监听的,是通过线程吗

java-用java.net.Socket和java.net.ServerSocket实现简单的聊天程序

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-02-06 13:56:30  更:2022-02-06 13:58:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 15:00:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码