UDP通信
聊天室V4.
服务器端可以将用户的信息转发给所有的客户端(广播),并在每个客户端控制台上显示.
1.对某个客户端发送的信息进行广播(转发给所有的客户),并且使的客户在接收到服务器端
转发的信息后输出到控制台
解决步骤:
1.需要在服务器定义一个集合类型的属性,用于存储所有客户端的输出流.
2.在Server类中的run方法最开始处,将客户端的输出流存入该集合,之后每当客户端发送信
息后就遍历集合,将信息写入集合中所有的输出流中(相当于将信息转发给所有的客户端)
3.在客户端使用多线程机制读取服务端发送过来的信息
3.1在客户端定义内部类ServerHandler实现Runnable接口
在run方法实现该线程要执行的任务,循环接收服务器端的信息并打印在控制台上
3.2修改start方法,在该方法中创建线程
聊天室V5.
目前存在的隐患:
1.随着用户的增多,服务端会频繁的和销毁线程,会很多程度上对系统资源造成浪费
2.过多的线程导致的过度切换也会为服务端带来崩溃的风险
3.多个线程会共享服务端的集合属性allout,这里还存在着多线程的并发安全问题
问题:
需要对之前程序进行优化,使程序更加健壮
解决方案:
需要使用线程池来控制客户端连接后启动和管理线程.并解决多线程共享Server属性allout
所引起的并发安全性问题
步骤一:
在server类中创建线程池属性,使用线程池管理服务端线程的创建.
步骤二:
修改server中的start方法,将原来创建并启动线程的代码替换为使用线程池管理的方式
步骤三:
在server中添加三个方法,用于操作属性allout,并使用同步锁,使三个方法变为同步的.
步骤四:
将原来操作向集合中添加,删除,遍历集合改为调用三个方法,以确保同步安全
//客户端应用程序
//第一步:实现向服务器发送一条信息
public class Client {
// 创建Socket实例
private Socket socket;
public Client() {
try {
socket = new Socket("localhost",8060);
} catch (Exception e) {
e.printStackTrace();
}
}
//客户端工作方法
public void start() {
try {
// 接收服务器端的信息并且启动线程
ServerHandler handler = new ServerHandler();
Thread r = new Thread(handler);
r.start();
// 获取OutputStream对象
OutputStream os = socket.getOutputStream();
// 缓冲字符输出流
OutputStreamWriter osw = new OutputStreamWriter(os,"UTF-8");
PrintWriter pw = new PrintWriter(osw,true);
// 创建Scanner读取用户输入的内容
Scanner scanner = new Scanner(System.in);
while(true) {
pw.println(scanner.nextLine());
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(socket!=null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Client c = new Client();
c.start();
}
private class ServerHandler implements Runnable{
@Override
public void run() {
// 接收服务端发送的信息
InputStream in;
try {
in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in,"UTF-8");
BufferedReader br = new BufferedReader(isr);
while(true) {
System.out.println(br.readLine());
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class Server {
private ServerSocket serverSocket;
// 存储所有客户端的输出流
private List<PrintWriter> allout;
// 线程池
private ExecutorService threadPool;
Server(){
try {
serverSocket = new ServerSocket(8060);
allout = new ArrayList<PrintWriter>();
threadPool = Executors.newFixedThreadPool(40);
} catch (Exception e) {
e.printStackTrace();
}
}
// 创建内部类,继承Runnable的形式创建线程
private class ClientHandler implements Runnable{
private Socket socket;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
// 获取向该客户端发送信息的输出流,并且将该流包装PrintWriter之后
// 放入集合中.如果客户掉线,集合中对应的输出流也要从集合中删除
PrintWriter pw = null;
try {
OutputStream os = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os,"UTF-8");
pw = new PrintWriter(osw,true);
// 将用户信息放入集合中
allout(pw);
// 在获取到了客户端发送过来的信息后,遍历集合allout,将
// 获取到的信息写入该集合中每一个输出流中,从而将信息发送给所有的客户端
InputStream in;
in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in,"UTF-8");
BufferedReader br = new BufferedReader(isr);
String message = null;
// readLine只有在数据流发生异常,或者另一端被close()的时候返回null值
while((message = br.readLine())!=null) {
// 遍历所有输出流,将该客户端发送的信息转发给所有的客户端
sendMessage(message);
}
}catch(IOException e) {
e.printStackTrace();
}finally {
// 当客户掉线的时候,要将输出流从集合中删除
removeOut(pw);
if(socket!=null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public void start() {
System.out.println("等待客户端的连接");
// 监听客户端的连接
while(true) {
try {
Socket socket = serverSocket.accept();
System.out.println("客户端已经连接");
// 启动线程来完成针对该客户端的交互
ClientHandler handle = new ClientHandler(socket);
threadPool.execute(handle);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Server s = new Server();
s.start();
}
// 将输出流存入共享集合
private synchronized void allout(PrintWriter pw) {
allout.add(pw);
}
// 将消息转发给所有客户端
private synchronized void sendMessage(String message) {
for(PrintWriter o:allout) {
o.println(message);
}
}
// 将给定输出流从共享集合删除
private synchronized void removeOut(PrintWriter pw) {
allout.remove(pw);
}
}