Java Socket和TCP/IP以及OSI,RPC和RESTful协议
Java Socket和网络模型
Java Socket是JVM通过操作系统操控CPU、网卡与外界通信的一个组件,包括BIO、NIO、AIO等网络IO组件的底层也是Socket。
在了解Java Socket之前先得了解网络模型的相关概念
-
OSI七层模型
-
TCP/IP四层模型
-
RPC协议
-
Restful协议
OSI七层模型
-
应用层:主机中的各个进程,提供端口号供访问
-
表示层:将主机中的进程传递的数据通过不同的OS编译/转换/加密处理后交给硬件(网卡)
-
会话层:标识相互通讯的主机间正在进行的回话,负责建立、管理、终止会话
-
传输层:数据脱离网卡后即进入传输层,记录了即将访问的端口号(TCP/UDP),将数据分段
-
网络层:通过网线寻找目标主机(TCP/IP)
-
链路层:将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发现但不能纠正。
-
物理层:建立、维护、断开物理连接。
TCP/IP四层模型
-
- 主机到网络层
- 实际上TCP/IP参考模型没有真正描述这一层的实现,只是要求能够提供给其上层-网络互连层一个访问接口,以便在其上传递IP分组。由于这一层次未被定义,所以其具体的实现方法将随着网络类型的不同而不同。
-
- 网络互连层
- a. 网络互连层是整个TCP/IP协议栈的核心。它的功能是把分组发往目标网络或主机。同时,为了尽快地发送分组,可能需要沿不同的路径同时进行分组传递。因此,分组到达的顺序和发送的顺序可能不同,这就需要上层必须对分组进行排序。
- b. 网络互连层定义了分组格式和协议,即IP协议(Internet Protocol)。
- c. 网络互连层除了需要完成路由的功能外,也可以完成将不同类型的网络(异构网)互连的任务。除此之外,网络互连层还需要完成拥塞控制的功能。
-
- 传输层
- a. 在TCP/IP模型中,传输层的功能是使源端主机和目标端主机上的对等实体可以进行会话。在传输层定义了两种服务质量不同的协议。即:传输控制协议TCP(transmission control protocol)和用户数据报协议UDP(user datagram protocol)。
- b. TCP协议是一个面向连接的、可靠的协议。它将一台主机发出的字节流无差错地发往互联网上的其他主机。在发送端,它负责把上层传送下来的字节流分成报文段并传递给下层。在接收端,它负责把收到的报文进行重组后递交给上层。
- c. TCP协议还要处理端到端的流量控制,以避免缓慢接收的接收方没有足够的缓冲区接收发送方发送的大量数据。UDP协议是一个不可靠的、无连接协议,主要适用于不需要对报文进行排序和流量控制的场合。
-
- 应用层
- a. TCP/IP模型将OSI参考模型中的会话层和表示层的功能合并到应用层实现。
- b. 应用层面向不同的网络应用引入了不同的应用层协议。其中,有基于TCP协议的,如文件传输协议(File Transfer Protocol,FTP)、虚拟终端协议(TELNET)、超文本链接协议(Hyper Text Transfer Protocol,HTTP),也有基于UDP协议的。
OSI和TCP/IP的区别
OSI和TCP/IP都是概念,TCP/IP是OSI的合并,简化
工作原理
RPC协议
Remote Procedure Call 远程过程通讯
- RPC可以基于HTTP,rmi等协议,所谓的存根,就是两个应用之间可建立固定的Socket Channel,只在建立Channel时三次握手,之后的每次通讯不需三次握手,数据格式可以为xml,json等格式
- 是长连接,在客户端和服务端都有存根,不必每次通信都要去3次握手什么的,减少了网络开销
- 一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作
- 比如dubbo,发送方只有接口,而接收方有接口,并去真正的实现,通过这种方式在发送机创建port(传输层),在接收机也创建相同的固定的port
Restful协议
Representational State Transfer 表象性状态传递
- RESTful基于HTTP,对其语意强化为web应用常用的4种CRUD请求,数据格式可以为xml,json等格式
- RESTful基于HTTP,HTTP基于tcp,是短连接,每次请求都需要3次握手
Java Socket
直接上代码
- TCP Socket 客户端
package com.td.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class SocketTCPClient {
public static void main(String args[]) throws Exception {
String host = "127.0.0.1";
int port = 55533;
transportToServer(host, port, "data body");
heartbeatCheck(3000l, host, port);
}
/**
* 传输数据到服务端
*/
public static void transportToServer(String host, int port, String data) {
try {
Socket socket = new Socket(host, port);
socket.setReuseAddress(true); //使用 bind(SocketAddress)时,关闭 TCP 连接时,该连接可能在关闭后的一段时间内保持超时状态,不建议使用bind(),不建议使用次配置
socket.setSoLinger(true, 65535); //启用/禁用具有指定逗留时间的 SO_LINGER,Socket会等待指定的时间发送完数据包,当数据量发送过大抛出异常时,再来设置这个值
socket.setTcpNoDelay(true); //客户向服务器发送数据的时候,会根据当前数据量来决定是否发送,如果数据量过小,那么系统将会根据Nagle 算法(暂时还没研究),来决定发送包的合并,也就是说发送会有延迟,默认开启。这个操作可能导致拆包和粘包
socket.setSendBufferSize(10); //默认都是8K,如果有需要可以修改,通过相应的set方法。不建议修改的太小,设置太小数据传输将过于频繁。太大了将会造成消息停留。setTcpNoDelay为true时设置无效
socket.setReceiveBufferSize(10); //默认都是8K,如果有需要可以修改,通过相应的set方法。不建议修改的太小,设置太小数据传输将过于频繁。太大了将会造成消息停留。setTcpNoDelay为true时设置无效
socket.setKeepAlive(true); //构建长时间连接的Socket还是配置上SO_KEEPALIVE比较好,!!!!长连接!!!!
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
BufferedReader read = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
outputStream.write(data.getBytes("UTF-8"));
socket.shutdownOutput(); //通过关闭流来告知服务端传输完毕,可以处理收到的消息,可用outputStream.close();替代,原理一样
readStream(inputStream, read);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 读流
*/
private static void readStream(InputStream inputStream, BufferedReader read) {
try {
String line;
StringBuilder sb = new StringBuilder();
while ((line = read.readLine()) != null) {
sb.append(line);
}
System.out.println("get message from server: " + sb.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 心跳检测机制
*/
private static void heartbeatCheck(Long timeCycle, String host, int port) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(timeCycle);
transportToServer(host, port, "heartbeat package");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
- TCP Socket 服务端
package com.td.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SocketTCPServer {
private static ExecutorService threadPool = Executors.newFixedThreadPool(100);
public static void main(String[] args) throws Exception {
int port = 55533;
startSocketServer(port);
}
/**
* 启动服务端监听
*/
public static void startSocketServer(int port) {
try (ServerSocket server = new ServerSocket(port);) {
while(true) {
server.setSoTimeout(0);
//TCP三次握手操作后,系统才会将这个连接交给应用层
Socket socket = server.accept();
threadPool.submit(new Runnable() {
@Override
public void run() {
dealQueue(socket);
}
});
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
/**
* 处理收到的消息
*/
private static void dealQueue(Socket socket) {
try (InputStream inputStream = socket.getInputStream();
BufferedReader read = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
OutputStream outputStream = socket.getOutputStream();) {
String result = readStream(inputStream, read);
//检测心跳包
if (result != null && "heartbeat package".equals(result))
return;
outputStream.write("Hello Client,I get the message.".getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 读流
*/
private static String readStream(InputStream inputStream, BufferedReader read) {
try {
String line;
StringBuilder sb = new StringBuilder();
while ((line = read.readLine()) != null) {
sb.append(line);
}
System.out.println(sb.toString());
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
- UDP Socket 客户端
package com.td.socket;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
public class SocketUDPClient {
public static void main(String[] args) {
try {
DatagramSocket socket = new DatagramSocket();
sendUDP("127.0.0.1", 10010, "send data to server.", socket);
System.out.println("receive from server:" + (String) receiveUDP(socket));
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端发送报文
*/
public static void sendUDP(String host, int port, Object dataObj, DatagramSocket socket) throws IOException {
InetAddress address = InetAddress.getByName(host);
byte[] data = dataObj.toString().getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
socket.send(packet);
}
/**
* 客户端发送后接收报文,通过DatagramSocket为中介建立起新的接收Port
*/
public static Object receiveUDP(DatagramSocket socket) throws IOException {
byte[] dataReply = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(dataReply, dataReply.length);
socket.receive(packet2);
String reply = new String(dataReply, 0, packet2.getLength());
return reply;
}
}
- UDP Socket 服务端
package com.td.socket;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class SocketUDPServer {
public static void main(String[] args) {
try {
int port = 10010;
DatagramSocket socket = new DatagramSocket(port);
DatagramPacket reply = receiveUDP(socket);
sendUDP(reply.getAddress(), reply.getPort(), "welcome to server!", socket);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* UDP接收报文后发送报文
* @param port 服务端收文后通过DatagramPacket获取"动态"Port及来访IP
*/
public static void sendUDP(InetAddress inetAddress, int port, Object dataObj, DatagramSocket socket) throws IOException {
byte[] data = dataObj.toString().getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length, inetAddress, port);
socket.send(packet);
}
/**
* UDP接收报文
*/
public static DatagramPacket receiveUDP(DatagramSocket socket) throws IOException {
byte[] dataReply = new byte[1024];
DatagramPacket packet = new DatagramPacket(dataReply, dataReply.length);
socket.receive(packet);
String reply = new String(dataReply, 0, packet.getLength());
System.out.println("receive from client : " + reply);
return packet;
}
}