Socket

Socket的作用

在Internet上,数据按有限大小的包传输,这些包称为数据报。每个数据报包含一个首部和主体。首部包含目标地址和端口、源地址和端口、检测数据是否被破坏和校验、以及用于保证可靠传输的各种信息;主体即数据本身。由于数据报长度有限,通常必须将数据分解为多个包,再在目的地重新组合,也有可能一个包或多个包在传输过程中丢失或遭到破坏,需要重传;或者包乱序到达需要重新排序。所有这些工作Socket都为我们屏蔽了,掩盖了网络的底层细节,如错误检测、包大小、包分解、包重传、网络地址等。

客户端Socket

Socket相关API

  • 创建连接的Socket
    public Socket(String host, int port) throws UnknownHostException, IOException
    public Socket(InetAddress address, int port) throws IOException
  • Socket构造函数指定要连接的主机和端口:主机可以指定为InetAddress或String;端口指定为1~65535之间的int值。
  • 这些构造函数会创建连接的Socket,也就是说在构造函数返回之前,会与远程主机建立一个活动的网络连接。所以这个构造函数不只是创建Socket对象,还会尝试连接远程主机的Socket。如果出于某种原因未能打开连接,则会抛出异常。如果域名服务器无法解析这个主机名或域名服务器没有运行,则会抛出UnknownHostException;如果出于其它原因未能打开Socket,则会抛出IOException。
  • 创建未连接的Socket
    public Socket()
    public Socket(Proxy proxy)
    protected Socket(SocketImpl impl)

这些构造函数会创建未连接的Socket,对于底层Socket的行为提供了更多的控制,例如可以选择一个不同的代理服务器或者一个加密机制。

  • 选择从哪个本地接口连接
    public Socket(String host, int port, InetAddress localAddr,int localPort) throws IOException
    public Socket(InetAddress address, int port, InetAddress localAddr,int localPort) throws IOException

这些构造函数可以指定要连接的远程主机和端口,以及从哪个本地接口和端口连接的。前两个参数表示远程主机和端口号,后两个参数表示本地主机(本地网络接口)和端口号。本地网络接口可以是物理接口(一个以太网卡),也可以是虚拟接口(一个有多个IP地址的主机)。如果localPort参数传入0,Java会随机选择1024~65535之间的可用端口。

  • 构造连接分解
    public Socket()
    public void connect(SocketAddress endpoint) throws IOException
    public void connect(SocketAddress endpoint, int timeout) throws IOException

通常我们在创建一个Socket对象的同时都会打开与一个远程主机的连接,但是我们可能需要在进行网络连接之前设置Socket。所以当使用无参的构造函数时,就没有指定的主机可以连接,然后通过构造一个SocketAddress设置远程主机,最后调用connect()方法实现网络连接。

try {
       Socket socket = new Socket();
       // 这里可以配置Socket选项
       SocketAddress address = new InetSocketAddress("www.baidu.com", 80);
       socket.connect(address);
       // 这里可以开始使用Socket
} catch (IOException e) {
       e.printStackTrace();
}
  • 获取客户端Socket信息的方法
    public InetAddress getInetAddress()
    public int getPort()
    public InetAddress getLocalAddress()
    public int getLocalPort()
  • 前两个方法获取Socket连接到的远程主机和端口号,或者如果现在连接是关闭的,则获取到的是Socket最近一次连接的远程主机和端口号。后两个方法获取Socket连接的源主机地址和端口号。
  • 远程端口(相对于客户端Socket而言)通常是由一个标准委员会预先分配的“已知端口”,而本地端口和远程端口不同,通常是由系统在运行时,从未使用的空闲端口号中选择随机分配的,因此系统中的多个不同的客户端就可以同时访问相同的服务。

public boolean isConnected()
public boolean isClosed()
public boolean isBound()

  • isConnected():并不指示Socket当前是否连接到一个远程主机,实际上表示Socket是否从未连接过一个远程主机;如果这个Socket确实连接过一个远程主机,不管出于连接状态还是关闭状态,都返回true。
  • isClosed():Socket关闭则返回true,否则返回false;如果Socket从一开始从未连接也返回false。
  • isBound():指出Socket是否成功绑定到本地系统上的出站端口,注意是本地客户端。
