BIO阻塞的Socket实现一个服务器与多个客户端广播通信

传统的网络IO-BIO
案例场景:多个client与server通信,server接收到一个client的消息后广播给所有与server建立socket连接的客户端。简单群聊场景。
服务器端:ServerSocket类,accpet()方法默认是阻塞监听的。所以每当监听到一个客户端的socket连接请求,都开启一个新线程,新建一个socket进行一对一的与客户端通信。
客户端:主动发起Socket连接请求.

  • 服务器端代码
package BIOChatDemo1;

/*
* 服务器端接收所有客户端的连接请求,并将从一个客户端接收到的消息转发给所有连接的客户端,
* 服务器需要一个主线程:专门用来监听客户端的连接请求,
* 每当监听到一个连接就开启一个单独的线程,与该客户端建立一对一的socket连接,进行通信
* 服务器将所有的客户端连接请求加入list,如果客户端socket中断了,就从list中移除
* */

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
//主线程,只负责阻塞监听接收每个客户端的socket连接
public class BIOServer {

    public static void main(String[] args) {
        try{
            //服务器端的socket是ServerSocket,默认是阻塞的socket
            ServerSocket serverSocket = new ServerSocket(30000);
            System.out.println("等待客户端连接...");
            while (true){
                Socket socket = serverSocket.accept(); //等待客户端的连接
                //上一句执行完,表示有客户端的连接
                //开启新线程建立一对一的socket
                new ServerThread(socket).start();
            }
        }catch(IOException e){
            e.getStackTrace();
        }
    }
}
package BIOChatDemo1;

//服务器单独开启一个线程,与客户端建立一对一的IO操作
//接收到客户端的数据后,广播给所有list中的客户端

import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ServerThread extends Thread {
    private Socket socket;    //表示为每一个来连接的客户端建立唯一对应的socket
    //用来保存所有来连接的客户端的socket,包装成线程安全的
    private static List<Socket> socketList = Collections.synchronizedList(new ArrayList<>());
    public ServerThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        System.out.println("线程"+Thread.currentThread().getName()+"已与客户端建立连接,准备通信...");
        //将该客户端的socket加入到list中
        socketList.add(socket);
        //从socket中读数据
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));  //包装流
            String data;
            while ((data = bufferedReader.readLine()) != null){
                System.out.println("从客户端读取:"+ data);
                //并将数据发送给所有在list中有连接的客户端
                sendToClients(data);
            }
        }catch (IOException e){
            //当与客户端唯一对应的socket抛出异常,说明该客户端连接异常,则从list中将该客户端的socket移除
            socketList.remove(socket);
            System.out.println("客户端"+socket.getInetAddress()+"离线了...");
            //广播给所有客户端
            sendToClients("客户端"+socket.getInetAddress()+"离线了...");
            e.getStackTrace();
        }
    }

    private void sendToClients(String data){
        //遍历list中每个socket
        for(Socket socket : socketList){
            try {
                //将数据通过输出流,写入每一个客户端的socket
                PrintStream printStream = new PrintStream(socket.getOutputStream());
                printStream.println(data);
                System.out.println("服务器已向客户端"+socket.getInetAddress()+":"+socket.getLocalPort()+"发送数据:"+data);
            }catch (IOException e){
                e.getStackTrace();
            }
        }
    }
}
  • 客户端代码
package BIOChatDemo1;

import java.io.*;
import java.net.Socket;

//主动发起连接请求
//主线程,负责向服务器发数据
//还需要一个线程用来接收从服务器读取的数据
public class BIOClient {
    private static String serverAddress = "127.0.0.1";
    private static int serverPort = 30000;
    public static void main(String[] args) {
        try {
            //客户端的socket主动连接服务器,服务器的ip是127.0.0.1,服务器监听的端口是30000
            Socket socket = new Socket(serverAddress,serverPort);
            //与服务器建立连接后,开启新线程,用来接收服务器数据
            new Thread(new ClientThread(socket)).start();
            //将数据写入socket
            //从键盘获取输入流
            BufferedReader printReader = new BufferedReader(new InputStreamReader(System.in));
            String data;
            while ((data = printReader.readLine()) != null){
                PrintStream printStream = new PrintStream(socket.getOutputStream()); //通过输出流写入socket
                printStream.println(data);
            }

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

    }
}
package BIOChatDemo1;

import java.io.*;
import java.net.Socket;

//接收从服务器读到的数据,输出到控制台
public class ClientThread implements Runnable {

    private Socket socket;
    public ClientThread(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try{
            //从socket中读取数据,通过输入流输出到控制台
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));  //包装流
            String data ;
            while ((data = bufferedReader.readLine()) != null){
                System.out.println("从服务器读取:"+ data);
            }

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

    }
}

  • 运行结果
    先开启服务器,主线程阻塞监听等待客户端的连接
    在这里插入图片描述
    再开启客户端。启动了一个客户端,服务器开启了一个新线程Thread-0建立与该客户端的一对一socket通信。
    在这里插入图片描述
    在这里插入图片描述
    这个客户端向服务器发送数据:我是客户端1,服务器接收到数据,又群发给socketList中的每个与服务器连接的客户端,此时socketList中只有一个客户端。
    在这里插入图片描述
    再开启多个客户端与服务器连接,此时一共有3个客户端连接服务器,服务器创建3个线程,分别建立与客户端的唯一socket连接。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    假设此时一个客户端掉线,将该客户端的socket从服务器的socketList中移除,并广播给所有客户端。
    在这里插入图片描述
    在这里插入图片描述
    由于本机测试,服务器与客户端IP都是本机IP。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值