用socket底层原理实现简单聊天室程序,本篇为服务器端2.0版本(完结篇)
文章目录
* 1、启动服务器:构造方法 new ServerSocek对象并绑定程序端口
* 2、等待连接:
* 3、获取客户端发送的信息及客户端主机信息并打印在控制台
* 4、多线程支持被多个客户端连接互不干扰
* 5、广播给所有客户端其他客户端发送过来的消息
* 6、将PrintWriter数组换成集合列表优化减少代码和拓展的便捷
* 7、端口8888
package homeWork.ChatRoom220707;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* author:木易阿发
* time:220707
* moditime:220710
* port:8888
* 1、启动服务器:构造方法 new ServerSocek对象并绑定程序端口
* 2、等待连接:
* 3、获取客户端发送的信息及客户端主机信息并打印在控制台
* 4、多线程支持被多个客户端连接互不干扰
* 5、广播给所有客户端其他客户端发送过来的消息
* 6、将PrintWriter数组换成集合列表优化减少代码和拓展的便捷性
*/
public class Servers {
int serverNum ;
private ServerSocket serverSocket;
// private PrintWriter[] pws={};//在根类创建一个输出流数组用来存放所有客户端发送过来的消息
private Collection<PrintWriter> pws = new ArrayList<>();//在根类创建一个输出流数组用来存放所有客户端发送过来的消息
public static void main(String[] args) {
Servers server = new Servers();//new对象同时初始化
//调用等待连接方法
server.startServer();
}
// 1、构造器初始化启动服务器指定端口
public Servers(){
try {
System.out.println("duang 瓮....");
serverSocket = new ServerSocket(8888);
System.out.println("服务器已成功运行.哔..哔...哔....");
} catch (IOException e) {
e.printStackTrace();
}
}
// 2、等待客户端连接
public void startServer(){
//4、多线程支持被多个客户端连接互不干扰
try {
while (true) {//循环不断接受用户连接从而启动新线程
// 该线程任务是负责与指定的客户端进行交互
Socket socket = serverSocket.accept();
// System.out.println("ID-" + (++serverNum) + "-" + socket.getInetAddress().getHostName() + "(成功接入)");
//没得到一个连接就启动一个线程单独交互
Runnable clientHandler = new ClientHandler(socket, ++serverNum);//线程任务类
Thread thread = new Thread(clientHandler);//线程对象
thread.start();//启动线程
}
}catch(IOException e){
e.printStackTrace();
}
}
//3、获取客户端发送的信息及客户端主机信息并打印在控制台
//客户端处理程序:处理服务器发回来的消息等
private class ClientHandler implements Runnable{
private int serverNum;//用户编号
private Socket socket;
private String host;//记录访问对象的主机地址信息
public ClientHandler(Socket socket,int serverNum) {
this.serverNum=serverNum;
this.socket=socket;
host=socket.getInetAddress().getHostName();
}
@Override
public void run() {//交互任务
PrintWriter pw = null;//输出流
try {
InputStream is = socket.getInputStream();;//通过socket获取输入流,得到客户端发送过来的消息
InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);//字符转换流,将is的字节流转换为字符
BufferedReader br = new BufferedReader(isr);//缓冲字节输入流,块读取
//通过start方法循环传输过来的socket获取输出流给每个客户端广播消息
OutputStream os = socket.getOutputStream();//通过socket获取字节输出流
OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8);//字节转换流,将字符转换成字节写出指定编码
BufferedWriter bw = new BufferedWriter(osw);//缓冲输出流,保证语句的完整性
pw = new PrintWriter(bw,true);//字符输出流(特别注意开启行刷新),按行写出控制台输入的消息
//将输出流存入输出流数组
synchronized (Servers.this) {//线程同步锁,避免数组出现线程安全
// pws = Arrays.copyOf(pws, pws.length + 1);//先对数组扩容
// pws[pws.length - 1] = pw;//把输出流对象存入数组的最后一个空间位置
pws.add(pw);
}
sendMessage("ID"+(serverNum)+"-IP"+host+" 接入上线,当前在线人数:"+pws.size());//上线广播
//向所有客户端广播消息
String message;
while ((message = br.readLine()) != null) {
sendMessage("ID"+(serverNum)+"-IP"+host+"-"+message);//向每个客户端发送当前客户端发送的消息
}
} catch (IOException e) {
//当客户端强行关闭,服务端这里readLine会抛出异常,这里就可以catch异常从而执行finally
}finally {
synchronized (Servers.this) {//线程锁
//如果当前pw被关闭就将当前pw从广播数组中删除并通知下线
// for (int i = 0; i < pws.length; i++) {
// if (pws[i] == pw) {//找到数组中的当前pw
// pws[i] = pws[pws.length - 1];//将数组中的最后一个元素赋值到被关闭的pw所在数组的位置
// pws = Arrays.copyOf(pws, pws.length - 1);//将数组缩容
// //通知下线
// sendMessage("ID"+serverNum+"-IP"+host+"-关闭下线了,当前在线人数:"+pws.length);
// break;
// }
// }
pws.remove(pw);
sendMessage("ID"+serverNum+"-IP"+host+"-关闭下线了,当前在线人数:"+pws.size());
}
}
}
//5、广播给所有客户端其他客户端发送过来的消息
//将当前客户端发的消息发送给所有客户端
private void sendMessage(String line){
System.out.println(line);
synchronized (Servers.this) {//线程锁
// for (int i = 0; i < pws.length; i++) {
// pws[i].println(line);//调用每个客户端socket对象进行消息发送
// }
for (PrintWriter pw:pws
) {
pw.println(line);
}
}
}
}
}
/**------------------反爬声明o(▽)咻咻咻-------------------
** 作者:木易阿发 版权声明:**
本文为博主倾情原创文章,转载请附上源文链接!
如觉得本文对你有所收获,你的请评论点赞与
转发也将是鼓励支持我继续创作的动力。
更多精彩在视频号和公众号或 百度 *木易阿发* !
Gitee:木易阿发 (muyiafa) - Gitee.com ,
个人网站:http://www.muyiafa.com ,
乐学至善 开源共享 知行合一。
-------------------咻咻咻o(▽)反爬声明--------------------*/