// 查看当前一个Socket是否打开处于连接状态
boolean connected = socket.isConnected() && !socket.isClosed();

SocketAddress

SocketAddress类的主要用途是为暂时的Socket连接信息(IP地址和端口号)提供一个存储结构,即使最初的Socket已经断开并被垃圾回收,这些信息也可以重新用来创建Socket。其具体实现类为InetSocketAddress。

  • 基本构造方法
    public InetSocketAddress(int port)
    public InetSocketAddress(String hostname, int port)
    public InetSocketAddress(InetAddress addr, int port)

对于客户端来说:使用一个主机和端口号来创建InetSocketAddress
对于服务端来说:使用一个端口号来创建InetSocketAddress

  • 获取InetSocketAddress信息的方法
    public final InetAddress getAddress()
    public final String getHostName()
    public final int getPort()

  • Socket类提供的获取SocketAddress的方法
    public SocketAddress getRemoteSocketAddress()
    public SocketAddress getLocalSocketAddress()

设置代理服务器

public Socket(Proxy proxy)
public Proxy(Type type, SocketAddress sa)

  • Proxy.Type.SOCKS:是Java理解的唯一一种底层代理类型
  • Proxy.Type.HTTP:作用于应用层而不是传输层
  • Proxy.Type.DIRECT:表示无代理连接
// 创建代理服务器的SocketAddress
SocketAddress proxyAddress = new InetSocketAddress("myproxy.example.com", 1080);
Proxy proxy = new Proxy(Proxy.Type.DIRECT, proxyAddress);
// 构造代理服务器的Socket
Socket socket = new Socket(proxy);
// 创建远程主机的SocketAddress
SocketAddress remoteAddress = new InetSocketAddress("login.ibiblio.org", 25);
socket.connect(remoteAddress);

设置Socket选项

Socket选项指定了Socket类所依赖的原生Socket如何发送和接受数据,对于客户端Socket,Java支持9个选项:

  • TCP_NODELAY
  • SO_LINGER
  • SO_TIMEOUT
  • SO_RCVBUF
  • SO_SNDBUF
  • SO_KEEPALIVE
  • OOBINLINE
  • SO_REUSEADDR
  • IP_TOS
  1. TCP_NODELAY
    public void setTcpNoDelay(boolean on) throws SocketException

设置为true可确保包会尽可能快地发送而不管包的大小。如果这个属性为flase,在正常情况下,小数据包(一字节)在发送前会组合为更大的包,在发送下一个包之前,本地客户端需要等待远程服务器对前一个包的确认响应信息,即Nagle算法,这个算法的问题是,如果远程服务器没有足够快地将确认响应信息发送回本地客户端,那么依赖于小数据量信息稳定传输的应用程序会变得很慢。例如在游戏应用程序中,服务器需要实时跟踪客户端鼠标的移动,Nagle算法会使得响应变慢导致移动卡顿。所以设置为true可以关闭这种缓冲模式,这样所有包一旦就绪就会立即发送。

  1. SO_LINGER:
    public void setSoLinger(boolean on, int linger) throws SocketException

该属性指定了Socket关闭时如何处理未发送的数据报。默认情况下,调用close()方法将立即返回,但系统仍会尝试发送剩余的数据。如果设置为false且延迟时间设置为0,那么当Socket关闭时,所有未发送的数据包都将被丢弃;如果设置为true且延迟时间设置为任意正数,close()方法会阻塞(阻塞时间为指定的秒数),等待发送数据和接受确认,当阻塞时间结束时,Socket将会关闭并且所有剩余数据都不会发送,也不会收到确认。

  1. SO_TIMEOUT
    public synchronized void setSoTimeout(int timeout) throws SocketException

