socket长连接篇
package practice;
import client.Client;
import client.KeepAlive;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
* Created by sheng on 17/12/22.
*/
public class MyClient {
private boolean running =false;
interface ObjectAction {
Object doAction(Object obj);
}
class DefaultObjectAction implements ObjectAction {
@Override
public Object doAction(Object object) {
System.out.println(object);
return object;
}
}
public static void main(String[] args) {
MyClient client = new MyClient();
client.doStart();
}
public void doStart(){
try {
if(running)return;
Socket socket=new Socket("127.0.0.1",7890);
running=true;
Thread t1=new Thread(new KeepAliveWatchDog(socket));
t1.start();
Thread t2=new Thread(new ReceiveThread(socket));
t2.start();
Scanner input=new Scanner(System.in);
String command=input.next();
if(command.equals("cancel")){
doStop();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void doStop(){
if(running) running=false;
}
class KeepAliveWatchDog extends Thread{
Socket socket;
long lastReceive=System.currentTimeMillis();
public KeepAliveWatchDog(Socket socket){
this.socket=socket;
}
@Override
public void run() {
//线程靠running变量保持持续活跃,一旦主线程要求关闭,所有子线程要主动关闭套接字
//看门狗每3秒心跳一次
while(running){
try {
if (System.currentTimeMillis() - lastReceive > 2000) {
OutputStream outputStream = socket.getOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(new KeepAlive());
System.out.println("send to server...");
objectOutputStream.flush();
lastReceive = System.currentTimeMillis();
} else {
Thread.sleep(10);
}
}catch(IOException e){
}catch(InterruptedException e){
}
}
if(!running){
close();
}
}
public void close(){
if(this.socket!=null){
try {
this.socket.close();
}catch(IOException ex){
}
}
System.out.println("KeepAliveWatchDog socket closed");
}
}
class ReceiveThread extends Thread{
Socket socket;
public ReceiveThread(Socket socket){
this.socket=socket;
}
@Override
public void run() {
while(running){
try {
InputStream inputStream = socket.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
if (objectInputStream.available() > 0) {
Object object = objectInputStream.readObject();
ObjectAction oa = new DefaultObjectAction();
oa.doAction(object);
} else {
Thread.sleep(10);
}
}catch(IOException e){
}catch(InterruptedException e){
}catch(ClassNotFoundException e){
}
}
if(!running){
close();
}
}
public void close(){
if(this.socket!=null){
try {
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("ReceiveThread socket closed");
}
}
}
package practice;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/**
* Created by sheng on 17/12/20.
* 根据消息的类型,自动匹配处理器
*/
public class MyServer {
private final int PORT = 7890;
private boolean running = false;
private Thread thread1;
private Map mapping=new HashMap();
interface ObjectAction {
Object doAction(Object obj);
}
class DefaultObjectAction implements ObjectAction {
@Override
public Object doAction(Object object) {
System.out.println(object);
return object;
}
}
public static void main(String[] args) {
MyServer server = new MyServer();
server.doStart();
}
public void doStart() {
//启动accept线程,进行
if (running) return;//确保服务器单线程启动
running = true;
thread1 = new Thread(new ConnWatchDogThread());
thread1.start();
System.out.println("server initial....");
Scanner input=new Scanner(System.in);
String next=input.next();
if(next.equals("cancel")){
doStop();
}
//启动socket action线程接收处理
}
public void doStop() {
if (running) running = false;
}
/**
* 消息-处理器模型,避免做冗余的判断
* 通过key获取value对象处理器的方式比if..else...或设计模式都要简单便捷.
* */
public void addMapping(Class classes, ObjectAction oa){
this.mapping.put(classes,oa);
}
public ObjectAction getAction(Class classes){
return this.mapping.get(classes);
}
/**
* 接收线程
*/
class ConnWatchDogThread extends Thread {
ServerSocket socket;
@Override
public void run() {
try {
socket = new ServerSocket(PORT, 5);
while (running) {
Socket socket1 = socket.accept();//阻塞方法,但是只会接收一个,需要循环接收
System.out.println("accepted client:"+socket1.getRemoteSocketAddress());
Thread thread1 = new Thread(new SocketActionThread(socket1));
thread1.start();
}
if(!running)
close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void close() {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 消息处理线程
*/
class SocketActionThread extends Thread {
Socket socket;
long lastReceiveTime = System.currentTimeMillis();
public SocketActionThread(Socket socket) {
this.socket = socket;
}
private int delay = 3000;
private boolean runnable=true;
@Override
public void run() {
//长连接每隔3秒都需要心跳喂狗一次,
while(running && runnable) {
try {
if (System.currentTimeMillis() - lastReceiveTime > delay && running) {
executeOvertime();
} else {
//执行读写socket
InputStream inputStream = socket.getInputStream();
if (inputStream.available() > 0) {
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Object obj = objectInputStream.readObject();
lastReceiveTime = System.currentTimeMillis();
ObjectAction oa = new DefaultObjectAction();
Object out = oa.doAction(obj);
if (out != null) {
//回写给客户端
OutputStream outputStream = socket.getOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(out);
}
} else {
Thread.sleep(10);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
if(!running){
close();
}
}
//超时,主动断开socket
public void executeOvertime() {
if(runnable)runnable=false;
if (this.socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("client over time:"+socket.getRemoteSocketAddress());
}
public void close(){
if(this.socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
我开启了一个server,开启了5个client。client每隔2秒发送一次心跳,服务器端收到后会回复。
我并没有手动结束,但是日志提示,客户端主动断开了。
2017-12-23 11:36:19 维持连接包 开始
2017-12-23 16:06:04 维持连接包 结束
内存使用信息:
开启了5个java进程,占用内存分别为598MB,555MB,471MB,447MB,432MB.
手动关闭client端后,只剩下server 进程,占用48MB。
客户端哪个地方写的有bug,导致客户端内存占用率这么大,还是长连接的维持本来就占用内存较高。
———-分割线
java内存分析工具 jconsole snip
1.从上图我们可以看到每隔10分钟,JVM就会将堆内存回收一次.
2.从上图可以看到多个客户端请求响应,服务端内存回收的频率加快了.
4.上图是当服务器端意外断开或者出现网络超时,客户端出现了OOM.
5.当出现网络超时时,发送线程并没有正常退出.
通过下面的日志输出可以看到,IOException一直在报错,我尝试在异常中增加socket关闭操作,然而并没有用.不是socket没关闭造成的.
6.真正引起OOM的是线程处于不可控状态,while一直在循环异常.
7.在发生exception时,要进行线程控制,这里设置线程的局部变量runnable=false;在异常发生时,让while循环结束,在run方法最后释放socket资源.
到此完整解决 2018-1-16