java socket详解,java中socket编程入门实例

  java socket详解,java中socket编程入门实例

  00-1010简介1、Socket整体结构2、初始化3、连接服务器4、Socket常用设置参数4.1、setTcpNoDelay4.2、setSoLinger4.3、setOOBInline4.4、setSoTimeout4.5、setSendBufferSize4.6、SetReceiveBufferSize4.7、setKeepAlive4.8、setReuseAddress5、摘要

  

目录

Socket的中文翻译叫Socket。可能很多工作了四五年的同学都没用过这个API,但是无论什么时候用这个API,一定是在重要项目的核心代码里。

 

  通常,人们基本上使用各种开源的rpc框架,如Dubbo、gRPC、Spring Cloud等。而且很少需要打手写的网络电话。以下三节可以帮助你补充这一节的内容,在你真正需要的时候可以作为手册示例。

  本文和《ServerSocket 源码及面试题》主要讲Socket和ServerSocket的源代码,《工作实战:Socket 结合线程池的使用》这一章主要讲在实际工作中如何实现这两个API。

  

引导语

插座的结构很简单。Socket就像一个外壳,包裹了套接字初始化、连接创建等各种操作。它的底层实现全部由SocketImpl实现,Socket本身的业务逻辑非常简单。

 

  Socket的属性不多,包括socket status,SocketImpl,读写状态等。源代码如下:

  改变插座状态有相应的操作方法。例如,新创建套接字(createImpl方法)后,状态将更改为created=true,连接后,状态将更改为connect=true,以此类推。

  

1、Socket 整体结构

Socket的构造器有很多,可以分为两类:

 

  指定代理类型(Proxy)以创建套接字。有三种:DIRECT(直接连接)、HTTP(HTTP和FTP高层协议的代理)和Socks (Sockets代理)。三种不同的代码方法对应的SocketImpl是不同的,分别是:PlainSocketImpl、HttpConnectSocketImpl和SocksSocketImpl。除了类型之外,代理还指定地址和端口。

  创建默认的SocksSocketImpl,需要在构造函数中传递地址和端口。源代码如下:

  //address代表IP地址,port代表socket的端口。//地址我们一般用InetSocketAddress,有InetSocketAddress的初始化方法,比如ip端口,域名端口,InetAddress等。公共套接字(inetaddress,int port)引发io异常{this (address!=null?new InetSocketAddress(地址,端口): null,(SocketAddress) null,true);}这里的地址可以是ip地址,也可以是域名,比如127.0.0.1或者www.wenhe.com。

  我们来看看这个构造函数调用的这个底层构造函数的源代码:

  //流为真的时候是流套接字,使用TCP协议,相对稳定可靠,但是占用大量资源。//流为false时是数据报套接字,使用UDP协议,不稳定。但是占用资源少。私有套接字(套接字地址address,套接字地址本地addr,布尔流)抛出io异常{ set impl();//向后兼容if (address==null)抛出新的NullPointerException();Try {//创建socket Create impl(stream);//如果ip地址不为空,则绑定地址if (localAddr!=null) //create、bind、connect也是原生方法bind(localAddr);连接(地址);} catch (IOException

  IllegalArgumentException SecurityException e) { try { close(); } catch (IOException ce) { e.addSuppressed(ce); } throw e; }}从源码中可以看出:

  在构造 Socket 的时候,你可以选择 TCP 或 UDP,默认是 TCP;如果构造 Socket 时,传入地址和端口,那么在构造的时候,就会尝试在此地址和端口上创建套接字;Socket 的无参构造器只会初始化 SocksSocketImpl,并不会和当前地址端口绑定,需要我们手动的调用 connect 方法,才能使用当前地址和端口;Socket 我们可以理解成网络沟通的语言层次的抽象,底层网络创建、连接和关闭,仍然是 TCP 或 UDP 本身网络协议指定的标准,Socket 只是使用 Java 语言做了一层封装,从而让我们更方便地使用。

 

  

