1. Socket类是干嘛的
Socket类是用于建立客户端与服务端之间双向通信的类。
2. 为什么需要Socket类
在没有使用socket模式(双向通信模式)下,通信方式为http模式(请求-响应模式),即客户端发送请求,服务端响应数据。服务端并不能主动向客户端发送信息,客户端要想与服务端建立双向通信,只能每隔一段时间向服务端发起请求。但是这种方法会带来延时高、服务端资源消耗大等问题,因此socket模式更适合聊天室场景。
3. Socket()和ServerSocket()
你是李白,我是杜甫,有一天你饮酒诗意起,想要和我交流一下你刚写的诗。于是你拿起电话给我发了一句“劝君更尽一杯酒, 西出阳关无故人。”,可是怎么也发不出去,微信提示着网络无法连接让你为了难,于是你拼命的爬上了有基站的山顶才将信息发了出去。因此看出要想实现客户端(李白、杜甫)之间通信,服务端(基站)必须作为中转站转发消息。
现在,聪明的你一定想到了Socket()和ServerSocket()是用来干嘛的了。
Socket()为客户端构造方法。
Socket client = new Socket("127.0.0.1",1024)//参数:IP地址,服务端端口号
ServerSocket()为服务端构造方法。
ServerSocket server = new ServerSocket(1024)//参数:服务端端口号
4. 编写Socket服务端类
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class SocketServer {
public static void main(String[] args) {
ServerSocket server = null;
try {
//实例化服务端,端口号为1024
server = new ServerSocket(1024);
System.out.println("服务已启动,等待客户端连接");
//循环用于接收多个客户端连接
while (true) {
//接收客户端的连接,并将预制连接的客户端信息存入该Socket实例
Socket socket = server.accept();
String ip = socket.getInetAddress().getHostAddress();
System.out.println("有客户端连接ip:" + ip + "端口:" + socket.getPort());
//开启新线程接收客户端发送的信息
new Thread(()-> {
while (true) {
InputStream inputStream = null;
try {
//获取关于客户端的输入流
inputStream = socket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String read = null;
try {
read = bufferedReader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("收到客户端消息->" + read);
//获取关于客户端的输出流,并向客户端输出消息
OutputStream outputStream = null;
try {
outputStream = socket.getOutputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("我是服务端,我已收到消息:" + read);
printWriter.flush();
}
}).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
5. 编写Socket客户端类
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class SocketClient {
public static void main(String[] args) {
Socket client = null;
try {
//实例化客户端,与ip地址为127.0.0.1即本机地址(localhost),端口号为1024的服务端连接
client = new Socket("127.0.0.1", 1024);
System.out.println("客户端初始化,并连接上服务端");
//获取与服务端之间的输出流
OutputStream outputStream = client.getOutputStream();
PrintWriter printStream = new PrintWriter(outputStream);
//开启客户端输出信息线程,并输出信息
new Thread(()->{
while (true) {
System.out.println("请输入内容:");
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
printStream.println(input);
printStream.flush();
}
}).start();
//获取与服务端之间的输入流
InputStream inputStream = client.getInputStream();
//开启客户端输入信息线程,并接收信息
new Thread(()-> {
while (true) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String read = null;
try {
read = bufferedReader.readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println("收到服务端消息->" + read);
}
}).start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
输入内容测试完毕
6. 客户端之间的信息发送
在上面的步骤中,我们完成了客户端与服务端之间的消息发送,但是多个客户端之间群聊应该怎么实现呢?聪明的你一定想的了之前讲的李白和杜甫的故事。我们可以利用服务端作为中转站,将客户端A发送给服务端的消息转发给客户端B、客户端C......
具体实现: 我们在服务端维护一个HashMap存放客户端信息,每次做消息转发时就遍历HashMap向每个客户端发出消息。
7. 服务端代码
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class SocketServer {
public static void main(String[] args) {
ServerSocket server = null;
//新增->维护一个HashMap
Map<String,Socket> CLENT_MAP = new HashMap<>();
try {
//实例化服务端,端口号为1024
server = new ServerSocket(1024);
System.out.println("服务已启动,等待客户端连接");
//循环用于接收多个客户端连接
while (true) {
//接收客户端的连接,并将与之连接的客户端信息存入该Socket实例
Socket socket = server.accept();
String ip = socket.getInetAddress().getHostAddress();
System.out.println("有客户端连接ip:" + ip + "端口:" + socket.getPort());
//新增->将连接的客户端信息存入HashMap
String clintKey = ip + socket.getPort();
CLENT_MAP.put(clintKey,socket);
//开启新线程接收客户端发送的信息
new Thread(()-> {
while (true) {
InputStream inputStream = null;
try {
//获取关于客户端的输入流
inputStream = socket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String read = null;
try {
read = bufferedReader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("收到客户端消息->" + read);
//新增->进行消息转发
String finalRead = read;
CLENT_MAP.forEach( (k, v)->{
try {
OutputStream outputStream = v.getOutputStream();
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(outputStream));
printWriter.println(socket.getPort() + "->" + finalRead);
printWriter.flush();
}catch (IOException e) {
e.printStackTrace();
}
});
//获取关于客户端的输出流,并向客户端输出消息
OutputStream outputStream = null;
try {
outputStream = socket.getOutputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("我是服务端,我已收到消息:" + read);
printWriter.flush();
}
}).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
8. 测试
- 复制粘贴SocketClient类并重命名
- 先启动SocketServer类,后启动另外3个SocketClient类。此时60288端口为SocketClientA的端口号,依此类推
- 在SocketClientA下输入:“你们好呀!!!我是A”。同时另外2个SocketClient就收到消息了