java Socket 客户端服务端对接正确写法(BIO)
之前在工作中写过一些Socket客户端与服务端的代码,但是当时没有时间仔细研究,只能不报错先过的态度,对其细节了解不深,写的代码有各种问题也浑然不知,只是业务量级以及对接方对接代码没有出现出格的情况所以问题不得暴露。
首先通过单线程Socket做服务端是一种BIO的做法,这种做法会导致服务端只能同时接收一笔请求,性能非常差
下面我把BIO的代码帖一下,有需要的同学可以参考
服务端
public class SocketIO { //客户端编码,客户端发送编码与服务端一致,则服务端无需进行解码特殊处理 private static final String CLIENTENCODEING = "UTF-8"; private static final int PORT = 7777; private static AtomicInteger count = new AtomicInteger(); public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(PORT); System.out.println("端口启动:"+PORT); while (true) { Socket socket = null; InputStream inputStream = null; OutputStream outputStream = null; try { //无数据时会阻塞在这里,有数据即accept事件会触发accept方法的调用 //当一个连接进来,第二个连接进不来,因为单线程时,还阻塞在下面的read,线程无法再回到accept上继续等待 socket = serverSocket.accept(); int ccount = count.incrementAndGet(); System.out.println("新的客户端已连接,当前No."+ccount+" "+System.currentTimeMillis()); inputStream = new BufferedInputStream(socket.getInputStream()); outputStream = new BufferedOutputStream(socket.getOutputStream()); //读取正文内容 byte[] flush = new byte[1024]; int length = 0; StringBuffer rec = new StringBuffer(); while ((length = inputStream.read(flush)) != -1) { rec.append(new String(flush, 0, length)); } //写法2 //客户端不通知关闭socket.shutdownOutput();时,用下面这种方法,不通知关闭read方法会死循环 //available()方法可以在读写操作前先得知数据流里有多少个字节可以读取 //但如果客户端分批发送可能有问题,可能无法获得第二批及以后的数据 //所以最好还是让客户端通知一下 // int count = 0; // while(count == 0){ // count = inputStream.available(); // } // byte[] flush = new byte[count]; // inputStream.read(flush); // String rec = new String(flush, 0, count, CLIENTENCODEING); String back = "["+ccount+"]"+UUID.randomUUID() + ""; System.out.println("收到数据:" + rec.toString() + " 即将返回数据:" + back); //返回数据 outputStream.write(back.getBytes(), 0, back.getBytes().length); outputStream.flush(); } catch (Exception e) { e.printStackTrace(); } finally { close(socket,inputStream,outputStream); } } } private static void close(Socket socket, InputStream inputStream, OutputStream outputStream) throws IOException { if (outputStream != null) { outputStream.close(); } if (inputStream != null) { inputStream.close(); } if (socket != null) { socket.close(); } } }
列几点需要注意的点:
1、最好通过read方法来读取客户端发来的内容,代码中available我实测客户端发送多次的情况下会丢消息,不可靠。
2、最好通过缓冲流Buffered*Stream来配合InputStream、OutputStream使用
3、write后记得flush
4、关闭流
客户端
//服务端编码 private static final String SERVERENCODEING = "UTF-8"; public static void main(String[] args){ for (int i = 0; i < 1; i++) { new Thread(() -> { try { doo(); } catch (IOException e) { e.printStackTrace(); } }).start(); } } public static void doo() throws IOException { Socket socket = null; InputStream in = null; OutputStream out = null; String msg = "你好你好你好好好!"; try { //发送数据 socket = new Socket("127.0.0.1", 7777); out = new BufferedOutputStream(socket.getOutputStream()); in = new BufferedInputStream(socket.getInputStream()); out.write(msg.getBytes()); out.flush(); out.write("还有点".getBytes()); out.flush(); // //任何的输入流或输出流的close()都会造成Socket关闭 但是不关闭又导致服务端无法接收到-1 // out.close(); socket.shutdownOutput(); //读取正文内容 byte[] flush = new byte[1024]; int length = 0; StringBuffer rec = new StringBuffer(); while ((length = in.read(flush)) != -1) { rec.append(new String(flush, 0, length, SERVERENCODEING));//以服务端编码标准发送 } System.out.println("客户端收到回复:" + rec.toString()); in.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } finally { close(socket, in, out); } } private static void close(Socket socket, InputStream inputStream, OutputStream outputStream) throws IOException { if (outputStream != null) { outputStream.close(); } if (inputStream != null) { inputStream.close(); } if (socket != null) { socket.close(); } }
列几点需要注意的点:
1、out.close();任何的输入流或输出流的close()都会造成Socket关闭 但是不关闭又导致服务端无法接收到-1
2、使用socket.shutdownOutput();来通知服务端发送完成
3、write后记得flush
4、关闭流