传统的网络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。