Web项目集成Socket多线程通信,多客户端通信
ps:一个服务端面对多可客户端
需要写三个基本的类
- ServerListener:跟随web初始化创建服务端socket
- ServerThread:开启监听端口
- ServerOperate:处理数据
1、ServerListener
public class ServerListener implements ServletContextListener {
ServerThread serverThread;
//web容器初始化,执行此方法
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
if(serverThread==null){
serverThread = new ServerThread();
serverThread.start();
}
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
if(serverThread!=null){
serverThread.closeServer();
}
}
}
解释:
- 如果需要在web启动后就开启ServerSocket监听功能,则需要写一个类去继承ServletContextListener,并实现它的方法contextInitialized,我们则需要在这个方法里重写一下开启ServerSocket监听的方法。
- 还需要在web.xml里监听一下路径才可以生效
<listener>
<listener-class>com.socket.ServerListener</listener-class>
</listener>
- 里面的ServerThread 是需要创建的第二个基本的类。
2、ServerThread
public class ServerThread extends Thread{
private ServerSocket serverSocket = null;
public static final int PORT = 8888;
public ServerThread() {
try{
if(null == serverSocket){
//1、创建一个服务端Socket,即ServerSocket,指定绑定的端口,并监听此端口
this.serverSocket = new ServerSocket(PORT);
System.out.println("=========服务端即将启动,监听客户端连接=========");
}
} catch (IOException e){
System.out.println("创建服务端监听线程出错");
e.printStackTrace();
}
}
@Override
public void run() {
openServer();
}
public void openServer() {
try {
Socket socket = null;
int count = 0;
while (true) {
System.out.println("正在监听第"+ (++count)+"次");
//2、循环调用accept方法开始监听,等待客户端连接
socket = serverSocket.accept();
//创建一个新的线程
ServerOperate serverThread = new ServerOperate(socket);
//启动线程
serverThread.start();
}
//因为循环监听,所以不会停止
//serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void closeServer() {
try {
serverSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
解释:
- 为什么第一步需要线程来创建ServerThread 呢?
因为socket通信是通过accept()方法阻塞式监听的,而我们在创建ServerThread 类时,会执行到这个方法,这时候整个web程序就会阻塞而启动不了,所以我们需要另开启一个线程来执行这个阻塞式监听,所以这个类需要继承Thread,这样就不会影响web的主线程逻辑了。 - 为什么要写在while循环里呢?
因为我们不止监听一次,每次监听到数据传过来时,都会开启新线程处理数据,然后在回去继续监听。 - 为什么要开启新线程处理数据呢?
因为不可能在同一个线程上处理数据啊! - 里面的ServerOperate是需要创建的第三个基本的类
3、ServerOperate
public class ServerOperate extends Thread {
//和本线程相关的socket
Socket socket = null;
public ServerOperate(Socket socket) {
this.socket = socket;
}
//线程执行的操作,响应客户端的请求
@Override
public void run() {
System.out.println("我要处理客户端发送的数据了");
operate();
}
public void operate(){
InputStream is = null;//字节流
InputStreamReader isr = null;//转换为字符流
BufferedReader br = null;//添加缓冲
OutputStream os = null;
PrintWriter pw = null;
try {
//3、获取输入流,读取客户端的信息
is = socket.getInputStream();
isr = new InputStreamReader(is,"utf-8");
br = new BufferedReader(isr);
String info = null;
while ((info = br.readLine()) != null) {
System.out.println("服务端接收到的客户端数据:" + info);
}
socket.shutdownInput();//关闭输入流
//4、获取输出流,返回数据
os = socket.getOutputStream();
pw = new PrintWriter(os);
pw.write("服务端欢迎您");
pw.flush();//输出
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//5、关闭相关资源
if (pw != null) pw.close();
if (os != null) os.close();
if (br != null) br.close();
if (isr != null) isr.close();
if (is != null) is.close();
if (socket != null) socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这三个类就基本写完了,没有写关闭的方法。
写main简单测一下
public static void main(String[] args) {
try {
//1、创建客户端socket,指定服务器地址端口
Socket socket = new Socket("localhost",8888);
//2、获取输出流,发送数据
OutputStream os = socket.getOutputStream();//字节输出流
PrintWriter pw = new PrintWriter(os);//转换为打印流
pw.write("我叫:Lisaqwe ");
pw.flush();
socket.shutdownOutput();//关闭输出流
//3、获取输入流,读取响应信息
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is,"gbk"));
String info =null;
while((info=br.readLine())!=null){
System.out.println("客户端接收响应的数据:"+info);
}
//4、关闭其他相关资源
br.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}