Java Socket是JVM通过操作系统操控CPU、网卡与外界通信的一个组件,包括BIO、NIO、AIO等网络IO组件的底层也是Socket。
在了解Java Socket之前最好先了解一下网络模型的相关概念:
OSI七层模型
TCP/IP四层模型
RPC协议
Restful协议
应用层:主机中的各个进程,提供端口号供访问
表示层:将主机中的进程传递的数据通过不同的OS编译/转换/加密处理后交给硬件(网卡)
会话层:标识相互通讯的主机间正在进行的回话,负责建立、管理、终止会话
传输层:数据脱离网卡后即进入传输层,记录了即将访问的端口号(TCP/UDP),将数据分段
网络层:通过网线寻找目标主机(TCP/IP)
链路层:将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发现但不能纠正。
物理层:建立、维护、断开物理连接。
TCP/IP模型将OSI参考模型中的会话层和表示层的功能合并到应用层实现。
应用层面向不同的网络应用引入了不同的应用层协议。其中,有基于TCP协议的,如文件传输协议(File Transfer Protocol,FTP)、虚拟终端协议(TELNET)、超文本链接协议(Hyper Text Transfer Protocol,HTTP),也有基于UDP协议的。
OSI和TCP/IP都是概念,TCP/IP是OSI的合并,简化
Remote Procedure Call 远程过程通讯
Representational State Transfer 表象性状态传递
直接上代码
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();
}
}
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;
}
}
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;
}
}
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;
}
}