3、connect 连接服务端

connect 方法主要用于 Socket 客户端连接上服务端,如果底层是 TCP 层协议的话,就是通过三次握手和服务端建立连接,为客户端和服务端之间的通信做好准备,底层源码如下:

 

  

public void connect(SocketAddress endpoint, int timeout) throws IOException {}

connect 方法要求有两个入参,第一个入参是 SocketAddress,表示服务端的地址,我们可以使用 InetSocketAddress 进行初始化,比如:new InetSocketAddress(www.wenhe.com, 2000)。

 

  第二入参是超时时间的意思(单位毫秒),表示客户端连接服务端的最大等待时间,如果超过当前等待时间,仍然没有成功建立连接,抛 SocketTimeoutException 异常,如果是 0 的话,表示无限等待。

  

 

  

4、Socket 常用设置参数

Socket 的常用设置参数在 SocketOptions 类中都可以找到,接下来我们来一一分析下,以下理解大多来自类注释和网络。

 

  

 

  

4.1、setTcpNoDelay

此方法是用来设置 TCP_NODELAY 属性的,属性的注释是这样的:此设置仅仅对 TCP 生效,主要为了禁止使用 Nagle 算法,true 表示禁止使用,false 表示使用,默认是 false。

 

  对于 Nagle 算法,我们引用维基百科上的解释:

  纳格算法是以减少数据包发送量来增进 [TCP/IP] 网络的性能,它由约翰·纳格任职于Ford Aerospace时命名。

  纳格的文件[注 1]描述了他所谓的小数据包问题-某个应用程序不断地提交小单位的数据,且某些常只占1字节大小。因为TCP数据包具有40字节的标头信息(TCP与IPv4各占20字节),这导致了41字节大小的数据包只有1字节的可用信息,造成庞大的浪费。这种状况常常发生于Telnet工作阶段-大部分的键盘操作会产生1字节的数据并马上提交。更糟的是,在慢速的网络连线下,这类的数据包会大量地在同一时点传输,造成壅塞碰撞。

  纳格算法的工作方式是合并(coalescing)一定数量的输出数据后一次提交。特别的是,只要有已提交的数据包尚未确认,发送者会持续缓冲数据包,直到累积一定数量的数据才提交。

  总结算法开启关闭的场景:

  如果 Nagle 算法关闭,对于小数据包,比如一次鼠标移动,点击,客户端都会立马和服务端交互,实时响应度非常高,但频繁的通信却很占用不少网络资源;如果 Nagle 算法开启,算法会自动合并小数据包,等到达到一定大小(MSS)后,才会和服务端交互,优点是减少了通信次数,缺点是实时响应度会低一些。

  Socket 创建时,默认是开启 Nagle 算法的,可以根据实时性要求来选择是否关闭 Nagle 算法。

  

 

  

4.2、setSoLinger

setSoLinger 方法主要用来设置 SO_LINGER 属性值的。

 

  注释上大概是这个意思:在我们调用 close 方法时,默认是直接返回的,但如果给 SO_LINGER 赋值,就会阻塞 close 方法,在 SO_LINGER 时间内,等待通信双方发送数据,如果时间过了,还未结束,将发送 TCP RST 强制关闭 TCP 。

  我们看一下 setSoLinger 源码:

  

// on 为 false,表示不启用延时关闭,true 的话表示启用延时关闭// linger 为延时的时间,单位秒public void setSoLinger(boolean on, int linger) throws SocketException { // 检查是否已经关闭 if (isClosed()) throw new SocketException("Socket is closed"); // 不启用延时关闭 if (!on) { getImpl().setOption(SocketOptions.SO_LINGER, new Boolean(on)); // 启用延时关闭,如果 linger 为 0,那么会立即关闭 // linger 最大为 65535 秒,约 18 小时 } else { if (linger < 0) { throw new IllegalArgumentException("invalid value for SO_LINGER"); } if (linger > 65535) linger = 65535; getImpl().setOption(SocketOptions.SO_LINGER, new Integer(linger)); }}

 

  

