《Java网络编程》读书笔记(二)socket编程

来源:互联网 发布:mac 输入法 emoji表情 编辑:程序博客网 时间:2024/04/30 05:28

根据所做的项目,以及最近查阅的一些资料,对socket相关的知识做一个总结!

我们先来看看一些跟socket相关的基本知识,可能还有点小小的扩展:

1. 长连接、短连接

长连接:连接一直保持着(需要用心跳机制保持连接);比较适合应用于操作频繁、点对点、连接数不多的实际场景(据说QQ可以同时2亿用户同时在线,而每个在线的用户需要维持一个TCP连接,这样资源会不会消耗很大,我很想知道他们的集群有多大)

短连接:当传输完成后就关闭连接;适合于操作不频繁(http服务)

2. socket中的基本操作

客户端:

(1)第一步:创建套接字,这不仅仅是一个对象,它会在网络上建立连接。Socket socket = new Socket ("hostname",port);
一般定情况下,这行代码被包装在一个try块中。  在Java 6之前(包括)之前的版本中需要在finally块中显示的关闭socket,Java7可以使用try-with-resource,这样就会自动释放了(Autocloseable);

try(Socket socket = new Socket("hostname", port)) {<span style="white-space:pre"></span>//通过socket从服务器接受数据或发送数据} catch (IOException e) {<span style="white-space:pre"></span>// TODO: handle exception}
(2)第二步:设置参数(一定时间如果没有连接服务器,抛出异常)。socket.setSoTimeout(15000);

(3)第三步:读取字节数据。一旦打开了socket,则可以通过socket.getInputStream()来获取InputStream,我们可以将其包装,《Java网络编程》读书笔记(一)详细介绍了Java I/O类库的结构,可以选择适合自己的Reader注意:在构造Reader时,可以选择字节的读取格式,例如:ASCII、Unicode等。

BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));StringBuilder timeBuilder = new StringBuilder();for (int c = reader.read(); c != -1; c = reader.read())<span style="white-space:pre"></span>timeBuilder.append((char)c);//read()读取的是一个字节数据,但是会返回一个int(0~255);流的结束通过返回-1来表示

(4)第四步:发送数据。同上一步类似

Writer out = new OutputStreamWriter(connection.getOutputStream());Date now = new Date();System.out.println("Start sending daytime to " + connection.getInetAddress());out.write(now.toString() + "\r\n");out.flush();//这是为了确保在缓冲区里的剩余数据可以发送,不然可能会造成数据不完整!

(5)第五步:一般情况下,数据读取完之后就可以关闭连接了。如果想保持连接持续(毕竟连接是很消耗资源的操作),则需要采用心跳机制,不停地向服务器发送消息(也会造成资源浪费)!

上面5个步骤是一个客户端基本的流程,每一个步骤只是举了一些简单的例子,还有许多内容!下面在扩充一点:

(1)半关闭Socket:调用close()方法会同时关闭输入和输出。我们可以可以选择只关闭一个,shutdownInput(isInputShutdown)、shutdownOutput(isOutputShutdown),当关闭之后在进行相关的操作则会抛出异常!特别注意:即使半关闭连接、或者两边都关闭了,结束后还是需要关闭socket。(要注意区分isConnected、isClosed)。

(2)Socket有8个构造函数(Java 6中),其中两个已过时,常用的也就一两个,如果有需要可以查API,不在这里啰嗦了!

(3)关于isConnected,这个方法会指出Socket是否从未连接过一个远程主机,如果一个socket确实能够连接远程主机、即使是关闭依旧会返回true。

要判断当前socket是否打开,需要检查两个条件:boolean connected = socket.isConnected() && ! socket.isClosed();

(4)Socket还有8个选项参数可供选择,我觉得这位老师写的Blog挺好:Java Socket 几个重要的TCP/IP选项解析(一)

服务端:

(1)服务器程序的生命周期:

1)使用一个ServerSocket()构造函数在特定端口创建一个新的ServerSocket;

2)ServerSocket使用其accept()方法监听这个端口的连接请求。accept会一直阻塞直到一个客户端尝试建立连接,此时accept将返回一个连接客户端和服务器端的Socket对象;

3)根据具体需求,调用Socket的getInputStream或者getOutputStream方法,获得与客户端通信的输入与输出流;

4)数据交互;

5)服务器或者客户端关闭连接

