以下内容转自:
https://www.cnblogs.com/gnoc/p/4866788.html
前言
socket(套接字),Socket和ServerSocket位于java.net包中,持续开启服务端,接收来自客户端的信息,并响应。
最开始,咱们先来两段最简单的服务端和客户端的代码
最简单的服务端代码:
package com.socket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class TestSocketService {
//自定义一个端口号
private static final int PORT = 8888;
public static void main(String[] args) {
ServerSocket server = null;
Socket socket = null;
DataInputStream dataInputStream = null;
DataOutputStream dataOutputStream = null;
try {
server = new ServerSocket(PORT);
System.out.println("监听端口:" + PORT);
socket = server.accept();
// 接受客户端请求
dataInputStream = new DataInputStream(socket.getInputStream());
String request = dataInputStream.readUTF();
System.out.println("from client..." + request);
// 响应客户端
dataOutputStream = new DataOutputStream(socket.getOutputStream());
String response = "收到";
dataOutputStream.writeUTF(response);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (dataInputStream != null) {
dataInputStream.close();
}
if (dataOutputStream != null) {
dataOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
最简单的客户端代码:
package com.socket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public class TestSocketClient {
private static final String HOST = "127.0.0.1";
private static final int PORT = 8888;
public static void main(String[] args) {
Socket socket = null;
DataInputStream dataInputStream = null;
DataOutputStream dataOutputStream = null;
try {
socket = new Socket(HOST, PORT);
//给服务端发送请求
dataOutputStream = new DataOutputStream(socket.getOutputStream());
String request = "我是客户1";
dataOutputStream.writeUTF(request);
dataInputStream = new DataInputStream(socket.getInputStream());
String response = dataInputStream.readUTF();
System.out.println(response);
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(dataInputStream != null){
dataInputStream.close();
}
if(dataOutputStream != null){
dataOutputStream.close();
}
if(socket != null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端和服务端分别运行,能收发数据,但是服务端只服务了一次就停止了,这明显不符合需求,服务端应该响应完客户端之后,继续监听端口,等待下一个客户端的连接。
让服务端一直提供服务
将服务端的代码写入循环中持续循环,一直监听来自客户端的请求。修改服务端的代码:
public static void main(String[] args) throws IOException {
ServerSocket server = null;
Socket socket = null;
DataInputStream dataInputStream = null;
DataOutputStream dataOutputStream = null;
server = new ServerSocket(PORT);
System.out.println("监听端口:" + PORT);
while(true){
try {
socket = server.accept();
// 接受客户端请求
dataInputStream = new DataInputStream(socket.getInputStream());
String request = dataInputStream.readUTF();
System.out.println("from client..." + request);
// 响应客户端
dataOutputStream = new DataOutputStream(socket.getOutputStream());
String response = "收到";
dataOutputStream.writeUTF(response);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (dataInputStream != null) {
dataInputStream.close();
}
if (dataOutputStream != null) {
dataOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
通过while(true)循环,服务端一直监听端口,由于socket是阻塞的,只有服务端完成了当前客户端的响应,才会继续处理下一个客户端的响应。这样一直让主线线程去处理socket请求不合适,因此需要为服务端加上多线程功能,同时处理多个socket请求。
给服务端加上多线程
修改代码,将服务端的socket处理抽取出来,并且封装到Runnable接口的run方法中:
package com.socket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public class TestThread extends Thread {
private Socket socket;
public TestThread(Socket socket){
this.socket = socket;
}
public void run() {
DataInputStream dataInputStream = null;
DataOutputStream dataOutputStream = null;
try {
// 接受客户端请求
dataInputStream = new DataInputStream(socket.getInputStream());
String request = dataInputStream.readUTF();
System.out.println("from client..." + request+" 当前线程:"+Thread.currentThread().getName());
// 响应客户端
dataOutputStream = new DataOutputStream(socket.getOutputStream());
String response = "收到";
dataOutputStream.writeUTF(response);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (dataInputStream != null) {
dataInputStream.close();
}
if (dataOutputStream != null) {
dataOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
修改服务端,添加多线程功能:
package com.socket;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class TestSocketService {
private static final int PORT = 8888;
public static void main(String[] args) throws IOException {
ServerSocket server = null;
Socket socket = null;
server = new ServerSocket(PORT);
System.out.println("监听端口:" + PORT);
while(true){
socket = server.accept();
new TestThread(socket).start();
}
}
}
弊端分析
尽管服务端现在已经有了多线程处理能力,但是服务端每次接收到客户端的请求后,都会创建一个新的线程去处理,而jvm的线程数量过多是,服务端处理速度会变慢。而且如果并发较高的话,瞬间产生的线程数量也会比较大,因此,我们需要再给服务端加上线程池的功能。
使用java.util.concurrent.Executor类就可以创建一个简单的线程池,代码如下:
package com.socket;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class TestSocketService {
private static final int PORT = 8888;
public static void main(String[] args) throws IOException {
ServerSocket server = null;
Socket socket = null;
server = new ServerSocket(PORT);
System.out.println("监听端口:" + PORT);
//FixedThreadPool最多开启3(参数)个线程,多余的线程会存储在队列中,等线程处理完了
//再从队列中获取线程继续处理
Executor executor = Executors.newFixedThreadPool(3);
while(true){
socket = server.accept();
executor.execute(new TestThread(socket));
}
}
}
Executor一共有4种线程池实现,这里使用了FixedThreadPool最多开启3(参数)个线程,多余的线程会存储在队列中,等线程处理完了再从队列中获取,继续处理。这样的话无论并发量多大,服务端只会最多3个线程进行同时处理,使服务端的压力不会那么大。