NIO与Socket笔记 :实现Socket 通信[ 二 ]

TCP 连接的 3 次“握手”过程

1 )客户端到服务端 : 我要连接 。
2 )服务端到客户端:好的,已经连接上了 。
3 )客户端到服务端:收到,确认已连接上了 。

1、client发送SYN到server,将状态修改为SYN_SEND,如果server收到请求,则将状态修改为SYN_RCVD,并把该请求放到syns queue队列中。
2、server回复SYN+ACK给client,如果client收到请求,则将状态修改为ESTABLISHED,并发送ACK给server。
3、server收到ACK,将状态修改为ESTABLISHED,并把该请求从syns queue中放到accept queue。

 

在linux系统内核中维护了两个队列:syns queue和accept queue

syns queue
用于保存半连接状态的请求,其大小通过/proc/sys/net/ipv4/tcp_max_syn_backlog指定,一般默认值是512,不过这个设置有效的前提是系统的syncookies功能被禁用。互联网常见的TCP SYN FLOOD恶意DOS攻击方式就是建立大量的半连接状态的请求,然后丢弃,导致syns queue不能保存其它正常的请求。

accept queue
用于保存全连接状态的请求,其大小通过/proc/sys/net/core/somaxconn指定,在使用listen函数时,内核会根据传入的backlog参数与系统参数somaxconn,取二者的较小值。

如果accpet queue队列满了,server将发送一个ECONNREFUSED错误信息Connection refused到client。


 

 

TCP 断开连接的 4 次“挥手”过程

1 )客户端到服务端:我关了 。
2 )服务端到客户端:好的,收到 。

3 )服务端到客户端:我也关了 。
4 )客户端到服务端:好的,收到 。

 

“握手”的时机与立即传数据的特性

 

服务端与客户端进行“握手”的时机不是在执行 accpet()方法时,

而是在 ServerSocket 对象创建出来并且绑定到指定的地址与端口时 。

 

 

结合多线程 Thread 实现通信

在 Socket技术中,常用的实践方式就是 Socket结合 Thread多线程技术,客户端每发起 一次新的请求,

就把这个请求交给新创建的线程来执行这次业务。

 

服务端与客户端互传对象以及 I/O 流顺序问题

防止阻塞

1)服务端先获得 ObjectlnputStream对象, 客户端就要先获得 ObjectOutputStream对象;

2 )服务端先获得 ObjectOutputStrearn对象,客户端就要先获得 ObjectinputStream对象。

 

ServerSocket 类的使用

接受 accept 与超时 Timeout

 

public Socket accept()方法 的作用就是侦昕并接受此套接字 的连接 。 此方法在连接传人 之前一直阻塞。

setSoTimeout (timeout)方法的作用是设置超时时间,通过指定超时 timeout值启用/禁 用 SO_TIMEOUT,以 ms为单位。

 

SO_TIMEOUT选项必须在进入阻塞操作前被启用才能生效。

超时值必须是大于 0的数。 超时值为 0被解释为无穷大超时值

 

 

构造方法的 backlog 参数含义

ServerSocket 类的构造方法

public ServerSocket(i nt port, int backlog) 中的参数 backlog 的主要作用就是允许接受客户端连接请求的个数 。

客户端有很多连接进入到操作系统 中,将这些连接放人操作系统的队列中,当执行 accept()方法时,允许客户端连接的个数要 取决于 backlog参数

利用指定的 backlog创建服务器套接字并将其绑定到指定的本地端口号 port。

对 port端 口参数传递值为 0,意味着将自动分配空闲 的端口号 。

传入 backlog 参数的作用是设置最大等待队列长度,如果队列己满,则拒绝该连接 。

backlog 参数必须是大于 0 的正值,如果传递的值等于或小于 0,则使用默认值 50。

其默认值是 50。

public ServerSocket(int port) throws IOException {
    this(port, 50, null);
}
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
    setImpl();
    if (port < 0 || port > 0xFFFF)
        throw new IllegalArgumentException(
                   "Port value out of range: " + port);
    if (backlog < 1)
      backlog = 50;
    try {
        bind(new InetSocketAddress(bindAddr, port), backlog);
    } catch(SecurityException e) {
        close();
        throw e;
    } catch(IOException e) {
        close();
        throw e;
    }
}

 

-----------------------------------------------------------------------------------------------------------

检测,貌似并没法有发现阻塞......

package com.nio.socket.test6;