6)返回accept继续监听端口(不能说返回,一般会启动一个新线程来处理一个连接,关闭之后也就处理完了,accept一直处于监听状态)

(2)创建服务器Socket:ServerSocket server = new ServerSocket(PORT)

(3)开始监听:Socket connection = server.accept();

(4)判断ServerSocket是否打开:server.isBound() && !server.isClosed()

(5)日志:记录错误信息和请求信息private final static Logger errorLogger = Logger.getLogger("request");

3. socket的长连接和短连接

其实每个socket只是一个TCP(或者UDP)连接,长连接和短连接则是靠程序来实现的。当连接建立之后,双方都不断开、并且保持一定的心跳,则是长连接;当连接后、完成数据发送或者接受后就断开连接,则表示是短连接。短连接很好实现,只要执行完程序关闭连接就好,下面是一个Daytime获取的例子,客户端从服务端获取时间。服务端:

package my_server;import java.io.IOException;import java.io.OutputStreamWriter;import java.io.Writer;import java.net.ServerSocket;import java.net.Socket;import java.util.Date;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class DaytimeServer {public final static int PORT = 13;public static void main(String[] args) {//用线程池来管理和运行多线程的并发执行ExecutorService executor = Executors.newFixedThreadPool(10);try (ServerSocket server = new ServerSocket(PORT)){System.out.println("Start listening port 13...");//这个循环是为了保持服务端的服务状态,主线程一直在监听是否有连接请求!while (true) {try {Socket connection = server.accept();//当有一个连接请求,则创建一个新的线程来处理!executor.execute(new DaytimeTask(connection));} catch (IOException e) {}}} catch (IOException e) {}}private static class DaytimeTask implements Runnable {private Socket connection;public DaytimeTask(Socket connection) {this.connection = connection;}@Overridepublic void run() {try {Writer out = new OutputStreamWriter(connection.getOutputStream());Date now = new Date();System.out.println("Start sending daytime to " + connection.getInetAddress());out.write(now.toString() + "\r\n");out.flush();System.out.println("Send successfull.");} catch (IOException e) {e.printStackTrace();} finally {//在finally块中关闭连接try {connection.close();} catch (IOException e) {}}}}}
客户端:

package my_server;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.Socket;public class MyDaytimeClient {public static void main(String[] args) {String hostname = "localhost";Socket client = null;try {client = new Socket(hostname, 13);client.setSoTimeout(15000);BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));StringBuilder timeBuilder = new StringBuilder();for (int c = reader.read(); c != -1; c = reader.read())timeBuilder.append((char)c);System.out.println(timeBuilder);} catch (IOException e) { e.printStackTrace(); }finally {if(client != null) {try {client.close();} catch (IOException e) { e.printStackTrace(); }}}}}
短连接,只要一方关闭连接,则连接肯定断开!

长连接则情况复杂一些。只是server和client两端都不关闭连接还不够,如果长时间没有数据传递则会断开连接(因为网络情况很复杂,很有可能网络已经断开,但是上层应用是不知道的),因此需要心跳机制来检测网络是否连接。

4. 我理解的"同步、异步、阻塞和非阻塞"

网上关于这个主题的有许多讨论,但总觉得不是很透彻,看了这一篇,觉得清晰了很多,也有了自己的理解!

IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)

这篇博文里面的一个自足点很好:"这其实是因为不同的人的知识背景不同,并且在讨论这个问题的时候上下文(context)也不相同",上面博主所讨论的是IO里面的概念,我们尝试着分析一下宏观的概念!

描述的对象不同(我的理解)
(1)同步、异步其实可以理解为一种"通信方式",例如:在I/O系统中,我们说"需要异步处理某个对象(数据)";再比如说在多线程中,我们需要同步某个共享资源。
(2)阻塞和非阻塞其实就是某个线程的状态,在不同的上下文中,造成阻塞的因素有许多!大部分情况下同步会造成一个线程的阻塞(为什么说大部分,是因为我还没发现同步操作下不会阻塞的情况,但是有不敢打包票!)
(3)异步是一个独特模式(比较高冷),需要系统主动通知调用它的线程:我已经完成了,你可以来取了!异步更像是一个领导告诉你:我需要什么什么,你给我准备好,准备好了通知我一声(毕竟领导很忙)。

杂谈:Java中的NIO充分体现了非阻塞的概念,可以好好学下下,另外高性能服务器中也会用到非阻塞模式(用一个线程同时处理多个连接)

0 0
原创粉丝点击