公司有本《Java网络编程》一直闲置在书架上,反正我对Socket方面不太懂,今天跟着书学习一番。
> 参考的优秀书籍
《Java网络编程》 --中国电力出版社
> 最简单的服务器端
当客户端连接进来,向客户端发送“welcome”以表咋程序员的亲切感~~
import java.io.IOException; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; public class SimpleServerSocket { public static void main(String[] args) { ServerSocket ss = null; Socket s = null; OutputStreamWriter osw = null; try { ss = new ServerSocket(9000); s = ss.accept(); osw = new OutputStreamWriter(s.getOutputStream()); osw.write("welcome..." + System.getProperty("line.separator")); osw.flush(); System.out.println("outputed."); } catch (IOException e) { System.out.println("socket exception."); // TODO Auto-generated catch block e.printStackTrace(); } finally { CloseableCloser.close(osw); CloseableCloser.close(s); CloseableCloser.close(ss); } } }
有许多资源需要关闭,那就写一个小的工具类来关闭吧
import java.io.Closeable; import java.io.IOException; public class CloseableCloser { public static void close(Closeable c) { if (c == null) { return; } try { c.close(); } catch (IOException e) { // TODO Auto-generated catch block System.out.println("exception when closing."); e.printStackTrace(); } } }
通过Linux telnet一下,看到反馈了。
当然用Windows telnet也一样的,只是Windows telnet跳了一个页面,不方便截图而已。
注意:如果服务端输出语句时没有加换行符,我在Linux、Windows测试时都没看到打印welcome哦。
telnet xx.xx.xx.xx 9000 Trying xx.xx.xx.xx... Connected to xx.xx.xx.xx. Escape character is '^]'. welcome... Connection closed by foreign host.
当然也可通过Java Socket编写客户端去连接
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import java.net.UnknownHostException; public class SimpleSocket { public static void main(String[] args) { Socket s = null; BufferedReader br = null; String line = null; try { s = new Socket("127.0.0.1", 9000); br = new BufferedReader(new InputStreamReader(s.getInputStream())); while ((line = br.readLine()) != null) { System.out.println(line); } } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { CloseableCloser.close(s); } } }
> 提供持续的服务,同时处理好不同的异常
看上面的服务端程序,可以发现它只能处理一个任务,而服务端一般来说是提供持续的服务的嘛,那么我们加一个while true呗。
另外,与客户端交互的Socket的异常和ServerSocket的异常是不是需要分开处理一下呢?试想,你一定不想某一个业务出现异常了,导致整个服务端的服务都中止的。
import java.io.IOException; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; public class SimpleServerSocket { public static void main(String[] args) { ServerSocket ss = null; Socket s = null; OutputStreamWriter osw = null; try { ss = new ServerSocket(9000); while (true) { try { s = ss.accept(); osw = new OutputStreamWriter(s.getOutputStream()); osw.write("welcome..." + System.getProperty("line.separator")); osw.flush(); System.out.println("outputed."); } catch (IOException e) { System.out.println("socket exception."); // TODO Auto-generated catch block e.printStackTrace(); } finally { CloseableCloser.close(osw); CloseableCloser.close(s); } } } catch (IOException e) { System.out.println("server socket exception."); // TODO Auto-generated catch block e.printStackTrace(); } finally { if (ss != null) { CloseableCloser.close(ss); } } } }
这样,你用多个客户端不断地连接,它都给你响应了。
> 并发地处理任务
上述的服务端代码有一段线程睡眠的代码用于模拟业务处理所需的时间的,我们把它的注解解开,然后用不同客户端连接,可以发现,程序在同一时间只能处理一个任务嘛。
而且,由于服务端同时只能处理一个请求,其他请求就堵塞了,操作系统会将请求同一端口的请求存储在一个先进先出的队列中,然而这个队列有长度限制。当然,这个限制各个操作系统不同。
不信,那么我们用Java多线程发送150个请求:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import java.net.UnknownHostException; public class MultipleSocket extends Thread { public static void main(String[] args) { for (int i = 0; i < 150; i++) { new MultipleSocket().start(); } } public void run() { Socket s = null; BufferedReader br = null; String line = null; try { System.out.println(this.getName() + " is started."); s = new Socket("127.0.0.1", 9000); br = new BufferedReader(new InputStreamReader(s.getInputStream())); while ((line = br.readLine()) != null) { System.out.println(line); } } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { CloseableCloser.close(br); CloseableCloser.close(s); } } }
不出意外,将报以下异常:
java.net.ConnectException: Connection refused: connect at java.net.DualStackPlainSocketImpl.connect0(Native Method) at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
那么我们就转为线程处理呗!
这里换为多线程处理,同时限制同时最多处理2个线程(需要限制几个线程数,自己设置哦)。
为什么要限制线程数量呢?如果同时许多了客户端连接,超过一定数量,最直接的结果就是内存耗尽了。
关于如何限制线程数量,可以参考以前的博文:【多线程】并发执行指定数量的线程
import java.io.IOException; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SimpleServerSocket { public static void main(String[] args) { ServerSocket ss = null; Socket s = null; OutputStreamWriter osw = null; ExecutorService es = Executors.newFixedThreadPool(2); try { ss = new ServerSocket(9000); while (true) { s = ss.accept(); es.execute(new BusinessThread(s)); } } catch (IOException e) { System.out.println("server socket exception."); // TODO Auto-generated catch block e.printStackTrace(); } finally { if (ss != null) { CloseableCloser.close(ss); } } } }
import java.io.IOException; import java.io.OutputStreamWriter; import java.net.Socket; import java.util.concurrent.TimeUnit; public class BusinessThread implements Runnable { Socket s = null; public BusinessThread(Socket s) { super(); this.s = s; } public void run() { OutputStreamWriter osw = null; try { osw = new OutputStreamWriter(s.getOutputStream()); osw.write("welcome..." + System.getProperty("line.separator")); osw.flush(); System.out.println("outputed."); // 模拟这里的业务进行得很慢 try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (IOException e) { System.out.println("socket exception."); // TODO Auto-generated catch block e.printStackTrace(); } finally { CloseableCloser.close(osw); CloseableCloser.close(s); } } }