import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(7777, 1);
        // sleep (5000 )的作用是不让 ServerSocket 调用 accept ()方法, 
        //而是由客户端 Socket 先发起 10 个连接请求
        // 然后在执行 accept ()方法时只能接收 3 个连接
        Thread.sleep(10000) ;

        System.out.println( "accept1 begin");
        Socket socketl = serverSocket .accept();
        System .out .println( "accept1 end" );

        System. out. println ( " accept2 begin " );
        Socket socket2 = serverSocket . accept() ;
        System .out. println( "accept2 end " );

        System. out. println ( "accept3 begin " );
        Socket socket3 = serverSocket.accept() ;
        System. out. println (" accept3 end" );

        System.out.println ("accept4 begin");
        Socket socket4 = serverSocket.accept();
        System. out .println(" accept4 end ") ;

        System.out.println("accept5 begin");
        Socket socket5= serverSocket.accept();
        System .out .println ("accept5 end " ) ;



        Thread.sleep(100000) ;
        socketl.close();
        socket2.close() ;
        socket3.close() ;
        socket4.close();
        socket5.close() ;
        serverSocket.close();

    }
}

 

 

package com.nio.socket.test6;

import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Client {

    public static void main(String[] args) throws Exception {
        Socket socket1 = new Socket("localhost", 7777) ;
        Socket socket2 = new Socket("localhost", 7777) ;
        Socket socket3 = new Socket("localhost", 7777) ;
        Socket socket4 = new Socket("localhost", 7777) ;
        Socket socket5 = new Socket("localhost", 7777) ;
        Socket socket6 = new Socket("localhost", 7777) ;
        Socket socket7 = new Socket("localhost", 7777) ;
        Socket socket8 = new Socket("localhost", 7777) ;
        Socket socket9 = new Socket("localhost", 7777) ;
        Socket socket10 = new Socket("localhost", 7777) ;
        Socket socket11 = new Socket("localhost", 7777) ;
        Socket socket12 = new Socket("localhost", 7777) ;
        Socket socket13 = new Socket("localhost", 7777) ;
        Socket socket14 = new Socket("localhost", 7777) ;
        Socket socket15 = new Socket("localhost", 7777) ;
        Socket socket16 = new Socket("localhost", 7777) ;
        Socket socket17 = new Socket("localhost", 7777) ;
        Socket socket18 = new Socket("localhost", 7777) ;
        Socket socket19 = new Socket("localhost", 7777) ;
        Socket socket20 = new Socket("localhost", 7777) ;



    }
}

 

绑定到指定的 Socket 地址

public void bind (SocketAddress endpoint)方法的主要作用是

将 ServerSocket绑定到特定 的 Socket地址 (Ip地址和端口号),使用这个地址与客户端进行通信。

 

绑走到指定的 Socket 地址并设置 backlog 数量

bind (SocketAddress endpoint, int backlog)方法不仅可以绑定到指定 IP,而且还可以设 置 backlog 的连接数量。

默认: 50 

 

获取本地 SocketAdress 对象以及本地端口

getLocalSocketAddress()方法用来获取本地的 SocketAddress 对象 ,它返回此 Socket 绑 定的端点的地址,如果尚未绑定,则返回 null。

 

lnetSocketAddress 类的使用

 

InetSocketAddress类表示此类实现 IP套
接字地址 (Ip地址+端口号)。 它还可以是
一个( 主机名+端口号), 在此情况下,将尝 试解析主机名,如果解析失败,则该地址将被视为未解析的地址 , 但是其在某些情形下仍然 可以使用,如通过代理连接。 它提供不可变对象,供套接字用于绑定 、 连接或用作返回值。

通配符是一个特殊的本地 IP地址。 它通常表示 “任何”,只能用于 bind操作。

SocketAddress 与 InetAddress本质的区别就是 SocketAddress不基于任何协议。

1. 构造方法 public lnetSocketAddress (lnetA ddress addr, int po时)的使用

package com.nio.socket;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class test8 {

    public static void main(String[] args) throws Exception {

        ServerSocket serverSocket = new ServerSocket() ;
        InetAddress inetAddress =InetAddress . getByName( " localhost " ) ;
        InetSocketAddress inetSocketAddress = new InetSocketAddress(inetAddress, 8888) ; serverSocket .bind(inetSocketAddress) ;
        System.out.println("server begin");
        Socket socket= serverSocket .accept() ;
        System .out .println( " server end " ) ;
        socket.close() ; 
        serverSocket. close() ;
    }
}

 