正常情况下,尝试从Socket读取数据时,调用read()方法会阻塞尽可能长的时间来得到足够的字节。设置SO_TIMEOUT可以确保阻塞的时间不会超过我们指定的时间,当时间到期时就会抛出一个InterruptedException异常,应该捕获这个异常。不过虽然抛出了异常,但Socket仍是连接的,虽然这个read()方法调用失败了,但还可以再次尝试读取该Socket。

  1. SO_RCVBUF和SO_SNDBUF
    public synchronized void setReceiveBufferSize(int size) throws SocketException
    public synchronized void setSendBufferSize(int size) throws SocketException

一般来讲,如果发现应用不能充分利用可用带宽(例如有一个25Mbs/s的网络连接,但是数据传输速率仅为1.5Mbs/s),那么可以试着增加缓冲区大小;相反如果存在丢包和拥塞现象,则要减少缓冲区大小;不过在大多数情况下,除非网络在某个方向上负载过大,否则就直接使用默认值就行。

  1. SO_KEEPALIVE
    public void setKeepAlive(boolean on) throws SocketException

如果设置该属性为true,则客户端偶尔会通过一个空闲连接发送一个数据包以确保服务器未奔溃。如果服务器没能响应这个包,客户端会持续尝试11分钟多的时间,直到接收到响应为止,如果在12分钟内没接收到响应,客户端就关闭Socket。如果该属性设置为false,不活动的客户端可能会一直处于打开状态,而不会注意到服务器已经奔溃。

  1. OOBINLINE
    public void sendUrgentData (int data) throws IOException

TCP有一个可以发送单字节的紧急数据的特性。这个数据会立即发送,而且当接收方收到紧急数据时,会优先选择处理这个紧急数据。

public void setOOBInline(boolean on) throws SocketException

默认情况下,Java会忽略从Socket接收的紧急数据,不过如果你希望接收正常数据中的紧急数据,就需要设置该属性为true。一旦打开OOBInline,到达的任何紧急数据就将以正常方式放在Socket的输入流中等待读取。

  1. SO_REUSEADDR
    public void setReuseAddress(boolean on) throws SocketException

默认情况下,当Socket关闭时可能不会马上释放本地端口,有时会等待一小段时间,确保接收到所有要发送到这个端口的延迟数据包,所以当Socket关闭时这些数据包可能仍在网络上传输。如果使用随机端口,则为题不大,如果绑定到已知端口,就会阻止所有其它Socket同时使用这个端口。此时设置SO_REUSEADDR为true,就允许另一个Socket绑定到这个端口,即使此时仍有可能存在前一个Socket未接收到的数据。
使用时setReuseAddress()方法必须在为这个端口绑定新的Socket之前调用,之前连接的Socket和重用老地址的新Socket都必须设置为true才能生效。

  1. IP_TOS
    public void setTrafficClass(int tc) throws SocketException

该属性用来定性描述网络服务质量,IP规定了4种服务类型:

  • 低成本:发送成本低
  • 高可靠性:保证把数据可靠的送到目的地
  • 最高吞吐量:一次可以接收或者发送大批量的数据
  • 最小延迟:传输数据的速度快,把数据快速送达目的地

public void setPerformancePreferences(int connectionTime,int latency, int bandwidth)

该方法也可以设置网络服务质量,为连接时间、延迟、带宽指定相对优先级。例如:connectionTime=2,latency=1,bandwidth=3,那么最大带宽bandwidth是最重要的特性,最小延迟latency最不重要,连接时间居中。

服务端Socket

