在服务端通过Socket获取输出流,客户端获取输入流,实现服务端将消息发送给客户端
这里的服务端直接将客户端发送过来的消息再回复给客户端来进行测试
//服务端
public class Server {
private ServerSocket serverSocket;
public Server() {
try {
System.out.println("正在启动服务器...");
/**
* 实例化ServerSocket时要指定服务器端口,该端口不能与操作系统其他应用程序占用的端口相同
* 否则会抛出异常:java.net.BindException:address already in use
* 端口是一个数字,取值范围:0-65535之间
* 6000之前的端口不要使用,密集绑定系统应用和流行应用程序
*/
serverSocket=new ServerSocket(8088);
System.out.println("服务器启动完毕");
} catch (IOException e) {
e.printStackTrace();
}
}
//服务端开始工作的方法
public void start(){
try {
while (true){
System.out.println("等待客户端连接...");
Socket socket = serverSocket.accept();
System.out.println("一个客户端连接");
ClientHandler handler=new ClientHandler(socket);
Thread t=new Thread(handler);
t.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server=new Server();
server.start();
}
/**
* 定义线程任务
* 目的是让一个线程完成与特定客户端的交互工作
*/
private class ClientHandler implements Runnable{
private Socket socket;
private String host;
public ClientHandler(Socket socket) {
this.socket = socket;
host=socket.getInetAddress().getHostAddress();
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
InputStreamReader isr=new InputStreamReader(is,"UTF-8");
BufferedReader br=new BufferedReader(isr);
OutputStream os = socket.getOutputStream();
OutputStreamWriter osw=new OutputStreamWriter(os,"UTF-8");
BufferedWriter bw=new BufferedWriter(osw);
PrintWriter pw=new PrintWriter(bw,true);
String message=null;
while ((message=br.readLine())!=null){
System.out.println(host+"说"+message);
//将消息回给客户端
pw.println(host+"说"+message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//客户端
public class Client {
private Socket socket;
public Client() {
try {
System.out.println("正在连接服务器...");
socket=new Socket("localhost",8088);
System.out.println("与服务器连接成功");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端开始工作的方法
*/
public void start(){
try {
//低级流,将字节通过网络发送给对方
OutputStream os = socket.getOutputStream();
//高级流,负责衔接字节流与字符流,并将写出的字符按指定字符集转换字节
OutputStreamWriter osw=new OutputStreamWriter(os, StandardCharsets.UTF_8);
//高级流,负责块写文本数据加速
BufferedWriter bw=new BufferedWriter(osw);
//高级流,负责按行写出字符串,自动刷新
PrintWriter pw=new PrintWriter(bw,true);
InputStream is = socket.getInputStream();
InputStreamReader isr=new InputStreamReader(is,"UTF-8");
BufferedReader br=new BufferedReader(isr);
Scanner sc=new Scanner(System.in);
while (true){
String line = sc.nextLine();
if("exit".equalsIgnoreCase(line)){
break;
}
pw.println(line);
line = br.readLine();
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
/**
* 通讯完毕后调用socket的close方法。
* 该方法会给对方发送断开信号
*/
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Client client=new Client();
client.start();
}
}
当一个客户端发送一个消息后,服务端收到后如何转发给所有客户端
问题:例如红色的线程一收到客户端消息后如何获取到橙色的线程二中的输出流?得不到就无法将消息转发给橙色的客户端(进一步延申就是无法转发给所有其他客户端)
解决:内部类可以访问外部类的成员,因此在Server类上定义一个数组allOut可以被所有内部类ClientHandler实例访问
从而将这些ClientHandler实例之间想互相访问的数据存放在这个数组中达到共享数据的目的,对此只需将所有
ClientHandler中输出流都存放到数组allOut中就可以达到互访输出流转发消息的目的了。

//客户端不变,以下是服务端的代码
public class Server {
private ServerSocket serverSocket;
private PrintWriter[] allOut ={};
public Server() {
try {
System.out.println("正在启动服务器...");
/**
* 实例化ServerSocket时要指定服务器端口,该端口不能与操作系统其他应用程序占用的端口相同
* 否则会抛出异常:java.net.BindException:address already in use
* 端口是一个数字,取值范围:0-65535之间
* 6000之前的端口不要使用,密集绑定系统应用和流行应用程序
*/
serverSocket=new ServerSocket(8088);
System.out.println("服务器启动完毕");
} catch (IOException e) {
e.printStackTrace();
}
}
//服务端开始工作的方法
public void start(){
try {
while (true){
System.out.println("等待客户端连接...");
Socket socket = serverSocket.accept();
System.out.println("一个客户端连接");
ClientHandler handler=new ClientHandler(socket);
Thread t=new Thread(handler);
t.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server=new Server();
server.start();
}
/**
* 定义线程任务
* 目的是让一个线程完成与特定客户端的交互工作
*/
private class ClientHandler implements Runnable{
private Socket socket;
private String host;
public ClientHandler(Socket socket) {
this.socket = socket;
host=socket.getInetAddress().getHostAddress();
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
InputStreamReader isr=new InputStreamReader(is,"UTF-8");
BufferedReader br=new BufferedReader(isr);
OutputStream os = socket.getOutputStream();
OutputStreamWriter osw=new OutputStreamWriter(os,"UTF-8");
BufferedWriter bw=new BufferedWriter(osw);
PrintWriter pw=new PrintWriter(bw,true);
allOut= Arrays.copyOf(allOut,allOut.length+1);
allOut[allOut.length-1]=pw;
String message=null;
while ((message=br.readLine())!=null){
System.out.println(host+"说"+message);
//将消息回给所有客户端
for (int i=0;i<allOut.length;i++){
allOut[i].println(host+"说"+message);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
由于客户端start方法中循环进行的操作顺序是先通过控制台输入一句话然后将其发送给服务端,然后再读取服务端发送回来的一句话,这导致如果客户端不输入内容就无法收到服务端发送过来的其他信息(其他客户端的聊天内容)
因此要将客户端中接收消息的工作移动到一个单独的线程上执行,才能保证发消息互不打扰。
package socket;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Client {
private Socket socket;
public Client() {
try {
System.out.println("正在连接服务器...");
socket=new Socket("localhost",8088);
System.out.println("与服务器连接成功");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端开始工作的方法
*/
public void start(){
//启动读取服务端发送过来消息的线程
try {
ServerHandler handler=new ServerHandler();
Thread t=new Thread(handler);
t.setDaemon(true);
t.start();
//低级流,将字节通过网络发送给对方
OutputStream os = socket.getOutputStream();
//高级流,负责衔接字节流与字符流,并将写出的字符按指定字符集转换字节
OutputStreamWriter osw=new OutputStreamWriter(os, StandardCharsets.UTF_8);
//高级流,负责块写文本数据加速
BufferedWriter bw=new BufferedWriter(osw);
//高级流,负责按行写出字符串,自动刷新
PrintWriter pw=new PrintWriter(bw,true);
Scanner sc=new Scanner(System.in);
while (true){
String line = sc.nextLine();
if("exit".equalsIgnoreCase(line)){
break;
}
pw.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
/**
* 通讯完毕后调用socket的close方法。
* 该方法会给对方发送断开信号
*/
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Client client=new Client();
client.start();
}
private class ServerHandler implements Runnable{
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
InputStreamReader isr=new InputStreamReader(is,"UTF-8");
BufferedReader br=new BufferedReader(isr);
String line;
while ((line=br.readLine())!=null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
为了让能叫消息转发给所有客户端,我们在Server上添加一个数组类型的属性allOut,并且共所有线程ClientHandler使用,这时对数组的操作要考虑并发安全问题
package socket;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
public class Server {
private ServerSocket serverSocket;
private PrintWriter[] allOut ={};
public Server() {
try {
System.out.println("正在启动服务器...");
/**
* 实例化ServerSocket时要指定服务器端口,该端口不能与操作系统其他应用程序占用的端口相同
* 否则会抛出异常:java.net.BindException:address already in use
* 端口是一个数字,取值范围:0-65535之间
* 6000之前的端口不要使用,密集绑定系统应用和流行应用程序
*/
serverSocket=new ServerSocket(8088);
System.out.println("服务器启动完毕");
} catch (IOException e) {
e.printStackTrace();
}
}
//服务端开始工作的方法
public void start(){
try {
while (true){
System.out.println("等待客户端连接...");
Socket socket = serverSocket.accept();
System.out.println("一个客户端连接");
ClientHandler handler=new ClientHandler(socket);
Thread t=new Thread(handler);
t.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server=new Server();
server.start();
}
/**
* 定义线程任务
* 目的是让一个线程完成与特定客户端的交互工作
*/
private class ClientHandler implements Runnable{
private Socket socket;
private String host;
public ClientHandler(Socket socket) {
this.socket = socket;
host=socket.getInetAddress().getHostAddress();
}
@Override
public void run() {
PrintWriter pw=null;
try {
InputStream is = socket.getInputStream();
InputStreamReader isr=new InputStreamReader(is,"UTF-8");
BufferedReader br=new BufferedReader(isr);
OutputStream os = socket.getOutputStream();
OutputStreamWriter osw=new OutputStreamWriter(os,"UTF-8");
BufferedWriter bw=new BufferedWriter(osw);
pw=new PrintWriter(bw,true);
// synchronized (this) {//不行,因为这个是ClientHandler实例
// synchronized (allOut) {//不行,下面操作会扩容,allOut对象会变
synchronized (Server.this){
allOut= Arrays.copyOf(allOut,allOut.length+1);
allOut[allOut.length-1]=pw;
}
//通知所有客户端该用户上线了
sendMessage(host+"下线了,当前在线人数:"+allOut.length);
String message=null;
while ((message=br.readLine())!=null){
System.out.println(host+"说"+message);
//将消息回给所有客户端
for (int i=0;i<allOut.length;i++){
allOut[i].println(host+"说"+message);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
synchronized (Server.this){
for(int i=0;i<allOut.length;i++){
if(allOut[i]==pw){
allOut[i] = allOut[allOut.length - 1];
allOut = Arrays.copyOf(allOut, allOut.length - 1);
break;
}
}
}
sendMessage(host+"下线了,当前在线人数:"+allOut.length);
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void sendMessage(String message){
for (int i=0;i<allOut.length;i++){
allOut[i].println(message);
}
}
}
}