2. getHostName()和 getHostString()方法的区别

 

 

public final String getHostName()方法的作用是获取主机名 。 注意,如果地址是用字面 IP地址创建的,则此方法可能触发名称服务反向查找, 也就是利用 DNS服务通过 IP找到 域名 。

public final String getHostString()方法的作用是返回主机名或地址的字符串形式, 如果 它没有主机名 ,则返回 IP地址。 这样做的好处是不尝试反向查找。

 

 

3. 获取 IP 地址 lnetAddress 对象

 

public final InetA ddress getAddress()方法的作用是获取 InetAddress 对象 。

 

4 创建未解析的套接字地址

public static InetSocketAddress createUnresolved (String host, int port)方法的作用是

根据 主机名和端口号创建未解析的套接字地址,但不会尝试将主机名解析为 InetAddress。

 

 

关闭与获取关闭状态

public void close()方法的作用是关闭此套接字。

 

 

判断 Socket 绑定状态

public boolean isBound()方法的作用是返回 ServerSocket 的 绑定状态。

 

获得IP地址信息

getlnetAddress()方法用来获取 Socket绑定的本地 IP地址信息。

 

Socket 选项 ReuseAddress

public void setReuseAddress (boolean on)方法的作用是启用 /禁用 SO_REUSEADDR套 接字选项。

关闭 TCP连接时,该连接可能在关闭后的一段时间内保持超时状态 (通常称为 TIME_WAIT状态或 2MSL等待状态)。

对于使用已知套接字地址或端口的应用程序而言,如果存在处于超时状态的连接(包括地址和端口),

则应用程序可能不能将套接字绑定到所 需的 SocketAddress 上 。

 

如果在使用bind(SocketAddress)方法‘绑定套接字之前’ 启用SO_REUSEADDR选项, 就可以允许绑定到处于超时状态的套接字 。

当 创建 ServerSocket 时, SO_REUSEADDR 的初始设 置是 不确定的,要依赖于操作系 统的实现 。

在使用 bind()方法绑定套接字 后,启用或禁用 SO_REUSEADDR 时的行为是不 确定的,也要依赖于操作系统的实现 。

 

应用程序可以使用 getReuseAddress()来判断 SO_REUSEADDR 的初始设置 。
public boolean getReuseAddress()方法的作用是测试是否启用 s。一阻USEADDR。

在调用 Socket类的 close()方法时·,会关闭当前连接,释放使用的端口,但在操作系统层面,并不会马上释放当前使用的端口

如果端口呈 TIME WAIT 状态,则在 Linux 操作系 统中可以重用此状态的端口 。

 

setReuseAddress (boolean)方法就是用来实现这样的功能的, 也就是端口复用 。

端口复用的优点是可以大幅提升端口的使用率,用较少的端口数完成更多 的任务 。

 

什么是 TIME WAIT 状态?

服务端( Server)与客户端( Client)建立 TCP 连接之后,主 动关闭连接的一方就会进入 TIME WAIT 状态 。

例如,客户端主动关闭连接时,会发送最后 一个 ACK,然后客户端就会进入 TIME_WAIT 状态,再“停留若干时间’,然后进入 CLOSED 状态 。 在 Linux 操作系统中,当在“停留若干时间”段时,应用程序是可以复用呈 TIME_WAIT 状态的端口的,这样可提升端口利用率 。

 

在 Linux 发行版 CentOS 中,默认允许端口复用 。

 

因为 Windows 操作系统并没有完全实现 BSD Socket 的标准,

所以意味着在 Windows 操作系统中不能使用 setReuseAddress (boolean)方法来实现端口复用 。

 

1. 服务端实现端口不允许被复用

2 服务端实现端口允许被复用

3. 客户端实现端口不允许被复用

4. 客户端实现端口允许被复用

 

Socket 选项 ReceiveBufferSize

public void setReceiveBufferSize (int size)方法的作用是为从此 ServerSocket接受的套 接字 的 SO_RCVBUF 选项设 置新 的建议值 。

在接受的套接字中,实际被采纳的值必须在 accept()方法返回套接字后通过调用 Socket.getReceiveBufferSize()方法进行获取。

@Native public final static int SO_RCVBUF = 0x1002;
0x1002 = 4098 

实际上设置的值,并不是我们所要的值:

A server serverSocket.getReceiveBufferSize ()= 131072      128kb
B server serverSocket . getReceiveBufferSize () =1024    1kb

 

 

 

 

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值