Java 网络编程
程序员文章站
2023-12-29 14:12:34
...
这里介绍下使用java的socket编程,搭建一个server与client的通信框架。先看一段代码:
Server端
Client端
上面的例子中Server端只是处理了与一个client端的通信,一旦有一个client与server连接上后,server端将不再接收其他client的连接了。如果想不断的接收其他client的连接,可以把accept()方法调用放到一个循环中。
这样服务器端就可以一直运行,只要在可用端口数的限制内,每个client都能与server建立连接。上面的代码只是搭建了一个基本的server与client通信的框架,要想完成一个可以商用的通信框架还需要做很多的工作。首当其冲的就是当客户端增多后,如何能快速的响应客户端的请求?
如果上面代码中通过ServerSocket.accept()获取到与client socket通信的server端socket后,如果与每个client通信的逻辑比较复杂耗时呢?也就是while 的每次循环都执行长时间,那么将有很多client的连接请求被阻塞,很多client会出现超时异常。对于这种情况,很自然的一个想法就是使用多线程,将与client通信的逻辑放到其他线程中处理,主线程只负责与client建立连接。
对server端代码做调整如下:
看到这里,肯定有人能发现问题了,如果有1000个client来连接,岂不是开1000个线程来处理,服务器的资源消耗将会直线上升(创建线程本身要消耗资源,线程之间的上下文频繁切换也消耗资源)。如何解决呢?线程池,对于优化高并发,对象池化是个神器。jdk中就提供了线程池,拿来主义,对服务器端代码修改如下:
弄到现在,一个像样地server与client的通信框架已成雏形,当然距离稳定,可扩展还有很远的距离。
Server端
import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class TestServer { public static void main(String[] args) { ServerSocket serverSocket = null; Socket socket = null; InputStream inStream = null; try { serverSocket = new ServerSocket(8080); } catch (IOException e) { e.printStackTrace(); } try { if (serverSocket != null) { // 线程将阻塞在此直到有客户端来连接 socket = serverSocket.accept(); //处理与客户端的通信逻辑 System.out.println("a client connected"); inStream = socket.getInputStream(); byte[] buff = new byte[8]; // 阻塞在这里直到有数据可以读取或者流结束也或者异常出现 int len = inStream.read(buff); System.out.println("server read " + len + " bytes"); } } catch (IOException e) { e.printStackTrace(); } finally { if (inStream != null) { try { inStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
Client端
import java.io.IOException; import java.io.OutputStream; import java.net.Socket; public class TestClient { public static void main(String[] args) { Socket socket = null; OutputStream out = null; try { // socket创建中就连接了服务器端,服务器端的accept()将不再阻塞 socket = new Socket("localhost", 8080); out = socket.getOutputStream(); // 在此行设置断点可以看到服务端一直阻塞在read方法调用上 out.write("hello world".getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
上面的例子中Server端只是处理了与一个client端的通信,一旦有一个client与server连接上后,server端将不再接收其他client的连接了。如果想不断的接收其他client的连接,可以把accept()方法调用放到一个循环中。
while (true) { // 线程将阻塞在此直到有客户端来连接 socket = serverSocket.accept(); //处理与客户端的通信逻辑 System.out.println("client " + socket.getRemoteSocketAddress() + " connected"); inStream = socket.getInputStream(); byte[] buff = new byte[8]; // 阻塞在这里直到有数据可以读取或者流结束也或者异常出现 int len = inStream.read(buff); System.out.println("server read " + len + " bytes"); }
这样服务器端就可以一直运行,只要在可用端口数的限制内,每个client都能与server建立连接。上面的代码只是搭建了一个基本的server与client通信的框架,要想完成一个可以商用的通信框架还需要做很多的工作。首当其冲的就是当客户端增多后,如何能快速的响应客户端的请求?
如果上面代码中通过ServerSocket.accept()获取到与client socket通信的server端socket后,如果与每个client通信的逻辑比较复杂耗时呢?也就是while 的每次循环都执行长时间,那么将有很多client的连接请求被阻塞,很多client会出现超时异常。对于这种情况,很自然的一个想法就是使用多线程,将与client通信的逻辑放到其他线程中处理,主线程只负责与client建立连接。
对server端代码做调整如下:
public static void main(String[] args) { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(8080); } catch (IOException e) { e.printStackTrace(); } try { if (serverSocket != null) { while (true) { // 线程将阻塞在此直到有客户端来连接 Socket socket = serverSocket.accept(); // 针对每个线程另开线程处理通信逻辑 process(socket); } } } catch (IOException e) { e.printStackTrace(); } finally { if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } } public static void process(final Socket socket) { new Thread(new Runnable() { public void run() { InputStream inStream = null; System.out.println("client " + socket.getRemoteSocketAddress() + " connected"); try { inStream = socket.getInputStream(); byte[] buff = new byte[8]; // 阻塞在这里直到有数据可以读取或者流结束也或者异常出现 int len = inStream.read(buff); System.out.println("server read " + len + " bytes"); } catch (IOException e) { e.printStackTrace(); } finally { if (inStream != null) { try { inStream.close(); } catch (IOException e) { e.printStackTrace(); } } try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }).start(); }
看到这里,肯定有人能发现问题了,如果有1000个client来连接,岂不是开1000个线程来处理,服务器的资源消耗将会直线上升(创建线程本身要消耗资源,线程之间的上下文频繁切换也消耗资源)。如何解决呢?线程池,对于优化高并发,对象池化是个神器。jdk中就提供了线程池,拿来主义,对服务器端代码修改如下:
private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime() .availableProcessors() * 2);; //... public static void process(final Socket socket) { executorService.execute(new Runnable() { public void run() { InputStream inStream = null; System.out.println("client " + socket.getRemoteSocketAddress() + " connected"); try { inStream = socket.getInputStream(); byte[] buff = new byte[8]; // 阻塞在这里直到有数据可以读取或者流结束也或者异常出现 int len = inStream.read(buff); System.out.println("server read " + len + " bytes"); } catch (IOException e) { e.printStackTrace(); } finally { if (inStream != null) { try { inStream.close(); } catch (IOException e) { e.printStackTrace(); } } try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }); }
弄到现在,一个像样地server与client的通信框架已成雏形,当然距离稳定,可扩展还有很远的距离。