一次socket长连接运行导致的性能问题

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");
        }
    }
}
服务端代码
实现一个守护线程进行socket监听客户端连接,每个客户端socket都会建立一个新的处理线程处理客户端的请求,并返回心跳包。

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.从上图可以看到多个客户端请求响应,服务端内存回收的频率加快了.

客户端正常运行
3.上图客户端正常运行的内存占用

当服务端意外断开,客户端运行
4.上图是当服务器端意外断开或者出现网络超时,客户端出现了OOM.

异常运行的客户端线程运行,Thread-1是发送线程
5.当出现网络超时时,发送线程并没有正常退出.
通过下面的日志输出可以看到,IOException一直在报错,我尝试在异常中增加socket关闭操作,然而并没有用.不是socket没关闭造成的.
thread-3是接收线程

IO异常产生,但是处理不正确
6.真正引起OOM的是线程处于不可控状态,while一直在循环异常.
正常处理客户端异常后
7.在发生exception时,要进行线程控制,这里设置线程的局部变量runnable=false;在异常发生时,让while循环结束,在run方法最后释放socket资源.

到此完整解决 2018-1-16

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
写高性能的 C++ socket 服务需要注意以下几点: 1. 使用非阻塞 I/O:在 C++ 中,可以使用 `select`、`poll`、`epoll` 等机制实现非阻塞 I/O。非阻塞 I/O 可以让程序在等待数据时不会停止运行,从而提高程序的效率。 2. 使用多线程或多进程:可以使用多线程或多进程来处理并发请求。在 C++ 中,可以使用 `pthread`、`boost.thread`、`std::thread`、`fork` 等机制实现多线程或多进程。 3. 使用高效的数据结构:在处理请求时,使用高效的数据结构可以大大提高程序的效率。如哈希表、红黑树、堆等。 4. 避免拷贝操作:在 C++ 中,拷贝对象需要耗费大量的时间。为了避免拷贝操作,可以使用引用或指针。 5. 使用内存池:在程序中频繁地申请和释放内存会导致大量的时间浪费。使用内存池可以避免这种浪费,从而提高程序的效率。 下面是一个简单的 C++ socket 服务的代码示例: ```c++ #include <iostream> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> using namespace std; int main() { int listen_fd, conn_fd; struct sockaddr_in serv_addr, client_addr; socklen_t client_len = sizeof(client_addr); // 创建 socket listen_fd = socket(AF_INET, SOCK_STREAM, 0); // 初始化 serv_addr bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(8080); // 绑定 socket bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); // 监听 socket listen(listen_fd, 10); while (true) { // 接受连接请求 conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len); // 处理请求 char buffer[1024]; int n = read(conn_fd, buffer, sizeof(buffer)); write(conn_fd, buffer, n); // 关闭连接 close(conn_fd); } return 0; } ``` 这段代码实现了一个简单的 HTTP 服务,它会监听 8080 端口,并在接收到请求后将请求内容原封不动地返回给客户端。当然,这段代码还需要进一步完善,例如处理请求头、错误处理等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值