软件系统架构分析之一:传统socket通讯阻塞现象分析

如果想要做一个高性能的软件系统,如何处理好系统各方面的瓶颈问题非常重要。

我觉得在一个基于Java的系统中,最容易出现性能瓶颈的地方就在线程、IO、数据库这几个方面。Socket通讯是我们系统间最常用的通讯方式之一,而Socket通讯又是伴随着大量IO操作,而且,因为socket的连接、读、写都是阻塞的,容易成为最明显的系统性能瓶颈。这篇文章记录一下自己对于socket阻塞现象的分析。

socket主要在accept方法,read方法,write方法上阻塞,从而影响性能。

1. 先上一段Server端TcpServer的代码:

package com.john;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {
    public static void main(String args[]) throws IOException, InterruptedException {
        ServerSocket server = new ServerSocket(5050);
        System.out.println("0.server start ...");
        while (true) {
            long startTime1 = System.currentTimeMillis();

            System.out.println("1.waiting for client connect....");
            System.out.println("2.Begin accept!");

            Socket socket = server.accept();
            System.out.println("Thread:" + Thread.currentThread().getName() + " and client socek:"+socket.getPort());
            System.out.println("3.Client accept!" + socket);

            InputStream in = socket.getInputStream();
            OutputStream out = socket.getOutputStream();

            long startTime2 = System.currentTimeMillis();
            char c = (char) in.read();
            long endTime2 = System.currentTimeMillis();
            System.out.println("read timeUsed===>>>" + (endTime2- startTime2));

            Thread.sleep(5000);
            System.out.println("4.server receive msg===>>>" + c);

            out.write(c);
            out.flush();
            in.close();
            socket.close();

            long endTime1 = System.currentTimeMillis();
            System.out.println(socket.getPort() + "" +
                    "client total timeUsed===>>>" + (endTime1 - startTime1));
        }
    }
}

启动socket server,能看到以下日志:

0.server start ...
1.waiting for client connect....
2.Begin accept!

说明此时accept是阻塞的,等待客户端连接过来。

2. 然后,我们来看客户端TcpClient的代码:

package com.john;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class TcpClient {

    public static void main(String args[]) throws IOException, InterruptedException {

        //for (int i = 0; i < 5; i++) {
            long startTime = System.currentTimeMillis();
            Socket client = new Socket("127.0.0.1", 5050);

            InputStream in = client.getInputStream();
            OutputStream out = client.getOutputStream();

            Thread.sleep(5000);
            out.write('a');
            out.flush();
            System.out.println("client send data===>>>" + 'a');
            long startTime2 = System.currentTimeMillis();
            char c = (char) in.read();
            long endTime2 = System.currentTimeMillis();
            System.out.println("return data===>>>" + c);
            System.out.println("read timeUsed===>>>" + (endTime2- startTime2));


            out.close();
            in.close();
            client.close();

            long endTime = System.currentTimeMillis();
            long timeUsed = endTime - startTime;
            System.out.println("total timeUsed===============>>>>>>>>>>>>>>" + timeUsed);
        //}
    }
}
TcpClient的代码中,在连接到TcpServer后设置了5秒的等待时间。
运行TcpClient后,我们在服务端TcpServer的日志中能看到:
1.waiting for client connect....
2.Begin accept!
Thread:main and client socek:54848
3.Client accept!Socket[addr=/127.0.0.1,port=54848,localport=5050]
(在此处能看到光标等待了5秒时间,即服务端的in.read()方法是阻塞的)
read timeUsed===>>>5002
4.server receive msg===>>>a

然后因为在TcpServer的代码中,在返回客户端之前我们也设置了5秒的等待时间,然后我们在客户端TcpClient的日志中也能看到:
client send data===>>>a
(在此处能看到光标等待了5秒时间,即客户端in.read()方法是阻塞的)
return data===>>>a
read timeUsed===>>>5002
total timeUsed===============>>>>>>>>>>>>>>10012

3. 我们多运行几次TcpClient,能看到TcpServer的日志中:
1.waiting for client connect....
2.Begin accept!
Thread:main and client socek:54883
3.Client accept!Socket[addr=/127.0.0.1,port=54883,localport=5050]
read timeUsed===>>>5002
4.server receive msg===>>>a
1.waiting for client connect....
2.Begin accept!
服务端TcpServer中始终都是main单线程在accept()方法处阻塞,等待客户端连接过来。
4.最后,我们给TcpClient加上for循环(将代码中的for循环注释打开)并将5秒的等待时间去掉,多运行几个client客户端(3个),会发现如下现象:
TcpServer的日志能看到,其对所有的客户端处理时间都是5秒(因为设置了延时5秒):
3.Client accept!Socket[addr=/127.0.0.1,port=54999,localport=5050]
read timeUsed===>>>0
4.server receive msg===>>>a
54999client total timeUsed===>>>5002
1.waiting for client connect....
2.Begin accept!
Thread:main and client socek:55000
3.Client accept!Socket[addr=/127.0.0.1,port=55000,localport=5050]
read timeUsed===>>>1
4.server receive msg===>>>a
55000client total timeUsed===>>>5002
但是客户端的日志中,能看到3个客户端的等待时间却是15秒!
read timeUsed===>>>15010
total timeUsed===============>>>>>>>>>>>>>>15014
client send data===>>>a
return data===>>>a
read timeUsed===>>>15019
total timeUsed===============>>>>>>>>>>>>>>15021

这又是怎么回事呢?
因为服务器是单线程的,只能依次处理3个客户端的请求,每个客户端接收到的返回延时会随着客户端的增多而翻倍!

5.总结
传统socket程序因为使用的是传统的IO流,在accept连接,read、write数据时都是阻塞的,线程会一直等待流结果后才能继续下一步运作,这极大地降低了线程的使用效率。
如果要提高客户端响应速度,只能增加线程数,即多线程的socket编程,我们下一篇文章来讲述这部分内容。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值