即时通信是什么含义,要实现怎么样的设计?
即时通信,是指一个客户端的消息发出去,其他客户端可以接收到
即时通信需要进行端口转发的设计思想。
服务端需要把在线的Socket管道存储起来
一旦收到一个消息要推送给其他管道
客户端:
首先具备发送数据的功能,其次具备接收其它客户的数据能力。
客户端操作:
1、创建Socket通信管道请求服务端的连接,指定服务端的ip与port;作为客户端,只需要两条线程,即数据输出,数据接收,主线程作为客户端信息的输出,那么我们只需要在创建一个线程为它接收数据做好准备就好啦。
2、数据输出,通过Socket管道得到一个字节输出流,负责数据发送,因为数据发送可以使用更加高级的(打印)PrintStream流,使得代码更加优雅,高级;
3、再使用Scanner获取客户输入的信息,并打印至服务端,记得每次输出需要刷新管道,释放资源。
4、因为即时通信,就不仅仅只是发送一次,接收一次;我们需要多发多收,即对上式进行循环操作即可。
5、子线程即数据接收线程通过自定义线程类,重写run()方法进行操作修改,也就是将数据的接收放在线程类的run()方法内跑起来。
6、首先需要一个Socket对象,那么就可以定义一个有参构造器,在定义的时候就将客户对象送进来,就很大方便操作了。同输出数据一样,还是通过Socketr获取一个字节输入流,同样将其通过BufferedReader流封装成高级流(缓冲字符输入流),方便一行读取接收到的信息;那么就存在一个问题,字节流怎么变成字符流:不得不告诉你了,有一个叫作转换流的家伙可以做到,就是它InputStreamReader(InputStream is)方法。这样就准备好了流,再不断地接收信息就好啦,如果没有接收到信息,也不用担心,它会等待,直到有数据给它。
/**
客户端:发消息的同时,随时有人发消息过来。
*/
public class Client {
public static void main(String[] args) throws Exception{
// 客户端
System.out.println("----客户端启动成功-----");
// 1.创建Socket管道请求服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket(InetAddress.getLocalHost(), 7777);
// 为客户端分配一个子线程负责收到的消息
new ClientThread(socket).start();
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.print("您说:");
String s = sc.nextLine();
// 打印信息给服务端
ps.println(s);
// 刷新
ps.flush();
}
}
}
public class ClientThread extends Thread{
Socket socket;
public ClientThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 1、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 2、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str;
// // 3、按照行读取消息
while ((str = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + str);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线啦");
}
}
}
服务端:
首先要考虑有多少客户需要接收来自其它用户的信息,可以先定义一个静态的集合存储每一个与服务端连接的客户。
1、客户要与服务端进行连接,那么服务端肯定需要一个端口;然后接收每一个客户的连接信号与它送过来的数据,以及端口的转发。
2、为了多个客户的同时性,将每一个连接客户添加至集合中,并交给一个线程,如果怕线程过多,也可以定义线程池进行限制,线程池可以很好的避免系统瘫痪;当前只需要几个线程进行测试,所有使用普通的Thread类重写即可;
3、启动线程。
4、同样使用有参构造器,获取客户对象进行操作。
5、服务端首先还是需要接收客户的信息再将信息发给其它客户这一流程。接收信息可参考客户端接收信息,步骤一致,唯一不同需要在接收信息时,为其它客户端发送信息,定义一个方法更加优雅。
6、信息转发方法:我们知道,在服务端内定义了一个静态的集合,通过类名.集合名可以得到集合的数据,对集合进行遍历,排除发送此条信息的客户本身,将信息发送给其它用户端即可。
/**
服务端:接收消息后,推送给其他所有的在线socket
*/
public class Server {
public static List<Socket> onLineSocket = new ArrayList<>();
public static void main(String[] args) throws Exception{
// 服务端
System.out.println("----服务端启动成功-----");
// 1、注册端口
ServerSocket socket = new ServerSocket(7777);
// 定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,交给一个线程负责读取消息
Socket accept = socket.accept();
System.out.println(accept.getRemoteSocketAddress() + "上线啦!");
// 把当前客户端管道Socket加入到在线集合中去
onLineSocket.add(accept);
// 3、开始创建独立线程处理socket
new ServerThread(accept).start();
}
}
}
public class ServerThread extends Thread{
Socket socket;
public ServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str;
while ((str = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说:" + str);
// 把这个消息发给当前所有在线socket
sendMassageOther(str);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线啦");
// 当某一个客户下线后,将其从集合中去除
Server.onLineSocket.remove(socket);
}
}
private void sendMassageOther(String str) {
// 遍历全部的在线socket给他们发消息
for (Socket socket : Server.onLineSocket) {
// 除了自己的socket,其他socket都发送信息
if (socket != this.socket){
try {
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println(str);
ps.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}