360 likes | 540 Views
Java 网络编程精解. 第 2 章 Socket 用法详解. 2.1 构造 Socket 2.2 获取 Socket 的信息 2.3 关闭 Socket 2.4 半关闭 Socket 2.5 设置 Socket 的选项 2.6 发送邮件的 SMTP 客户程序 . 参考 《Java 网络编程精解 》 的第 2 章. 2.1 构造 Socket. Socket 的构造方法有以下几种重载形式: ( 1 ) Socket()
E N D
第2章 Socket用法详解 • 2.1 构造Socket • 2.2 获取Socket的信息 • 2.3 关闭Socket • 2.4 半关闭Socket • 2.5 设置Socket的选项 • 2.6 发送邮件的SMTP客户程序 参考《Java网络编程精解》的第2章
2.1 构造Socket • Socket的构造方法有以下几种重载形式: • (1)Socket() • (2)Socket(InetAddress address, int port)throws UnknownHostException,IOException • (3)Socket(InetAddress address, int port, InetAddress localAddr, int localPort)throws IOException • (4)Socket(String host, int port) throws UnknownHostException,IOException • (5)Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException
2.1.1 设定等待建立连接的超时时间 • 设定等待建立连接的超时时间 Socket socket=new Socket(); SocketAddress remoteAddr=new InetSocketAddress("localhost",8000); //等待建立连接的超时时间为1分钟 socket.connect(remoteAddr, 60000);
2.1.2 设定服务器的地址 • Socket(InetAddress address, int port) Socket(String host, int port) • InetAddress的用法如下: //返回本地主机的IP地址 InetAddress addr1=InetAddress.getLocalHost(); //返回代表"222.34.5.7"的IP地址 InetAddress addr2=InetAddress.getByName("222.34.5.7"); //返回域名为"www.javathinker.org"的IP地址 InetAddress addr3=InetAddress.getByName("www.javathinker.org");
2.1.3 设定客户端的地址 • 在一个Socket对象中,既包含远程服务器的IP地址和端口信息,也包含本地客户端的IP地址和端口信息。默认情况下,客户端的IP地址来自于客户程序所在的主机,客户端的端口则由操作系统随机分配。Socket类还有两个构造方法允许显式的设置客户端的IP地址和端口: • Socket(InetAddress address, int port, InetAddress localAddr, int localPort)throws IOException • Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException
2.1.4 客户连接服务器时可能抛出的异常 • 当Socket的构造方法请求连接服务器时,可能会抛出以下异常: • UnknownHostException:如果无法识别主机的名字或IP地址,就会抛出这种异常。 • ConnectException:如果没有服务器进程监听指定的端口,或者服务器进程拒绝连接,就会抛出这种异常。 • SocketTimeoutException:如果等待连接超时,就会抛出这种异常。 • BindException:如果无法把Socket对象与指定的本地IP地址或端口绑定,就会抛出这种异常。
2.1.4 客户连接服务器时可能抛出的异常 • 抛出UnknownHostException的情况:如果无法识别主机的名字或IP地址,就会抛出这种异常。 • 抛出ConnectException的情况: • 没有服务器进程监听指定的端口。 • 服务器进程拒绝连接。 • 抛出SocketTimeoutException的情况:如果客户端等待连接超时,就会抛出这种异常。 • 抛出BindException的情况:如果无法把Socket对象与指定的本地IP地址或端口绑定,就会抛出这种异常。
2.2 获取Socket的信息 • 以下方法用于获取Socket的有关信息: • getInetAddress():获得远程服务器的IP地址。 • getPort():获得远程服务器的端口。 • getLocalAddress():获得客户本地的IP地址。 • getLocalPort():获得客户本地的端口。 • getInputStream():获得输入流。如果Socket还没有连接,或者已经关闭,或者已经通过shutdownInput()方法关闭输入流,那么此方法会抛出IOException。 • getOutputStream():获得输出流。如果Socket还没有连接,或者已经关闭,或者已经通过shutdownOutput()方法关闭输出流,那么此方法会抛出IOException。
当客户与服务器的通信结束,应该及时关闭Socket,以释放Socket占用的包括端口在内的各种资源。Socket的close()方法负责关闭Socket。当客户与服务器的通信结束,应该及时关闭Socket,以释放Socket占用的包括端口在内的各种资源。Socket的close()方法负责关闭Socket。 Socket socket=null; try{ socket=new Socket("www.javathinker.org",80); //执行接收和发送数据的操作 … }catch(IOException e){ e.printStackTrace(); }finally{ try{ if(socket!=null)socket.close(); }catch(IOException e){e.printStackTrace();} } 2.3 关闭Socket
2.3 关闭Socket • Socket类提供了三个状态测试方法: • isClosed() • isConnected() • isBound() • 如果要判断一个Socket对象当前是否处于连接状态,可采用以下方式: String isConnected=socket.isConnected() && !socket.isClosed();
2.4 半关闭Socket • 有的时候,可能仅仅希望关闭输出流或输入流之一。此时可以采用Socket类提供的半关闭方法: • shutdownInput():关闭输入流。 • shutdownOutput(): 关闭输出流。
2.4 半关闭Socket • 先后调用Socket的shutdownInput()和shutdownOutput()方法,仅仅关闭了输入流和输出流,并不等价于调用Socket的close()方法。在通信结束后,仍然要调用Socket的close()方法,因为只有该方法才会释放Socket占用的资源,比如占用的本地端口等。 • Socket类还提供了两个状态测试方法,用来判断输入流和输出流是否关闭: • public boolean isInputShutdown() • public boolean isOutputShutdown()
2.5 设置Socket的选项 • TCP_NODELAY:表示立即发送数据。 • SO_RESUSEADDR:表示是否允许重用Socket所绑定的本地地址。 • SO_TIMEOUT:表示接收数据时的等待超时时间。 • SO_LINGER:表示当执行Socket的close()方法时,是否立即关闭底层的Socket。 • SO_SNFBUF:表示发送数据的缓冲区的大小。 • SO_RCVBUF:表示接收数据的缓冲区的大小。 • SO_KEEPALIVE:表示对于长时间处于空闲状态的Socket,是否要自动把它关闭。 • OOBINLINE:表示是否支持发送一个字节的TCP紧急数据。
2.5.1 TCP_NODELAY选项 • 设置该选项:public void setTcpNoDelay(boolean on) throws SocketException • 读取该选项:public boolean getTcpNoDelay() throws SocketException • TCP_NODEALY的默认值为false,表示采用Negale算法。如果调用setTcpNoDelay(true)方法,就会关闭Socket的缓冲,确保数据及时发送: if(!socket.getTcpNoDelay())socket.setTcpNoDelay(true); • 如果Socket的底层实现不支持TCP_NODELAY选项,那么getTcpNoDelay()和setTcpNoDelay()方法会抛出SocketException。
2.5.2 SO_RESUSEADDR选项 • 设置该选项:public void setResuseAddress(boolean on) throws SocketException • 读取该选项:public boolean getResuseAddress() throws SocketException • 为了确保一个进程关闭了Socket后,即使它还没释放端口,同一个主机上的其他进程还可以立刻重用该端口,可以调用Socket的setResuseAddress(true)方法: if(!socket.getResuseAddress())socket.setResuseAddress(true); • 值得注意的是socket.setResuseAddress(true)方法必须在Socket还没有绑定到一个本地端口之前调用,否则执行socket.setResuseAddress(true)方法无效。
2.5.3 SO_TIMEOUT选项 • 设置该选项:public void setSoTimeout(int milliseconds) throws SocketException • 读取该选项:public int getSoTimeOut() throws SocketException • 当通过Socket的输入流读数据时,如果还没有数据,就会等待。例Socket类的SO_TIMEOUT选项用于设定接收数据的等待超时时间,单位为毫秒,它的默认值为0,表示会无限等待,永远不会超时。
2.5.4 SO_LINGER选项 • 设置该选项:public void setSoLinger(boolean on, int seconds) throws SocketException • 读取该选项:public int getSoLinger() throws SocketException • SO_LINGER选项用来控制Socket关闭时的行为。 • socket.setSoLinger(true,0):执行Socket的close()方法时,该方法也会立即返回,但底层的Socket也会立即关闭,所有未发送完的剩余数据被丢弃。 • socket.setSoLinger(true,3600):执行Socket的close()方法时,该方法不会立即返回,而进入阻塞状态,同时,底层的Socket会尝试发送剩余的数据。只有满足以下两个条件之一,close()方法才返回: • 底层的Socket已经发送完所有的剩余数据。 • 尽管底层的Socket还没有发送完所有的剩余数据,但已经阻塞了3600秒。close()方法的阻塞时间超过3600秒,也会返回,剩余未发送的数据被丢弃。
2.5.5 SO_RCVBUF选项 • 设置该选项:public void setReceiveBufferSize(int size) throws SocketException • 读取该选项:public int getReceiveBufferSize() throws SocketException • SO_RCVBUF表示Socket的用于输入数据的缓冲区的大小。 • 如果底层Socket不支持SO_RCVBUF选项,那么setReceiveBufferSize()方法会抛出SocketException。
2.5.6 SO_SNDBUF选项 • 设置该选项:public void setSendBufferSize(int size) throws SocketException • 读取该选项:public int getSendBufferSize() throws SocketException • SO_SNDBUF表示Socket的用于输出数据的缓冲区的大小。 • 如果底层Socket不支持SO_SNDBUF选项,setSendBufferSize()方法会抛出SocketException。
2.5.7 SO_KEEPALIVE选项 • 设置该选项:public void setKeepAlive(boolean on) throws SocketException • 读取该选项:public int getKeepAlive() throws SocketException • 当SO_KEEPALIVE选项为true,表示底层的TCP实现会监视该连接是否有效。 • SO_KEEPALIVE选项的默认值为false,表示TCP不会监视连接是否有效,不活动的客户端可能会永久存在下去,而不会注意到服务器已经崩溃。
2.5.9 服务类型选项 • IP规定了四种服务类型,用来定性的描述服务的质量: • 低成本:发送成本低。 • 高可靠性:保证把数据可靠的送达目的地。 • 最高吞吐量:一次可以接收或发送大批量的数据。 • 最小延迟:传输数据的速度快,把数据快速送达目的地。
2.5.9 服务类型选项 • 这四种服务类型还可以进行组合,例如,可以同时要求获得高可靠性和最小延迟。Socket类中提供了设置和读取服务类型的方法: • 设置服务类型:public void setTrafficClass(int trafficClass) throws SocketException • 读取服务类型:public int getTrafficClass() throws SocketException • Socket类用四个整数表示服务类型: • 低成本:0x02 (二进制的倒数第二位为1) • 高可靠性:0x04(二进制的倒数第三位为1) • 最高吞吐量:0x08(二进制的倒数第四位为1) • 最小延迟:0x10(二进制的倒数第五位为1)
2.5.10 设定连接时间、延迟和带宽的相对重要性 public void setPerformancePreferences(int connectionTime,int latency,int bandwidth) • 以上方法的三个参数表示网络传输数据的三项指标: • 参数connectionTime:表示用最少时间建立连接。 • 参数latency:表示最小延迟。 • 参数bandwidth:表示最高带宽。 • setPerformancePreferences()方法用来设定这三项指标之间的相对重要性。可以为这些参数赋予任意的整数,这些整数之间的相对大小就决定了相应参数的相对重要性。例如,如果参数connectionTime为2,参数latency为1,而参数bandwidth为3,就表示最高带宽最重要,其次是最少连接时间,最后是最小延迟。
2.6 发送邮件的SMTP客户程序 • SMTP 协议(Simple Mail Transfer Protocol,简单邮件传输协议)是应用层的协议,建立在TCP/IP协议基础之上。SMTP协议规定了把邮件从发送方传输到接收方的规则。 • SMTP客户程序请求发送邮件,SMTP服务器负责把邮件传输到目的地。默认情况下,SMTP服务器监听25端口。在SMTP客户与SMTP服务器的一次会话过程中,SMTP客户会发送一系列SMTP命令,SMTP服务器则做出响应,返回相应的应答码,以及对应答码的描述。
2.6 发送邮件的SMTP客户程序 • MailSender类就是一个SMTP客户程序。它的sendMail()方法请求SMTP服务器发送一封邮件,发送过程如下: • (1)首先创建与SMTP服务器连接的Socket对象。 • (2)当连接成功,SMTP服务器就会返回一个应答码为220的响应,表示服务就绪。 • (3)接着sendMail()方法开始发送“HELO”、“MAIL FROM”、“RCPT TO”等命令,每条命令都按行发送,即以“\r\n”结束。每发送完一条命令后,都会等接收到了SMTP服务器的响应数据,然后再发送下一条命令。
练习题1 • 问题:对于以下程序代码: Socket socket=new Socket(); //第1行 SocketAddress remoteAddr1=new InetSocketAddress("localhost",8000); //第2行 SocketAddress remoteAddr2=new InetSocketAddress("localhost",8001); //第3行 socket.connect(remoteAddr1, 60000); //第4行 socket.connect(remoteAddr2, 60000); //第5行 下面哪些说法是正确的?(多选) • 选项: • a)以上程序代码可以顺利编译和运行通过。 • b)第1行程序代码创建了一个与本地匿名端口绑定的Socket对象。 • c) 第1行程序代码创建的Socket对象没有与任何服务器建立连接,并且没有绑定任何本地端口。 • d) 第5行程序代码会运行出错,因为一个Socket对象只允许建立一次连接。 • e) 第4行程序代码使Socket对象与一个服务器建立连接,并且绑定一个本地匿名端口。 • 答案: c,d,e
练习题2 • 问题: 当客户端执行以下程序代码时: Socket socket=new Socket("angel",80); 如果远程服务器angel不存在,会出现什么情况?(单选) • 选项: • a) 构造方法抛出UnknownHostException异常。 • b) 客户端一直等待连接,直到连接超时,从而抛出SocketTimeoutException。 • c) 抛出BindException。 • d) 构造方法返回一个Socket对象,但它不与任何服务器连接。 • 答案: a
练习题3 • 问题:Socket类的哪个方法返回Socket对象绑定的本地端口?(单选) • 选项: • a) getPort() • b) getLocalPort() • c) getRemotePort() • d) 不存在这样的方法,因为Socket对象绑定的本地端口对程序是透明的。 • 答案: b
练习题4 • 问题:以下两段程序代码是否等价?(单选) //第一段程序 socket.shutdownInput(); socket.shutdownOutput(); //第二段程序 socket.close(); • 选项: • a)等价 • b)不等价 • 答案: b
练习题5 • 问题:以下哪个选项设定Socket的接收数据时的等待超时时间?(单选) • 选项: • a) SO_LINGER • b) SO_RCVBUF • c) SO_KEEPALIVE • d) SO_TIMEOUT • 答案: d
练习题6 • 问题:如何判断一个Socket对象当前是否处于连接状态?(单选) • 选项: • a) boolean isConnected=socket.isConnected() && socket.isBound(); • b) boolean isConnected=socket.isConnected() && !socket.isClosed(); • c) boolean isConnected=socket.isConnected() && !socket.isBound(); • d) boolean isConnected=socket.isConnected(); • 答案: b
练习题7 • 问题:客户程序希望底层网络的IP层提供高可靠性和最小延迟传输服务,客户程序中应该如何提出这一请求?(单选) • 选项: • a)调用Socket的setPerformancePreferences()方法。 • b)设置Socket的SO_SERVICE选项。 • c)调用Socket的setTrafficClass()方法。 • d)客户程序无法提出这种请求,必须直接配置底层网络。 • 答案: c