长连接及Java Socket实现
Socket默认是不关闭的,除非手动关闭,建立连接的两端可互相发送信息,连接的长短主要针对的是连接的时间,长时间不关闭的连接即长连接,短连接即建立连接的两端在发送一次或几次数据后很快关闭Socket的连接。
以下是Tcp、UDP的字节流和字符流的Socket使用,可用其直接传输文件及字符数据。
Tcp.服务端
package com.td.socket;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Tcp
* 短连接:建立连接的两端在发送一次或几次数据后很快关闭Socket的连接
* 长连接:Socket默认是不关闭的,除非手动关闭,长时间不关闭的连接即长连接,建立连接的两端可互相发送信息
* 长连接实现:不主动关闭建立连接的两端,通过流的flush()发送数据
*/
public class SocketTCPServer {
private static ExecutorService threadPool = Executors.newFixedThreadPool(100);
private static final int characterPort = 55533;
private static final int bytePort = 55534;
public static File writeToFile1 = new File("E:\\logs\\copied1.pdf");
public static File writeToFile2 = new File("E:\\logs\\copied2.pdf");
public static void main(String[] args) {
try {
startSocketServerCharacter();
startSocketServerByte();
} catch (Exception e) {
System.out.println(e.toString());
}
}
/**
* 字符流-启动socket
*/
public static void startSocketServerCharacter() throws Exception {
ServerSocket serverSocket = new ServerSocket(characterPort);
while (true) {
//等待客户端的连接
Socket socket = serverSocket.accept();
// 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比较好,默认2小时无数据交互时发送探测包,周期为75秒,失败重连次数为9次,不同系统配置不一样
threadPool.submit(new Runnable() {
@Override
public void run() {
try (
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
) {
characterMsgReader(inputStream, outputStream);
socket.shutdownOutput(); // 长连接则不关闭输出流
socket.shutdownInput(); // 长连接则不关闭输入流
} catch (Exception e) {
System.out.println(e.toString());
}
}
});
}
}
/**
* 字符流-解析客户端的消息
*/
public static void characterMsgReader(InputStream inputStream, OutputStream outputStream) throws Exception {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String str;
//收到断行符"\n"则读出一行
while((str=bufferedReader.readLine()) != null) { // 如果不关闭接收流的话,bufferedReader.readLine()会一直阻塞,等待客户端传递消息
System.out.println("客户端消息:收到一条来自客户端的消息: " + str);
characterMsgWriter(outputStream, inputStream, str);
}
System.out.println("end");
}
/**
* 字符流-发送给客户端回执消息
*/
public static void characterMsgWriter(OutputStream outputStream, InputStream inputStream, String str) throws Exception {
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
str = "消息已收到";
bufferedWriter.write(str + " - 第一次回执");
bufferedWriter.write("\n");
bufferedWriter.flush(); // 第一次回执消息,长连接实现方式
}
/**
* 字节流-启动socket
*/
public static void startSocketServerByte() throws Exception {
ServerSocket serverSocket = new ServerSocket(bytePort);
while (true) {
//等待客户端的连接
Socket socket = serverSocket.accept();
// 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比较好,默认2小时无数据交互时发送探测包,周期为75秒,失败重连次数为9次,不同系统配置不一样
threadPool.submit(new Runnable() {
@Override
public void run() {
try (
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
) {
byteMsgReader(inputStream, outputStream);
socket.shutdownOutput(); // 长连接则不关闭输出流
socket.shutdownInput(); // 长连接则不关闭输入流
} catch (Exception e) {
System.out.println(e.toString());
}
}
});
}
}
/**
* 字节流-解析客户端的消息
*/
public static void byteMsgReader(InputStream inputStream, OutputStream outputStream) throws Exception {
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
FileOutputStream bufferedOutputStream1 = new FileOutputStream(writeToFile1, false);
FileOutputStream bufferedOutputStream2 = new FileOutputStream(writeToFile2, false);
byte[] b = new byte[4096];
int i = 0;
while ((i = bufferedInputStream.read(b)) > 0) {
bufferedOutputStream1.write(b, 0, i);
bufferedOutputStream1.flush();
if (i < b.length) { // 根据读取的长度是否满格判断当前文件是否已读取完毕
bufferedOutputStream1.close();
break;
}
}
System.out.println("接收完成第一个文件");
byteMsgWriter(outputStream);
i = 0;
byte[] b2 = new byte[4096];
while ((i = bufferedInputStream.read(b2)) > 0) {
bufferedOutputStream2.write(b2, 0, i);
bufferedOutputStream2.flush();
if (i < b2.length) { // 根据读取的长度是否满格判断当前文件是否已读取完毕
bufferedOutputStream2.close();
break;
}
}
System.out.println("接收完成第二个文件");
byteMsgWriter(outputStream);
}
/**
* 字节流-发送给客户端回执消息
*/
public static void byteMsgWriter(OutputStream outputStream) throws Exception {
BufferedOutputStream bufferedOutputStreamBack = new BufferedOutputStream(outputStream);
bufferedOutputStreamBack.write("服务端已成功接收文件.".getBytes("UTF-8"));
bufferedOutputStreamBack.write("\n".getBytes("UTF-8"));
bufferedOutputStreamBack.flush(); // 第一次发送消息,长连接实现方式
}
}
Tcp.客户端
package com.td.socket;
import java.io.*;
import java.net.Socket;
public class SocketTCPClient {
private static final String host = "127.0.0.1";
private static final int characterPort = 55533;
private static final int bytePort = 55534;
public static File readFile = new File("E:\\logs\\file.pdf");
public static void main(String args[]) throws Exception {
transportToServer();
}
/**
* 传输数据到服务端
*/
public static void transportToServer() {
try {
startSocketClientByte();
startSocketClientCharacter();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 字节流-启动socket
*/
public static void startSocketClientByte() throws Exception {
Socket socket = new Socket(host, bytePort);
// 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比较好,默认2小时无数据交互时发送探测包,周期为75秒,失败重连次数为9次,不同系统配置不一样
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
byteMsgWriter(outputStream, inputStream);
socket.shutdownOutput(); // 长连接则不关闭输出流
socket.shutdownInput(); // 长连接则不关闭输入流
}
/**
* 字节流-发送给服务端
*/
public static void byteMsgWriter(OutputStream outputStream, InputStream inputStream) throws Exception {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(readFile));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
//mark后读取超过readlimit字节数据,mark标记就会失效
bufferedInputStream.mark(909600000);
byte[] b = new byte[4096];
int i = 0;
while ((i = bufferedInputStream.read(b)) > 0) {
bufferedOutputStream.write(b, 0, i);
bufferedOutputStream.flush(); // 第一次发送消息,长连接实现方式
}
bufferedInputStream.reset();
Thread.sleep(1); // 必须加休眠,否则第二个文件流会发生错乱
characterMsgReader(bufferedReader); // 从服务端接收消息
i = 0;
while ((i = bufferedInputStream.read(b)) > 0) {
bufferedOutputStream.write(b, 0, i);
bufferedOutputStream.flush(); // 第二次发送消息,长连接实现方式
}
characterMsgReader(bufferedReader); // 从服务端接收消息
}
/**
* 字符流-启动socket
*/
public static void startSocketClientCharacter() throws Exception {
Socket socket = new Socket(host, characterPort);
// 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比较好,默认2小时无数据交互时发送探测包,周期为75秒,失败重连次数为9次,不同系统配置不一样
String data = "来自客户端的字符流消息";
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
characterMsgWriter(outputStream, data, inputStream);
// socket.shutdownOutput(); //通过心跳关闭socket输出流
// socket.shutdownInput(); //通过心跳关闭socket输入流
}
/**
* 字符流-发送给服务端
*/
public static void characterMsgWriter(OutputStream outputStream, String data, InputStream inputStream) throws Exception {
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
bufferedWriter.write(data);
bufferedWriter.write("\n");
bufferedWriter.flush(); // 第一次发送消息,长连接实现方式
characterMsgReader(bufferedReader); // 从服务端接收消息
bufferedWriter.write(data);
bufferedWriter.write("\n");
bufferedWriter.flush(); // 第二次发送消息,长连接实现方式
characterMsgReader(bufferedReader); // 从服务端接收消息
}
/**
* 字符流-解析服务端的回执消息-单次解析
*/
public static void characterMsgReader(BufferedReader bufferedReader) throws Exception {
System.out.println("服务端消息: " + bufferedReader.readLine());
}
/**
* 字符流-解析服务端的回执消息-不间断解析
*
*/
public static void characterMsgReader(InputStream inputStream) throws Exception {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line = null;
while ((line = bufferedReader.readLine()) != null) {
System.out.println("服务端消息: " + line);
}
}
}
UDP.服务端
package com.td.socket;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* UDP同理
* 短连接:建立连接的两端在发送一次或几次数据后很快关闭Socket的连接
* 长连接:Socket默认是不关闭的,除非手动关闭,长时间不关闭的连接即长连接,建立连接的两端可互相发送信息
* 长连接实现:不主动关闭建立连接的两端,通过流的flush()发送数据
*/
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);
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("第一次收到客户端消息 : " + reply);
String reply2 = new String(dataReply, 0, packet.getLength());
System.out.println("第二次收到客户端消息 : " + reply2);
return packet;
}
}
UDP.客户端
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());
System.out.println("第一次收到数据" + reply);
socket.receive(packet2);
String reply2 = new String(dataReply, 0, packet2.getLength());
System.out.println("第二次收到数据" + reply2);
return reply;
}
}