`

Socket类选项介绍

阅读更多

最近在用Socket做一个文件传输系统,遇到一个奇怪的现象,接收端到了最后总是接收不到发送端的数据。经过一番努力,还是找到了原因。Socke的SO_SNDBUF默认为8K,尽管关闭了TCP_NODELAY,但还是要达到8K才发送出去。
  转载一篇Socket类选项的说明文章: http://java.chinaitlab.com/net/786337.html

  二、用于获得和设置Socket选项的getter和setter方法
    Socket选择可以指定Socket类发送和接受数据的方式。在JDK1.4中共有8个Socket选择可以设置。这8个选项都定义在java.net.SocketOptions接口中。定义如下:
    public final static int TCP_NODELAY = 0x0001;

    public final static int SO_REUSEADDR = 0x04;

    public final static int SO_LINGER = 0x0080;

    public final static int SO_TIMEOUT = 0x1006;

    public final static int SO_SNDBUF = 0x1001;

    public final static int SO_RCVBUF = 0x1002;

    public final static int SO_KEEPALIVE = 0x0008;

    public final static int SO_OOBINLINE = 0x1003;
    有趣的是,这8个选项除了第一个没在SO前缀外,其他7个选项都以SO作为前缀。其实这个SO就是Socket Option的缩写;因此,在Java中约定所有以SO为前缀的常量都表示Socket选项;当然,也有例外,如TCP_NODELAY.在Socket类中为每一个选项提供了一对get和set方法,分别用来获得和设置这些选项。
    1. TCP_NODELAY
public boolean getTcpNoDelay() throws SocketException
public void setTcpNoDelay(boolean on) throws SocketException
    在默认情况下,客户端向服务器发送数据时,会根据数据包的大小决定是否立即发送。当数据包中的数据很少时,如只有1个字节,而数据包的头却有几十个字节(IP头+TCP头)时,系统会在发送之前先将较小的包合并到软大的包后,一起将数据发送出去。在发送下一个数据包时,系统会等待服务器对前一个数据包的响应,当收到服务器的响应后,再发送下一个数据包,这就是所谓的Nagle算法;在默认情况下,Nagle算法是开启的。
    这种算法虽然可以有效地改善网络传输的效率,但对于网络速度比较慢,而且对实现性的要求比较高的情况下(如游戏、Telnet等),使用这种方式传输数据会使得客户端有明显的停顿现象。因此,最好的解决方案就是需要Nagle算法时就使用它,不需要时就关闭它。而使用setTcpToDelay正好可以满足这个需求。当使用setTcpNoDelay(true)将Nagle算法关闭后,客户端每发送一次数据,无论数据包的大小都会将这些数据发送出去。
    2.  SO_REUSEADDR
public boolean getReuseAddress() throws SocketException          
public void setReuseAddress(boolean on) throws SocketException
    通过这个选项,可以使多个Socket对象绑定在同一个端口上。其实这样做并没有多大意义,但当使用close方法关闭Socket连接后,Socket对象所绑定的端口并不一定马上释放;系统有时在Socket连接关闭才会再确认一下是否有因为延迟面未到达的数据包,这完全是在底层处理的,也就是说对用户是透明的;因此,在使用Socket类时完全不会感觉到。
    这种处理机制对于随机绑定端口的Socket对象没有什么影响,但对于绑定在固定端口的Socket对象就可能会抛出“Address already in use: JVM_Bind”例外。因此,使用这个选项可以避免个例外的发生。
package mynet;

import java.net.*;
import java.io.*;

public class Test
{
    public static void main(String[] args)
    {
        Socket socket1 = new Socket();
        Socket socket2 = new Socket();
        try
        {
            socket1.setReuseAddress(true);
            socket1.bind(new InetSocketAddress("127.0.0.1", 88));
            System.out.println("socket1.getReuseAddress():"
                    + socket1.getReuseAddress());
            socket2.bind(new InetSocketAddress("127.0.0.1", 88));
        }
        catch (Exception e)
        {
            System.out.println("error:" + e.getMessage());
            try
            {
                socket2.setReuseAddress(true);
                socket2.bind(new InetSocketAddress("127.0.0.1", 88));
                System.out.println("socket2.getReuseAddress():"
                        + socket2.getReuseAddress());
                System.out.println("端口88第二次绑定成功!");
            }
            catch (Exception e1)
            {
                System.out.println(e.getMessage());
            }
        }
    }
}
    上面的代码的运行结果如下:
socket1.getReuseAddress():true
error:Address already in use: JVM_Bind
socket2.getReuseAddress():true
端口88第二次绑定成功!
    使用SO_REUSEADDR选项时有两点需要注意:
    1.  必须在调用bind方法之前使用setReuseAddress方法来打开SO_REUSEADDR选项。因此,要想使用SO_REUSEADDR选项,就不能通过Socket类的构造方法来绑定端口。
    2.  必须将绑定同一个端口的所有的Socket对象的SO_REUSEADDR选项都打开才能起作用。如在例程4-12中,socket1和socket2都使用了setReuseAddress方法打开了各自的SO_REUSEADDR选项。

   3.  SO_LINGER
public int getSoLinger() throws SocketException
public void setSoLinger(boolean on, int linger) throws SocketException
    这个Socket选项可以影响close方法的行为。在默认情况下,当调用close方法后,将立即返回;如果这时仍然有未被送出的数据包,那么这些数据包将被丢弃。如果将linger参数设为一个正整数n时(n的值最大是65,535),在调用close方法后,将最多被阻塞n秒。在这n秒内,系统将尽量将未送出的数据包发送出去;如果超过了n秒,如果还有未发送的数据包,这些数据包将全部被丢弃;而close方法会立即返回。如果将linger设为0,和关闭SO_LINGER选项的作用是一样的。
    如果底层的Socket实现不支持SO_LINGER都会抛出SocketException例外。当给linger参数传递负数值时,setSoLinger还会抛出一个IllegalArgumentException例外。可以通过getSoLinger方法得到延迟关闭的时间,如果返回-1,则表明SO_LINGER是关闭的。例如,下面的代码将延迟关闭的时间设为1分钟:
if(socket.getSoLinger() == -1) socket.setSoLinger(true, 60);
    4.  SO_TIMEOUT
public int getSoTimeout() throws SocketException
public void setSoTimeout(int timeout) throws SocketException
    这个Socket选项在前面已经讨论过。可以通过这个选项来设置读取数据超时。当输入流的read方法被阻塞时,如果设置timeout(timeout的单位是毫秒),那么系统在等待了timeout毫秒后会抛出一个InterruptedIOException例外。在抛出例外后,输入流并未关闭,你可以继续通过read方法读取数据。
    如果将timeout设为0,就意味着read将会无限等待下去,直到服务端程序关闭这个Socket.这也是timeout的默认值。如下面的语句将读取数据超时设为30秒:
socket1.setSoTimeout(30 * 1000);
    当底层的Socket实现不支持SO_TIMEOUT选项时,这两个方法将抛出SocketException例外。不能将timeout设为负数,否则setSoTimeout方法将抛出IllegalArgumentException例外。
    5.  SO_SNDBUF
public int getSendBufferSize() throws SocketException
public void setSendBufferSize(int size) throws SocketException
    在默认情况下,输出流的发送缓冲区是8096个字节(8K)。这个值是Java所建议的输出缓冲区的大小。如果这个默认值不能满足要求,可以用setSendBufferSize方法来重新设置缓冲区的大小。但最好不要将输出缓冲区设得太小,否则会导致传输数据过于频繁,从而降低网络传输的效率。
    如果底层的Socket实现不支持SO_SENDBUF选项,这两个方法将会抛出SocketException例外。必须将size设为正整数,否则setSendBufferedSize方法将抛出IllegalArgumentException例外。
    6.  SO_RCVBUF
public int getReceiveBufferSize() throws SocketException
public void setReceiveBufferSize(int size) throws SocketException
    在默认情况下,输入流的接收缓冲区是8096个字节(8K)。这个值是Java所建议的输入缓冲区的大小。如果这个默认值不能满足要求,可以用setReceiveBufferSize方法来重新设置缓冲区的大小。但最好不要将输入缓冲区设得太小,否则会导致传输数据过于频繁,从而降低网络传输的效率。
    如果底层的Socket实现不支持SO_RCVBUF选项,这两个方法将会抛出SocketException例外。必须将size设为正整数,否则setReceiveBufferSize方法将抛出IllegalArgumentException例外。

7.  SO_KEEPALIVE
public boolean getKeepAlive() throws SocketException
public void setKeepAlive(boolean on) throws SocketException
    如果将这个Socket选项打开,客户端Socket每隔段的时间(大约两个小时)就会利用空闲的连接向服务器发送一个数据包。这个数据包并没有其它的作用,只是为了检测一下服务器是否仍处于活动状态。如果服务器未响应这个数据包,在大约11分钟后,客户端Socket再发送一个数据包,如果在12分钟内,服务器还没响应,那么客户端Socket将关闭。如果将Socket选项关闭,客户端Socket在服务器无效的情况下可能会长时间不会关闭。SO_KEEPALIVE选项在默认情况下是关闭的,可以使用如下的语句将这个SO_KEEPALIVE选项打开:
socket1.setKeepAlive(true);
    8.  SO_OOBINLINE
public boolean getOOBInline() throws SocketException
public void setOOBInline(boolean on) throws SocketException
    如果这个Socket选项打开,可以通过Socket类的sendUrgentData方法向服务器发送一个单字节的数据。这个单字节数据并不经过输出缓冲区,而是立即发出。虽然在客户端并不是使用OutputStream向服务器发送数据,但在服务端程序中这个单字节的数据是和其它的普通数据混在一起的。因此,在服务端程序中并不知道由客户端发过来的数据是由OutputStream还是由sendUrgentData发过来的。下面是sendUrgentData方法的声明:
public void sendUrgentData(int data) throws IOException
    虽然sendUrgentData的参数data是int类型,但只有这个int类型的低字节被发送,其它的三个字节被忽略。下面的代码演示了如何使用SO_OOBINLINE选项来发送单字节数据。
package mynet;

import java.net.*;
import java.io.*;

class Server
{
    public static void main(String[] args) throws Exception
    {
        ServerSocket serverSocket = new ServerSocket(1234);
        System.out.println("服务器已经启动,端口号:1234");
        while (true)
        {
            Socket socket = serverSocket.accept();
            socket.setOOBInline(true);
            InputStream in = socket.getInputStream();
            InputStreamReader inReader = new InputStreamReader(in);
            BufferedReader bReader = new BufferedReader(inReader);
            System.out.println(bReader.readLine());
            System.out.println(bReader.readLine());
            socket.close();
        }
    }
}
public class Client
{
    public static void main(String[] args) throws Exception
    {
        Socket socket = new Socket("127.0.0.1", 1234);
        socket.setOOBInline(true);
        OutputStream out = socket.getOutputStream();
        OutputStreamWriter outWriter = new OutputStreamWriter(out);
        outWriter.write(67);              // 向服务器发送字符"C"
        outWriter.write("hello world\r\n");
        socket.sendUrgentData(65);        // 向服务器发送字符"A"
        socket.sendUrgentData(322);        // 向服务器发送字符"B"
        outWriter.flush();
        socket.sendUrgentData(214);       // 向服务器发送汉字”中”
        socket.sendUrgentData(208);
        socket.sendUrgentData(185);       // 向服务器发送汉字”国”
        socket.sendUrgentData(250);
        socket.close();
    }
}
    由于运行上面的代码需要一个服务器类,因此,在加了一个类名为Server的服务器类,关于服务端套接字的使用方法将会在后面的文章中详细讨论。在类Server类中只使用了ServerSocket类的accept方法接收客户端的请求。并从客户端传来的数据中读取两行字符串,并显示在控制台上。

由于本例使用了127.0.0.1,因Server和Client类必须在同一台机器上运行。
    运行Server
java mynet.Server
    运行Client
java mynet.Client
    在服务端控制台的输出结果
服务器已经启动,端口号:1234
ABChello world
中国
    在ClienT类中使用了sendUrgentData方法向服务器发送了字符'A'(65)和'B'(66)。但发送'B'时实际发送的是322,由于sendUrgentData只发送整型数的低字节。因此,实际发送的是66.十进制整型322的二进制形式如图1所示。

图1  十进制整型322的二进制形式
    从图1可以看出,虽然322分布在了两个字节上,但它的低字节仍然是66.
    在Client类中使用flush将缓冲区中的数据发送到服务器。我们可以从输出结果发现一个问题,在Client类中先后向服务器发送了'C'、"hello world"r"n"、'A'、'B'.而在服务端程序的控制台上显示的却是ABChello world.这种现象说明使用sendUrgentData方法发送数据后,系统会立即将这些数据发送出去;而使用write发送数据,必须要使用flush方法才会真正发送数据。
    在Client类中向服务器发送"中国"字符串。由于"中"是由214和208两个字节组成的;而"国"是由185和250两个字节组成的;因此,可分别发送这四个字节来传送"中国"字符串。
    注意:在使用setOOBInline方法打开SO_OOBINLINE选项时要注意是必须在客户端和服务端程序同时使用setOOBInline方法打开这个选项,否则无法命名用sendUrgentData来发送数据。

http://anywaybean.iteye.com/blog/646927

分享到:
评论

相关推荐

    C#网络编程-socket类介绍

    此外,Socket类还支持套接字选项和错误处理,确保了网络编程的灵活性和可靠性。 总的来说,C#的网络编程主要是通过Socket类来实现的,它提供了一套全面的API,使得开发者能够方便地创建各种网络应用程序,包括但不...

    SOCKET类的设计和实现

    下面将详细介绍SOCKET类的基本设计思路。 ##### 类结构设计 1. **构造函数**:用于初始化WinSock库,并创建套接字。 2. **析构函数**:用于清理资源,如关闭套接字并释放WinSock库。 3. **成员函数**: - `...

    C# socket封装类和组件含例程全部开源

    8. **配置选项**:为了适应不同的网络环境和需求,封装类可能会提供一系列配置选项,比如连接超时时间、缓冲区大小等。 9. **性能优化**:经过优化的Socket封装可能采用了更高效的数据打包和解包策略,减少了内存...

    C# Socket封装类和组件源码

    3. **套接字选项**:Socket类还提供了设置各种套接字选项的方法,如`SetSocketOption()`,可以用来调整连接性能、错误处理等。 4. **Buffer管理**:在进行数据传输时,通常会用到缓冲区管理。源码可能包含了对缓冲...

    开源socket封装类SimpleSocket

    同时,熟悉网络编程的基本概念,如TCP连接、套接字选项、端口等,将有助于更好地利用SimpleSocket。 总之,开源的SimpleSocket是网络编程的一个优秀工具,它降低了Socket编程的门槛,提高了开发效率。通过理解和...

    检查linux系统支持的socket选项

    测试linux系统支持的socket设置选项

    cSharp-socket-class.zip_C# socket类_C# socket详解_C# socket 编程_c#

    6. **套接字选项和属性**:Socket类有许多属性和方法用于设置和查询套接字选项,例如NoDelay(TCP_NODELAY)可以禁用Nagle算法以减少延迟,KeepAlive则可以开启心跳检测。 7. **关闭和释放资源**:完成通信后,记得...

    C#Socket类学习要点

    - 可以使用`SetSocketOption`方法配置Socket选项。 - 使用`GetSocketOption`方法检索这些配置。 #### 三、代码示例 以下是一个简单的Socket连接示例代码: ```csharp using System; using System.Net; using ...

    linux 网络编程 socket选项的实现

    Linux网络编程中的Socket选项是控制套接字行为的关键机制,它们允许程序员调整网络通信的各种特性。本文将深入探讨Socket选项的实现,以及如何通过`getsockopt`和`setsockopt`函数来读取和设置这些选项。 `...

    socket的keepalive介绍

    设置Keepalive的函数通常包括`setsockopt()`,在套接字层(`SOL_SOCKET`)和TCP层(`SOL_TCP`)分别设置不同的选项。例如: ```c int keepalive = 1; // 开启Keepalive int keepidle = 60; // 60秒后开始探测 int ...

    socket选项说明

    ### Socket选项详解 #### 引言 在深入探讨Socket选项之前,我们先回顾一下Socket的基本概念。Socket,或称为套接字,是网络通信中的一种重要机制,它为应用程序提供了在不同主机之间发送和接收数据的能力。在前几...

    c# UDP组播的发送和接收(使用socket 类)

    在C#编程中,UDP(用户...总结,C#中实现UDP组播的关键在于正确配置Socket选项,加入组播组,并设置好发送和接收的过程。通过这种方式,你可以构建高效的多播通信系统,尤其适用于需要广播信息给多个接收者的应用场景。

    C++ SOCKET编程介绍

    在C++中,SOCKET编程涉及的主要头文件是`#include <winsock2.h>`(对于Windows平台)或`#include <sys/socket.h>`(对于类Unix系统),以及相应的库链接,如`ws2_32.lib`。 接下来,我们来了解C++中建立SOCKET的...

    server.rar_c++ Socket服务类_socket server

    - `socket.suo`:Visual Studio的用户选项文件,存储用户的设置和状态。 - `SOCKET`:可能是一个头文件,包含了Socket类的定义。 - `Debug`:目录,包含了调试版本的编译输出文件。 综上所述,这个资源提供了一...

    c socket pyton socket

    在Socket编程中,可以通过设置Socket选项来增强其功能,例如设置可重用地址选项(`SO_REUSEADDR`)以允许快速重启服务器。 ```c int re_use_addr = 1; int ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *...

    用于广播的SOCKET类(19KB)

    总结起来,"用于广播的SOCKET类"是一个编程技术,它涉及了网络通信的基础知识,包括IP多播、UDP协议、socket选项以及如何在编程环境中实现广播功能。了解并掌握这些概念,对于开发需要广播功能的网络应用程序至关...

    socket Demo

    4. **超时设置**:为了防止网络延迟或故障导致程序阻塞,封装类可能提供设置连接和读写超时的选项。 5. **状态管理**:封装类可能包含对连接状态的管理,如判断是否已连接、是否可读可写等。 6. **重连机制**:...

    socket telnet类

    本节将详细介绍标题中提到的"socket telnet类",以及与之相关的C++ socket编程知识。 首先,让我们来理解什么是socket。Socket是网络通信中的一个重要概念,它相当于一个端点,用于在网络中的两台计算机之间建立...

    C#Socket高并发_socket_socket并发_c#socket_C#_socket高并发_源码.zip

    分析源码可以帮助我们学习如何配置Socket选项、管理连接队列、处理异步事件以及优化性能。 总之,这个资源对于想要学习或提升C# Socket高并发编程能力的开发者来说是宝贵的。通过阅读和实践源码,你可以了解到如何...

    JAVA Socket 经典教程

    - **SOCKET选项**:Socket类的`setOption()`方法允许设置各种选项,如超时设置、是否启用套接字选项等。 - **NIO(非阻塞I/O)**:Java NIO库提供了非阻塞的Socket编程模型,适用于高并发场景,如`Selector`和`...

Global site tag (gtag.js) - Google Analytics