长连接及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;
	}
}