Java网络编程基础(BIO)

[TOC]

TCP/IP协议分层模型

  • TCP/IP分层模型
自顶向下 协议 功能 PDU(协议数据单元)
应用层 HTTP、SSH、FTP、TELNET等等 通过应用进程间的交互来完成特定网络应用 报文
运输层 TCP、UDP 在应用程序端点之间传输应用层报文 报文段
网络层 IP 将数据包从一台主机传输到另一台主机 数据报
链路层 以太网、wifi、电缆接入网的DOCSIS等 数据链路层将网络层交下来的IP数据报组装成帧,在两个相邻节点间的链路上传送帧
物理层 与传输媒介相关 实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异 比特

下层提供接口供上层使用,为上层服务。

Socket简述

Socket是运输层提供给应用层的接口,使得应用层可以基于socket接口实现基于TCP/UDP协议的通信。它处于应用层和运输层之间。Socket是面向应用程序端点的,封装了IP地址和端口号,二者标识了网络主机上的某一个应用程序,IP地址标识网络主机,端口号标识主机上的应用程序,这样唯一指定了通信地址,便可以发起应用程序端之间的网络通信。

在操作系统层面,socket是抽象成文件的,它和文件相似,都是数据来源,只不过socket底层还有网络通信链路和各种底层协议的支持,可以简单的看成一个远端的文件,使用socket打开文件,对这个特殊的文件进行读写操作和交互,从而达到通信(分享)的目的。

基于BIO的TCP通信

当客户端想要打开一个连接到服务器的TCP/IP连接时,就要使用到Java Socket类。socket类只需要被告知连接的IP地址和TCP端口,其余的都有Java实现。

假如我们想要打开一个监听服务,来监听客户端连接某些指定TCP端口的连接,那就需要使用Java ServerSocket类。当客户端通过Socket连接服务器端的ServerSocket监听时,服务器端会指定这个连接的一个Socket,此时客户端与服务器端间的通信就变成Socket与Socket之间的通信。

ServerSocket类

ServerSocket,由其名称即可知,是作为服务端的socket对象,在网络编程中,它又称为欢迎套接字,专门监听主机上的某个端口,如果有连接到此端口,则产生一个与该连接相连的Socket类对象。

Socket类是对网络套接字的封装,也即为主机应用端通信接口的抽象(可能是远程也可能是本地,但是是通过网络来通信的进程)。

当socket对象之间连接建立成功,进程持有的socket即可以抽象为远端文件,通过对这个文件进行读写进行数据交换也就是所谓的通信。

ServerSocket的使用目的是监听主机端口,接收到连接后返回一个Socket对象建立通信,这是基于TCP的可靠连接通信。UDP通信中无需双方建立连接。

  • ServerSocket(缩写成SS)的核心属性:
    1. bindAddr, 宿主主机的IP地址
    2. port,监听的端口号
    3. backlog,请求传入连接队列的最大长度
  • 核心API
  1. 实例化对象
// 创建一个未绑定的SS.此方法创建的SS需要调用bind方法绑定端口
public ServerSocket() throws IOException;

// 创建一个绑定到指定端口号的SS
// 端口号指定为0,则自动选择
// backlog=50
public ServerSocket(int port) throws IOException {
        this(port, 50, null);
    }

public ServerSocket(int port, int backlog) throws IOException;

// 核心方法
// @param bindAddr the local InetAddress the server will bind to
// InetAddress 是对IP地址的封装
public ServerSocket(int port, int backlog, InetAddress bindAddr) 
  throws IOException ;
  1. bind
    手动绑定到端
public void bind(SocketAddress endpoint) throws IOException
public void bind(SocketAddress endpoint, int backlog) throws IOException ;

endpoint是SocketAddress类型,使用其实现类InetSocketAddress来构造地址(IP+Port)。指定端地址的创建SS方法不需要再手动绑定。

  1. 监听连接
public Socket accept() throws IOException ;

阻塞方法,在连接到达之前一直阻塞,有连接时返回一个对该连接抽象的Socket对象

  1. 其他
    获取地址信息: getInetAddress(),getLocalPort(),getLocalSocketAddress()[IP+Port]
    检查状态:isBound(),isClosed()
    关闭:close() (ServerSocket实现了Closeable接口)
    accept超时时间:setSoTimeout(int timeout),getSoTimeout()
  • 示例
public class TestServerSocket {

    private static final int PORT = 12345;
    private static final int BACKLOG = 100;

    public static void main(String[] args) throws IOException {
        ServerSocket ss = getServerSocket();
        System.out.println("server start...");
        while (true) {
            Socket socket = ss.accept();
            System.out.println("connect incoming: " 
                                        + socket.getRemoteSocketAddress());

        }
    }

    private static ServerSocket getServerSocket() throws IOException {
        ServerSocket ss = new ServerSocket(PORT);
        // or
        ss = new ServerSocket(PORT, BACKLOG);
        // or
        InetAddress bindAddr = InetAddress.getLocalHost();
        ss = new ServerSocket(PORT, BACKLOG, bindAddr);
        // or
        ss = new ServerSocket();
        SocketAddress endpoint = new InetSocketAddress("localhost", PORT);
        ss.bind(endpoint, BACKLOG);

        return ss;
    }
}

Socket类

A socket is an endpoint for communication between two machines.

  • Socket使用:
  1. 创建连接到远端端点的Socket对象(实例化和连接)
  2. 读写socket进行面向流的通信
  • 创建socket(连接)
// 创建一个未连接的socket对象,可显式调用connect连接
public Socket();
// 指定代理创建一个未连接的socket对象,可显式调用connect连接
public Socket(Proxy proxy);
// 创建一个连接到指定地址的socket对象
public Socket(String host, int port)
        throws UnknownHostException, IOException;
public Socket(InetAddress address, int port) throws IOException;
// 同时为socket绑定本地地址,也可以显式调用bind方法来绑定本地地址;默认是自动分配(端口号)
public Socket(String host, int port, InetAddress localAddr,
                  int localPort) throws IOException 

/**
连接、bind
*/
public void connect(SocketAddress endpoint) throws IOException ;
/*
* Connects this socket to the server with a specified timeout value.
* A timeout of zero is interpreted as an infinite timeout. The connection
* will then block until established or an error occurs.
*/
public void connect(SocketAddress endpoint, int timeout) throws IOException ;
// Binds the socket to a local address.
public void bind(SocketAddress bindpoint) throws IOException
  • 读写socket进行通信
socket.getInputStream();
socket.getOutputStream();
  • 示例
    private static final int PORT = 12345;
    private static final String HOST = "localhost";
    private static final int LOCAL_PORT = 23456;

    public static void main(String[] args) throws IOException {
        Socket socket = getSocket();
        // communicate
        OutputStream os = socket.getOutputStream();
        os.write("hellosocket".getBytes());
        os.flush();
        socket.close();

    }

    private static Socket getSocket() throws IOException {
        Socket sock = new Socket();
        SocketAddress endpoint = new InetSocketAddress(HOST, PORT);
        sock.connect(endpoint);
        // or sock = new Socket(HOST, PORT);
        // or sock = new Socket(InetAddress.getLocalHost(), PORT);
        // or sock = new Socket(HOST, PORT, InetAddress.getLocalHost(),
        // LOCAL_PORT);
        return sock;
    }

基于BIO的UDP通信

UDP的工作方式与TCP相比略有不同。使用UDP通信时,在客户端与服务器之间并没有建立连接的概念,客户端发送到服务器的数据,服务器可能(也可能并没有)收到这些数据,而且客户端也并不知道这些数据是否被服务器成功接收。当服务器向客户端发送数据时也是如此。

正因为是不可靠的数据传输,UDP相比与TCP来说少了很多的协议开销。

使用UDP通信不需要建立连接,没有TCP三次握手的连接动作,只需要在通信时指定消息的接收者地址即可,类似于短信,交互需要发送-接收-发送,每一步都是独立的。

DatagramSocket

DatagramSocket类实现“UDP通信”,包括客户端和服务器端。虽然UDP方式的网络通讯不需要建立专用的网络连接,但是毕竟还是需要发送和接收数据,DatagramSocket实现的就是发送数据时的发射器,以及接收数据时的监听器的角色。

  • 创建实例
// 创建一个自动分配本地端口的DatagramSocket
public DatagramSocket() throws SocketException ;
// 指定绑定的本地地址
public DatagramSocket(SocketAddress bindaddr) throws SocketException ;
// 指定本地绑定端口
public DatagramSocket(int port) throws SocketException ;
//  指定本地绑定地址和端口
public DatagramSocket(int port, InetAddress laddr) throws SocketException;

  • bind
    public synchronized void bind(SocketAddress addr) throws SocketException

  • send
    发送信息

/**
     * Sends a datagram packet from this socket. The
     * {@code DatagramPacket} includes information indicating the
     * data to be sent, its length, the IP address of the remote host,
     * and the port number on the remote host.
     *
*/
public void send(DatagramPacket p) throws IOException

发送的目的地址在报文对象内配置。

  • receive
    /**
     * Receives a datagram packet from this socket. When this method
     * returns, the {@code DatagramPacket}'s buffer is filled with
     * the data received. The datagram packet also contains the sender's
     * IP address, and the port number on the sender's machine.
     * 

* This method blocks until a datagram is received. The * {@code length} field of the datagram packet object contains * the length of the received message. If the message is longer than * the packet's length, the message is truncated. *

*/ public synchronized void receive(DatagramPacket p) throws IOException

DatagramPacket

DatagramPacket类实现对于网络中传输的数据封装,也就是说,该类的对象代表网络中交换的数据。在UDP方式的网络编程中,无论是需要发送的数据还是需要接收的数据,都必须被处理成DatagramPacket类型的对象,该对象中包含发送到的地址、发送到的端口号以及发送的内容等。其实DatagramPacket类的作用类似于现实中的信件,在信件中包含信件发送到的地址以及接收人,还有发送的内容等,邮局只需要按照地址传递即可。在接收数据时,接收到的数据也必须被处理成DatagramPacket类型的对象,在该对象中包含发送方的地址、端口号等信息,也包含数据的内容。和TCP方式的网络传输相比,IO编程在UDP方式的网络编程中变得不是必须的内容,结构也要比TCP方式的网络编程简单一些。

    /**
     * Constructs a {@code DatagramPacket} for receiving packets of
     * length {@code length}, specifying an offset into the buffer.
     * 

* The {@code length} argument must be less than or equal to * {@code buf.length}. * * @param buf buffer for holding the incoming datagram. * @param offset the offset for the buffer * @param length the number of bytes to read. * * @since 1.2 */ public DatagramPacket(byte buf[], int offset, int length) { setData(buf, offset, length); this.address = null; this.port = -1; } public DatagramPacket(byte buf[], int length) { this (buf, 0, length); } /** * Constructs a datagram packet for sending packets of length * {@code length} with offset {@code ioffset}to the * specified port number on the specified host. The * {@code length} argument must be less than or equal to * {@code buf.length}. * * @param buf the packet data. * @param offset the packet data offset. * @param length the packet data length. * @param address the destination address. * @param port the destination port number. * @see java.net.InetAddress * * @since 1.2 */ public DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port) { setData(buf, offset, length); setAddress(address); setPort(port); } /** * Constructs a datagram packet for sending packets of length * {@code length} with offset {@code ioffset}to the * specified port number on the specified host. The * {@code length} argument must be less than or equal to * {@code buf.length}. * * @param buf the packet data. * @param offset the packet data offset. * @param length the packet data length. * @param address the destination socket address. * @throws IllegalArgumentException if address type is not supported * @see java.net.InetAddress * * @since 1.4 */ public DatagramPacket(byte buf[], int offset, int length, SocketAddress address) { setData(buf, offset, length); setSocketAddress(address); } public DatagramPacket(byte buf[], int length, InetAddress address, int port) { this(buf, 0, length, address, port); } public DatagramPacket(byte buf[], int SocketAddress address) { this(buf, 0, length, address); }

指定address的用作发送的数据封装,没有address的用来封装接收到的数据。

示例

  • UDPserver
/*
* UDP 服务端
*/

public class UDPServer {
    public static void main(String[] args) throws Exception {
        // 绑定端口用于在端口监听数据到来
        DatagramSocket ds = new DatagramSocket(12345);
        // 接收数据的buffer
        byte[] buf = new byte[1024];
        DatagramPacket dp = new DatagramPacket(buf, 1024);
        // 阻塞方法,阻塞直到有数据到达
        ds.receive(dp);
        // 解析数据
        String data = new String(dp.getData(), 0, dp.getLength());
        String ip = dp.getAddress().getHostAddress();
        int port = dp.getPort();

        System.out.println("ip地址:" + ip + " 端口号:" + port + " 消息:" + data);
        // 关闭socket
        ds.close();
    }
}

  • UDPclient
public class UDPClient {
    public static void main(String[] args) throws Exception {
        // 创建socket
        DatagramSocket ds = new DatagramSocket();
        String message = "Hello Java World!";
        // 封装报文对象
        DatagramPacket dp = new DatagramPacket(message.getBytes(), 
                           message.length(), InetAddress.getByName("127.0.0.1"),
                12345);
        // 发送报文
        ds.send(dp);
        ds.close();
    }
}

参考资料
[1] Java网络教程
[2] Java网络编程:UDP通信

你可能感兴趣的:(Java网络编程基础(BIO))