SercerSocket相关API

  • 基本构造函数
    public ServerSocket() throws IOException
    public ServerSocket(int port) throws IOException
    public ServerSocket(int port, int backlog) throws IOException
    public ServerSocket(int port, int backlog, InetAddress bindAddr)
  • 这些构造方法可以指定端口、保存入站连接请求所用的队列长度、要绑定的本地网络接口。如果试图将队列长度设置大于操作系统的最大队列长度,则会使用最大队列长度。
  • 默认情况下,如果一个主机有多个IP地址,服务器Socket会在所有IP地址的指定端口上监听,不过第三个参数可以要求只绑定某一个本地IP地址。
  • 如果传入的端口为0,则系统会随机选择可用的端口,这样的端口成为匿名端口。
  • 创建ServerSocket时如果抛出一个IOException异常,往往说明两点:一是可能有另一个服务器Socket已经占用了请求的端口;二是你试图连接1~1023范围内的某个端口,但没有root权限。
  • 构造连接分解
    public ServerSocket() throws IOException
    public void bind(SocketAddress endpoint) throws IOException
    public void bind(SocketAddress endpoint, int backlog) throws IOException

无参构造函数会创建一个ServerSocket对象,但未将它具体绑定到某个端口,所以不能接受任何网络连接,需要使用bind()方法进行绑定。这个特性的主要用途是,允许程序在绑定端口之前设置服务器Socket的属性,因为有些属性必须在服务器Socket连接前进行设置。

ServerSocket serverSocket = new ServerSocket();
// 这里可以设置服务器Socket属性...
SocketAddress address = new InetSocketAddress(80);
serverSocket.bind(address);
  • 获取服务端Socket信息的方法
    public InetAddress getInetAddress()
    public int getLocalPort()

getInetAddress():返回服务器的地址,如果一个主机有多个IP地址,则随机返回其中一个地址,如果没有绑定网络接口则返回null。
getLocalPort():返回服务器监听的端口号,如果没有绑定端口则返回-1。

设置Socket选项

Socket选项指定了ServerSocket类所依赖的原生Socket如何发送和接受数据,对于服务端Socket,Java支持3个选项:

  • SO_TIMEOUT
  • SO_REUSEADDR
  • SO_RCVBUF
  • 性能首选项
  1. SO_TIMEOUT
    public synchronized void setSoTimeout(int timeout) throws SocketException

该属性指定了accept()阻塞的时间,如果达到了指定的超时时间则会抛出SocketTimeoutException异常。如果设置为0则表示accept()方法永远不会超时,默认值就是0。一般很少设置这个TIMEOUT值,大多数服务器都设计为运行无限长的时间。

  1. SO_REUSEADDR
    public void setReuseAddress(boolean on) throws SocketException

该属性与客户端的SO_REUSEADDR非常类似,确定了是否允许一个新的Socket绑定到之前使用过的一个端口,即使可能还有一些发送到原Socket的数据正在网络上传输。

  1. SO_RCVBUF
    public synchronized void setReceiveBufferSize (int size) throws SocketException

该属性设置了服务器Socket接收的缓存区大小

  1. 设置性能首选项
    public void setPerformancePreferences(int connectionTime,int latency,int bandwidth)

不同的Internet服务有不同的性能需求,例如体育运动的直播视频需要相对较高的带宽;电影可能仍需要高带宽,但是可以接收较大的延迟;电子邮件可以通过低带宽的连接传输,甚至延迟几个小时都允许。为TCP定义了4个通用业务流类型:

  • 低成本
  • 高可靠性
  • 最大吞吐量
  • 最小延迟
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • 在客户端/服务器通信模式中,Socket是双方通信通道的抽象封装,用户可通过配置Socket的参数并构建Socke...
    桥头放牛娃阅读 4,710评论 1 11
  • 这一块属于网络编程,主要是学习TCP/IP四层的网络体系结构,学习TCP编程和UDP编程。 java.net中 一...
    取名废同学阅读 636评论 0 0
  • 对于即时类应用或者即时类的游戏,HTTP协议很多时候无法满足于我们的需求。这会,Socket对于我们来说就非常实用...
    育树凌峰阅读 1,970评论 0 1
  • 7.2 面向套接字编程我们已经通过了解Socket的接口,知其所以然,下面我们就将通过具体的案例,来熟悉Socke...
    lucas777阅读 1,166评论 0 2
  • 本文基于《Spring实战(第4版)》所写。 使用MongoDB持久化文档数据 将数据收集到一个非规范化(也就是文...
    阳光的技术小栈阅读 713评论 0 0