Socket笔记之客户端Socket

TCP与UDP

  1. TCP:传输控制协议。TCP是面向连接的通信协议,TCP提供两台计算机之间的可靠无差错的数据传输
  2. UDP:用户数据报协议。UDP是无连接通信,不保证可靠数据的传输,但能够向若干个目标传输数据,接收来自若干个源的数据。
  3. 利用UDP通信的两个程序是平等的,没有主次之分,两个程序的代码可以是一样的。利用TCP协议进行通信的两个应用程序是有主次之分的,一个称为服务器程序一个称为客户端程序,两者的功能的编写大不一样。

套接字

  1. Socket可以看成在两个程序进行通讯连接中的一个端点,一个程序将一段信息写入Socket中,该Socket将这段信息发送给另一个Socket中,使这段信息能传送到其他程序中。java中Socket代表了客户端流套接字。ServerSocket代表了服务程序流套接字
  2. SocketServerSocket的交互过程

客户端Socket

  1. 可通过SocketgetInputStream()方法从服务程序获得输入流读传送来的信息,也可通过SocketgetOutputStream()方法获得输出流来发送信息。

  2. 构造Socket,Socket(host, port)在创建后自动连接,如果连接成功就返回Socket对象,如果连接失败就抛出IOExceptionSocket()只是创建,需要手动连接。

    @Test
    public void testCreateAndConnect() {
        Socket socket = null;
        try {
            int port = 80;
            String host = "httpbin.org";
            //创建并自动连接到服务端
            socket = new Socket(host, 80);
            System.out.println("connect server success ,in port " + 80);
            //关闭连接
            socket.close();
    
            
            //创建
            socket = new Socket();
            SocketAddress socketAddress = new InetSocketAddress(host, port);
            //手动连接,并设置超时时间,如果在2s秒的时间内连接成功就返回,
            //如果在2s内出现其他异常则抛该异常,如果既没有抛出其他异常也没有连接成功则抛出SocketTimeoutException异常
            socket.connect(socketAddress, 2000);
            System.out.println("connect server success ,in port " + port);
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (ConnectException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

关闭Socket

  1. 当客户端与服务器的通信结束,应该及时关闭Socket,以释放Socket占用的包括端口在内的各种资源。Close()负责关闭Socket,当一个Socket对象被关闭,它不能再通过它的输入流和输出流进行IO操作。否则会导致IOException

  2. 检测方法

    isClosed():如果Socket已经连接到远程主机,并且还没有关闭,返回true,否则false
    isConnected:如果Socket曾经连接到远程主机,则返回true,否则返回false
    isBound():如果Socket已经与一个本地端口邦定,则返回true,否则返回false
    
  3. 如果判断一个Socket对象当前是否处于连接状态

    Boolean isConnected = socket.isConnected()&&!socket.isClosed();
    

异常

  1. 客户端连接服务器可能抛出的异常

    • UnknowHostException:如果无法识别主机的名字或IP,就会抛出这种异常
    • ConnectException:如果没有服务器进程监听指定的端口(指定ip地址的机器不能找到或者是该ip存在,但找不到指定的端口进行监听),或者服务器进程拒绝连接(Connection refused:connect),就会抛此异常
    • SocketTimeoutException:如果等待数据超时,就会抛出异常
    • BindException:如果无法把Socket对象与指定的本地IP地址或端口号绑定,就会抛异常
    • NoRouteToHostException:没有路由连接到主机,无法到达远程主机,
  2. ConnectException案例

    @Test
    public void testConnectException() throws Exception {
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress("127.0.0.1", 8888), 10000);
        System.out.println("socket连接成功....");
        Socket socket2 = new Socket();
        socket2.connect(new InetSocketAddress("127.0.0.1", 8888), 1);
        System.out.println("socket2连接成功....");
    }
    
  3. SocketTimeoutException案例

    //服务端
    public class SimpleServer {
        public static void main(String[] args) throws IOException, InterruptedException {
            ServerSocket serverSocket = new ServerSocket(8888, 1);
            LockSupport.park();
        }
    }
    @Test
    public void testSocketTimeoutException() throws Exception {
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress("127.0.0.1", 8888), 1);
        System.out.println("socket连接成功....");
    }
    
    @Test
    public void testReadTimeOut() {
        Socket socket = new Socket();
        long startTime = 0;
        try {
            socket.connect(new InetSocketAddress("127.0.0.1", 8888), 10000);
            System.out.println("socket连接成功....");
            //此处仅仅是设置了一个值,只有调用read()方法,超时时间才真正生效
            socket.setSoTimeout(2000);
            startTime = System.currentTimeMillis();
            int read = socket.getInputStream().read();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            long endTime = System.currentTimeMillis();
            System.out.println("执行时间:" + (endTime - startTime));
        }
    }
    


  4. BindException

    • 无法把Socket对象与指定的本地Ip地址或端口邦定,就会抛出此异常。
    • 使用netstat –an 查看Listening状态的端口,找到被占用的端口
  5. SocketException

    • Socket is closed
      • 该异常在客户端和服务器端均可能发生。异常的原因是己方主动关闭了连接后(调用了Socket的close方法)再对网络连接进行读写操作
    • Connection reset
      • Connection reset或者Socket write error:该异常在客户端和服务器端均有可能发生,引起该异常的原因有两个,第一个就是如果一端的Socket被关闭(或主动关闭或者因为异常退出而引起的关闭),另一端仍发送数据,发送的第一个数据包引发该异常(Connect reset by peer)。另一个是一端退出,但退出时并未关闭该连接,另一端如果在从连接中读数据则抛出该异常(Connection reset)。简单的说就是由连接断开后的读和写操作引起的。
      • Connection reset by peer:服务器的并发连接数已经超过了承载量,服务器会将一些连接关闭
    • Broken pipe
      • Broken pipe:该异常在客户端和服务器均有可能发生。在SocketException异常的第一种情况中(也就是抛出 SocketExcepton:Connect reset by peer:Socket write error后),如果再继续写数据则抛出该异常。前两个异常的解决方法是首先确保程序退出前关闭所有的网络连接,其次是要检测对方的关闭连接操作,发现对方关闭连接后自己也要关闭该连接。
    • Too many open files:操作系统的中打开文件的最大句柄数受限所致,常常发生在很多个并发用户访问服务器的时候。因为为了执行每个用户的应用服务器都要加载很多文件(new一个socket就需要一个文件句柄),这就会导致打开文件的句柄的不足。
      • 尽量把类打成jar包,因为一个jar包只消耗一个文件句柄,如果不打包,一个类就消耗一个文件句柄。
      • java的GC不能关闭网络连接打开的文件句柄,如果没有执行close()则文件句柄将一直存在,而不能被关闭
      • 设置socket的最大打开数来控制这个问题。对操作系统做相关的设置,增加最大文件句柄数量
  6. 对于一个Http请求来说,一次http请求,必定会有三个阶段,一:建立连接;二:数据传送;三,断开连接。当建立连接在规定的时间内(ConnectionTimeOut)没有完成,那么此次连接就结束了。后续的SocketTimeOutException就一定不会发生。只有当连接建立起来后,也就是没有发生ConnectionTimeOutException,才会开始传输数据,如果数据在规定的时间内(SocketTimeOut)传输完毕,则断开连接。否则,触发SocketTimeOutException 。关于java的ConnectionTimeOutSocketTimeOutException的详细分析,可以参见Socket笔记之深入分析java中的ConnectionTimedOutSocket笔记之Read timed out深入分析

Socket选项

  1. Socket有以下几个选项

    • TCP_NODELAY:表示立即发送数据
    • SO_RESUSEADDR:表示是否允许重用Socket所绑定的本地地址
    • SO_TIMEOUT:表示接收数据时的等待超时时间
    • SO_LINGER:表示当执行Socket的close方法时,是否立即关闭底层的Socket
    • SO_SNFBUF:表示发送数据的缓冲区的大小
    • SO_RCVBUF:表示接收数据的缓冲区大小
    • SO_KEEPALIVE:表示对于长时间处于空闲状态的Socket,是否要自动把它关闭
    • OOBINLINE:表示是否支持发送一个字节的TCP紧急数据
  2. SO就是SocketOptions的缩写

    public interface SocketOptions {
        public final static int TCP_NODELAY = 0x0001;
        public final static int SO_BINDADDR = 0x000F;
        public final static int SO_REUSEADDR = 0x04;
        public final static int SO_BROADCAST = 0x0020;
        public final static int IP_MULTICAST_IF = 0x10;
        public final static int IP_MULTICAST_IF2 = 0x1f;
        public final static int IP_MULTICAST_LOOP = 0x12;
        public final static int IP_TOS = 0x3;
        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;
    }
    

缓冲区

  1. 缓冲区(SO_RCVBUF, SO_SNDBUF)越大,操作效率越高。在以太网上,4KB的缓冲区大小是不够的,将4KB提高到16KB会是吞吐量提高40%(《Fundamental Networking in java》)

  2. Socket缓冲区大小应始终至少为连接的Maximum segment size(MSS)三倍

  3. 发送缓冲区大小应至少与另一端的接收缓冲区一样大

  4. 对于一次发送大量数据的应用程序,将发送缓冲区的大小增加到48k或者64Kb可能是对应用程序进行的最有效的性能改进

  5. 无论缓冲区大小是多少,都应该用通过BufferedOutputStreamByteBuffer写入缓冲区大小的块来改善TCP的性能

SO_KEEPALIVE

  1. 表示底层TCP协议的心跳机制。true为连接保持心跳,默认值为false。启用该功能时,TCP会主动探测空闲连接的有效性。可以将此功能视为TCP的心跳机制,需要注意的是:默认的心跳间隔是7200s即2小时。 一般情况下,我们不需要开启,比如Netty默认关闭该功能。

TCP_NODELAY

  1. 在默认情况下,客户端向服务器发送数据时,会根据数据包的大小决定是否立即发送。当数据包中的数据很少时,如只有1个字节,而数据包的头却有几十个字节(IP头+TCP头)时,系统会在发送之前先将较小的包合并到较大的包后,一起将数据发送出去。在发送下一个数据包时,系统会等待服务器对前一个数据包的响应,当收到服务器的响应后,再发送下一个数据包,这就是所谓的Nagle算法;在默认情况下,Nagle算法是开启的。
  2. Nagle算法虽然可以有效地改善网络传输的效率,但对于网络速度比较慢,而且对实现性的要求比较高的情况下(如游戏、Telnet等),使用这种方式传输数据会使得客户端有明显的停顿现象。因此,最好的解决方案就是需要Nagle算法时就使用它,不需要时就关闭它。而使用setTcpToDelay正好可以满足这个需求。当使用setTcpNoDelay(true)将Nagle算法关闭后,客户端每发送一次数据,无论数据包的大小都会将这些数据发送出去。
  3. 如果Socket底层实现不支持TCP_NODELAY选项,那么setTcpNoDelaygetTcpNoDelay就会抛SocketException

SO_LINGER

  1. SO_LINGER选项指定了Socket关闭时如何处理尚未发送的数据报。默认情况下,close()方法将立即返回,但系统仍会发送剩余的数据。如果延迟时间设置为0,那么当Socket关闭时,所有未发送的数据包将被丢弃。如果SO_LINGER打开而且延迟时间设置为任意正数,close()方法将会阻塞(阻塞时间为指定的秒数),等待发送数据和接收确认。当过去相应秒数后,Socket关闭,所有剩余的数据都不会发送,也不会收到确认。

服务类型

  1. 用户可以根据自己的需求选择不同的服务类型。例如发送视频需要较高的带宽,快速达到目的地,以保证接收方看到连续的画面。而发送电子邮件可以使用较低的带宽

  2. IP规定了4中服务类型,用来定性地描述服务的质量

    • 低成本:发送成本低
    • 高可靠性:保证把数据可靠地送达目的地
    • 高吞吐量:一次可以接收或发送大批量的数据
    • 最小延迟:传输数据的速度快,把数据快速送达目的地
  3. 四种服务类型可以组合,例如,同时要求获得最高可靠性和最小延迟。

    public void setTrafficClass(int tc)
    public int getTrafficClass() throws SocketException
    低成本:IPTOS_LOWCOST (0x02) 
    高可靠性:IPTOS_RELIABILITY (0x04) 
    最高吞吐量:IPTOS_THROUGHPUT (0x08) 
    最小延迟:IPTOS_LOWDELAY (0x10)
    socket = new Socket(host,port);
    socket.setTrafficClass(0x04|0x10);//0x04|0x10 进行位或运算
    

设定连接时间、延迟和带宽的相对重要性

  1. jdk1.5提供了setPerformancePreferences,用于设定三个指标的重要性,三个整数之间的相对大小决定了相应参数的相对重要性。

    connectionTime:表示用最少时间建立连接
    latency:表示最小延迟
    bandwidth:表示最高带宽
    public void setPerformancePreferences(int connectionTime,int latency,int bandwidth)
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值