【Java学习笔记】58:多线程Socket通信的demo

多线程Socket通信

对于服务端来说,可能有多个客户端连接进来,从平衡上考虑就应当为每个客户端的连接单独开启一个线程,并且在main进程中继续用accept()等待其它用户连接

对于客户端来说,虽然不涉及多个服务端问题,但是数据输入流的readXxx()方法会引起阻塞,所以可以单独放到子线程中,不影响主线程的执行。

书上的例子,改了改,这个例子有挺多细节值得考虑的。

服务端ThreadServer.java
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class ThreadServer {

    public static void main(String[] args) {
        ServerSocket ss;
        Socket sck;
        while (true) {
            try {
                ss = new ServerSocket(3838);
                System.out.println("等待客户端连接...");
                sck = ss.accept();// 阻塞以等待连接
                System.out.println("[+]客户端地址:" + sck.getInetAddress());
                if (sck != null) {
                    // 每个客户启动一个专门的线程
                    new MyThread(sck).start();
                }
            } catch (IOException e) {
                // 不能重复创建ServerSocket
            }
        }
    }

}

// 线程类,每个客户单独一个线程,继承Thread类方便一些
class MyThread extends Thread {
    Socket sck;// 服务器端Socket
    DataOutputStream dos;// 数据输出流
    DataInputStream dis;// 数据输入流
    String s;

    // 构造器中获取这些成员
    public MyThread(Socket sck) {
        this.sck = sck;
        try {
            dos = new DataOutputStream(this.sck.getOutputStream());
            dis = new DataInputStream(this.sck.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            double r;
            while (true) {
                try {
                    r = dis.readDouble();// 从输入流中读取信息,即客户端发送来的半径
                    dos.writeDouble(Math.PI * r * r);// 从输出流发送给客户端
                } catch (IOException e) {
                    System.out.println("[-]客户端" + sck.getInetAddress() + "断开连接");
                    return;// 结束这个线程
                }
            }
        }
    }

}
客户端ThreadClient.java
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class ThreadClient {

    public static void main(String[] args) {
        Socket sck;
        DataInputStream dis;// 输入流
        DataOutputStream dos = null;// 输出流
        Thread thd;// 线程对象
        MyThread mythd;// 目标对象
        InetAddress ia;// 地址对象
        InetSocketAddress isa;// 套接字地址对象,用来为Socket建立连接

        try {
            sck = new Socket();// 无参构造的Socket对象,在后面再建立连接
            mythd = new MyThread();// 目标对象
            thd = new Thread(mythd);// 建立线程对象
            ia = InetAddress.getByName("127.0.0.1");// 对这个地址对象实例化
            isa = new InetSocketAddress(ia, 3838);// 套接字地址对象,多提供一个端口
            sck.connect(isa);// 为Socket对象建立连接
            dis = new DataInputStream(sck.getInputStream());// 输入流
            dos = new DataOutputStream(sck.getOutputStream());// 输出流
            mythd.setDataInputStream(dis);// 将输入流对象传入给子线程目标对象
            // 即便线程对象的构造方法在这前面,也会影响到线程对象
            thd.start();// 启动子线程去做输入流操作,因为输入流可能阻塞
        } catch (UnknownHostException e) {
            System.out.println("[x]无法解析地址");
            System.exit(0);
        } catch (IOException e) {
            System.out.println("[x]无法连接到服务端");
            System.exit(0);
        }

        // 子线程阻塞与否不影响主线程,主线程中做输出流操作
        System.out.println(">输入圆的半径:");
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext())// main线程的阻塞之处
        {
            double r;
            try {
                r = sc.nextDouble();// 读入圆的半径
                dos.writeDouble(r);// 从输出流发送给服务端
            } catch (Exception e) {
                System.out.println("[x]输入错误,程序结束");
                System.exit(0);
            }
        }

    }

}

// 线程目标对象
class MyThread implements Runnable {
    DataInputStream dis;// 输入流

    // 将输入流对象的引用给子线程,在子线程里做输入,防止阻塞main线程
    public void setDataInputStream(DataInputStream dis) {
        this.dis = dis;
    }

    @Override
    public void run() {
        double result;
        while (true) {
            try {
                // 从输入流中读取计算结果,即服务器的输出流
                result = this.dis.readDouble();// 子线程的阻塞之处
                System.out.println("[+]服务器计算得:" + result);
                // 放在这里而不是主线程中,才能保证提示输入在计算结果之后
                System.out.println(">输入圆的半径:");
            } catch (IOException e) {
                System.out.println("[-]与服务器断开");
                System.exit(0);
            }
        }
    }

}

输出(正常)

>输入圆的半径:
2.3
[+]服务器计算得:16.619025137490002
>输入圆的半径:
3.1
[+]服务器计算得:30.190705400997913
>输入圆的半径:
bye
[x]输入错误,程序结束
等待客户端连接...
[+]客户端地址:/127.0.0.1
等待客户端连接...
[-]客户端/127.0.0.1断开连接

输出(客户端直接退出)

>输入圆的半径:
2
[+]服务器计算得:12.566370614359172
>输入圆的半径:
等待客户端连接...
[+]客户端地址:/127.0.0.1
等待客户端连接...
[-]客户端/127.0.0.1断开连接

输出(服务端宕机)

>输入圆的半径:
1.1
[+]服务器计算得:3.8013271108436504
>输入圆的半径:
[-]与服务器断开
等待客户端连接...
[+]客户端地址:/127.0.0.1
等待客户端连接...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值