4.3、setOOBInline

setOOBInline 方法主要使用设置 SO_OOBINLINE 属性。

 

  注释上说:如果希望接受 TCP urgent data(TCP 紧急数据)的话,可以开启该选项,默认该选项是关闭的,我们可以通过 Socket#sendUrgentData 方法来发送紧急数据。

  查询了很多资料,都建议尽可能的去避免设置该值,禁止使用 TCP 紧急数据。

  

 

  

4.4、setSoTimeout

setSoTimeout 方法主要是用来设置 SO_TIMEOUT 属性的。

 

  注释上说:用来设置阻塞操作的超时时间,阻塞操作主要有:

  ServerSocket.accept() 服务器等待客户端的连接;SocketInputStream.read() 客户端或服务端读取输入超时;DatagramSocket.receive()。我们必须在必须在阻塞操作之前设置该选项, 如果时间到了,操作仍然在阻塞,会抛出 InterruptedIOException 异常(Socket 会抛出 SocketTimeoutException 异常,不同的套接字抛出的异常可能不同)。

  对于 Socket 来说,超时时间如果设置成 0,表示没有超时时间,阻塞时会无限等待。

  

 

  

4.5、setSendBufferSize

setSendBufferSize 方法主要用于设置 SO_SNDBUF 属性的,入参是 int 类型,表示设置发送端(输出端)的缓冲区的大小,单位是字节。

 

  入参 size 必须大于 0,否则会抛出 IllegalArgumentException 异常。

  一般我们都是采取默认的,如果值设置太小,很有可能导致网络交互过于频繁,如果值设置太大,那么交互变少,实时性就会变低。

  

 

  

4.6、setReceiveBufferSize

setReceiveBufferSize 方法主要用来设置 SO_RCVBUF 属性的,入参是 int 类型,表示设置接收端的缓冲区的大小,单位是字节。

 

  入参 size 必须大于 0,否则会抛出 IllegalArgumentException 异常。

  一般来说,在套接字建立连接之后,我们可以随意修改窗口大小,但是当窗口大小大于 64k 时,需要注意:

  必须在 Socket 连接客户端之前设置缓冲值;必须在 ServerSocket 绑定本地地址之前设置缓冲值。

  

 

  

4.7、setKeepAlive

setKeepAlive 方法主要用来设置 SO_KEEPALIVE 属性,主要是用来探测服务端的套接字是否还是存活状态,默认设置是 false,不会触发这个功能。

 

  如果 SO_KEEPALIVE 开启的话,TCP 自动触发功能:如果两小时内,客户端和服务端的套接字之间没有任何通信,TCP 会自动发送 keepalive 探测给对方,对方必须响应这个探测(假设是客户端发送给服务端),预测有三种情况:

  服务端使用预期的 ACK 回复,说明一切正常;服务端回复 RST,表示服务端处于死机或者重启状态,终止连接;没有得到服务端的响应(会尝试多次),表示套接字已经关闭了。

  

 

  

4.8、setReuseAddress

setReuseAddress 方法主要用来设置 SO_REUSEADDR 属性,入参是布尔值,默认是 false。

 

  套接字在关闭之后,会等待一段时间之后才会真正的关闭,如果此时有新的套接字前来绑定同样的地址和端口时,如果 setReuseAddress 为 true 的话,就可以绑定成功,否则绑定失败。

  

 

  

5、总结

如果平时一直在做业务代码,Socket 可能用到的很少,但面试问到网络协议时,或者以后有机会做做中间件的时候,就会有大概率会接触到 Socket,所以多学学,作为知识储备也蛮好的。

 

  以上就是Java编程Socket结构常用参数设置源码及面试题的详细内容,更多关于Java编程Socket结构常用参数面试的资料请关注盛行IT其它相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: