[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)的核心属性:
- bindAddr, 宿主主机的IP地址
- port,监听的端口号
- backlog,请求传入连接队列的最大长度
- 核心API
- 实例化对象
// 创建一个未绑定的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 ;
- bind
手动绑定到端
public void bind(SocketAddress endpoint) throws IOException
public void bind(SocketAddress endpoint, int backlog) throws IOException ;
endpoint是SocketAddress
类型,使用其实现类InetSocketAddress
来构造地址(IP+Port)。指定端地址的创建SS方法不需要再手动绑定。
- 监听连接
public Socket accept() throws IOException ;
阻塞方法,在连接到达之前一直阻塞,有连接时返回一个对该连接抽象的Socket对象
- 其他
获取地址信息: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使用:
- 创建连接到远端端点的Socket对象(实例化和连接)
- 读写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.
* <p>
* 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.
* <p>
*/
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.
* <p>
* 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通信