`
san_yun
  • 浏览: 2639423 次
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

java socket的理解-从helo world开始

 
阅读更多

之前我以为java的socket很简单,不就是创建一个socketServet,然后不断的accept么?这种代码网上很多,基本流程是这样的:

ServerSocket server = new ServerSocket(8080);
while (true) {
	Socket socket = server.accept();
	InputStream input = socket.getInputStream();
	int length = input.available();
	if(length>0){
		byte[] data = new byte[length];
		input.read(data);
		String str = new String(data);
		String result = execute(str); 
		OutputStream out = socket.getOutputStream();
		out.write(("ok"+result+"\r\n").getBytes());
		out.flush();
	}
       socket.close();
 }

虽然在做轮询,但accept()会阻塞,直到有新的客户端请求过来才会产生一个新的socket,然后读取数据,处理数据,最后关闭。基本流程是 accept()--->read()--->hand()--->close()。

 

代码看上去很简单,也能成功运行,但这段代码存在2个问题:

1. 一个socket只能处理一次就被关闭了,对于局域网的RPC来说这是很不划算的,很多时候我们希望能是长链接。处理流程变成accept()-->read()-->hand()-->read()-->hand()-->close()

2. 如果一个client在一个socket中连续发送两次数据,client端代码会报错,因为socket在处理玩第一次read数据之后就被server关闭了。

 

所以为了解决上面两个问题,我把这段代码修改一下,每个socket一个线程来处理:

  class Channel implements Runnable {

        private Socket socket;
        private ServiceRegisterTester t ;
        public Channel(Socket socket,ServiceRegisterTester t ){
            super();
            this.socket = socket;
            this.t = t;
        }

        public void run() {
            try{
                Socket socket = this.socket;
                while(true){
                    InputStream input = socket.getInputStream();
                    int length = input.available();
                    if(length>0){
                        byte[] data = new byte[length];
                        input.read(data);
                        String str = new String(data);
                        String result = t.test(str);
                        OutputStream out = socket.getOutputStream();
                        out.write(("ok"+result+"\r\n").getBytes());
                        out.flush();
                    }
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
ServerSocket server = new ServerSocket(8080);
while (true) {
    Socket socket = server.accept();
    Channel chanenl = new Channel(socket,t);
    new Thread(chanenl).start();
}
 

 这样每个socket都有一个单独的线程来处理,前面2个问题解决了!

但通过top观测发现java进程cpu占用非常高,都在做空转,每个socket都有线程在做轮询,这太伤害性能了吧,怎么才能让

socket.getInputStream();

也阻塞,让他能智能的读取到可用的数据才唤醒呢?

 

 

我相信netty也会遇到这个问题看看netty是如何解决的吧:

        ChannelFactory factory =  
                new OioServerSocketChannelFactory (  
                        Executors.newCachedThreadPool(),  
                        Executors.newCachedThreadPool());  
        
        ServerBootstrap bootstrap = new ServerBootstrap (factory);  
        DiscardServerHandler handler = new DiscardServerHandler();
        ChannelPipeline pipeline = bootstrap.getPipeline();  
        pipeline.addLast("handler", handler);  
        bootstrap.setOption("child.tcpNoDelay", true);  
        bootstrap.setOption("child.keepAlive", true);  
        bootstrap.bind(new InetSocketAddress(8080));  

 ChannelFactory需要两个线程池,一个是boss,一个是worker,boss只需要一个线程即可,worker可以根据合适的情况配置。当在执行 bootstrap.bind()的时候会启动boss线程,代码如下:

class OioServerSocketPipelineSink{

	private void bind(
		        OioServerSocketChannel channel, ChannelFuture future,
		        SocketAddress localAddress) {


	 Executor bossExecutor =
		            ((OioServerSocketChannelFactory) channel.getFactory()).bossExecutor;
		        bossExecutor.execute(
		                new IoWorkerRunnable(
		                        new ThreadRenamingRunnable(
		                                new Boss(channel),
		                                "Old I/O server boss (channelId: " +
		                                channel.getId() + ", " + localAddress + ')')));
		        bossStarted = true;
	}
}
 

OioServerSocketPipelineSink&Boss 是一个Runnable,其run方法如下:

 

 while (channel.isBound()) {
        try {
            Socket acceptedSocket = channel.socket.accept();
            try {
                ChannelPipeline pipeline =
                    channel.getConfig().getPipelineFactory().getPipeline();
                final OioAcceptedSocketChannel acceptedChannel =
                    new OioAcceptedSocketChannel(
                            channel,
                            channel.getFactory(),
                            pipeline,
                            OioServerSocketPipelineSink.this,
                            acceptedSocket);
                workerExecutor.execute(
                        new IoWorkerRunnable(
                                new ThreadRenamingRunnable(
                                        new OioWorker(acceptedChannel),
                                        "Old I/O server worker (parentId: " +
                                        channel.getId() + ", channelId: " +
                                        acceptedChannel.getId() + ", " +
                                        channel.getRemoteAddress() + " => " +
                                        channel.getLocalAddress() + ')')));
            } catch (Exception e) {
                logger.warn(
                        "Failed to initialize an accepted socket.", e);
                try {
                    acceptedSocket.close();
                } catch (IOException e2) {
                    logger.warn(
                            "Failed to close a partially accepted socket.",
                            e2);
                }
            }
  }

 可以看到Boss的职责就是轮询获取每个新请求Socket,立即交给workerExecutor处理,workerExecutor的逻辑单元封装在OioWorker,OioWorker的run方法如下:

public void run(){
        final PushbackInputStream in = channel.getInputStream();
        while (channel.isOpen()) {
            synchronized (channel.interestOpsLock) {
                while (!channel.isReadable()) {
                    try {
                        // notify() is not called at all.
                        // close() and setInterestOps() calls Thread.interrupt()
                        channel.interestOpsLock.wait();
                    } catch (InterruptedException e) {
                        if (!channel.isOpen()) {
                            break;
                        }
                    }
                }
            }

            byte[] buf;
            int readBytes;
            try {
                int bytesToRead = in.available();
                if (bytesToRead > 0) {
                    buf = new byte[bytesToRead];
                    readBytes = in.read(buf);
                } else {
                    int b = in.read();
                    if (b < 0) {
                        break;
                    }
                    in.unread(b);
                    continue;
                }
            } catch (Throwable t) {
                if (!channel.socket.isClosed()) {
                    fireExceptionCaught(channel, t);
                }
                break;
            }

            ChannelBuffer buffer;
            if (readBytes == buf.length) {
                buffer = ChannelBuffers.wrappedBuffer(buf);
            } else {
                // A rare case, but it sometimes happen.
                buffer = ChannelBuffers.wrappedBuffer(buf, 0, readBytes);
            }

            fireMessageReceived(channel, buffer);
        }

        // Setting the workerThread to null will prevent any channel
        // operations from interrupting this thread from now on.
        channel.workerThread = null;

        // Clean up.
        close(channel, succeededFuture(channel));

}

 OioWorker里有一个非常重要的InputStream-PushbackInputStream,这个输入流能阻塞io,请看其read()方法的注释:“从此输入流中读取下一个数据字节。返回 0 到 255 范围内的 int 字节值。如果因流的末尾已到达而没有可用的字节,则返回值 -1。在输入数据可用、检测到流的末尾或者抛出异常前,此方法一直阻塞。”
netty就是利用这种方式来做轮询,而CPU又不至于空转。

 

分享到:
评论

相关推荐

    java socket 实现SMTP协议 发送邮件.docx

    Java Socket 实现 SMTP 协议发送邮件 Java Socket 是 Java 语言中用于实现网络编程的 API,通过 Socket,可以实现与远程服务器的通信。在这里,我们将使用 Java Socket 实现 SMTP 协议来发送邮件。 SMTP 协议简介 ...

    用java socket实现smtp邮件发送,支持SSL

    2. 发送EHLO/HELO命令:向服务器表明身份,开始会话。 3. 如果服务器支持STARTTLS,发送STARTTLS命令,升级连接至TLS。如果支持SSL,直接创建SSLSocket并连接。 4. 身份验证:发送用户名和密码,通常使用AUTH PLAIN...

    Java用Socket实现EMAIL的实例.rar_ javamail_java socket _java socket ema

    总的来说,通过Java的Socket实现EMAIL发送,虽然相对复杂,但有助于深入理解网络通信和SMTP协议的工作原理。如果你正在学习网络编程或者想要深入了解邮件发送的底层机制,这是一个非常有价值的实践项目。

    用Java Socket实现SMTP邮件发送

    总的来说,通过Java Socket实现SMTP邮件发送涉及到网络编程、SMTP协议理解、异常处理等多个知识点。这种技术在日常工作中非常实用,可以方便地集成到各种自动化任务或系统中,如定时发送报告、系统通知等。然而,...

    java_socket_smtp.rar_SMTPFrame.java_java base64 smtp_java socket

    SMTP(Simple Mail Transfer Protocol)是互联网上用于发送...如果你想要学习如何用Java发送邮件,理解这个项目中的`SMTPFrame.java`代码将非常有帮助,同时掌握SMTP协议的基本流程和Java Socket编程也是必不可少的。

    Java Socket Mail 源代码

    Java Socket邮件发送是一种基于Java网络编程技术实现的邮件发送方式,主要利用了Socket类来连接SMTP(Simple Mail Transfer Protocol)服务器进行数据传输。SMTP是互联网上用于发送电子邮件的标准协议,而Java ...

    基于java的简单socket邮件发送

    Java中的Socket编程是一种网络通信机制,它允许两个网络应用程序之间进行双向通信。在这个"基于Java的简单socket邮件发送"实验中,我们将深入探讨如何利用Java的Socket类来构建一个基本的邮件发送系统。 首先,我们...

    java 纯Socket发送邮件

    Java纯Socket发送邮件的知识点涉及了Java网络编程和电子邮件协议,特别是SMTP(Simple Mail Transfer Protocol)。在Java中,我们可以使用Socket类直接与SMTP服务器通信,实现邮件的发送。以下是对这个主题的详细...

    java实验三socket.doc

    【Java Socket 编程基础】 ...通过这个实验,学生将深入理解Java Socket编程的原理和实践,掌握SMTP协议的使用,以及如何在Java环境中创建用户友好的图形界面。这为后续的网络编程和应用开发打下了坚实的基础。

    黑鲨helo-刷写第三方rec资源+步骤 实测支持安卓多版本

    黑鲨helo-刷写第三方rec资源+步骤 实测支持安卓多版本 1----请使用本人实测的资源来刷写你的机型 2----资源可以支持不同安卓版本刷写第三方rec 3-----内含刷写资源和详细刷写教程步骤 4-----完美兼容当前此安卓...

    基于socket的邮件发送程序(java编写)

    基于Socket的邮件发送程序(Java编写) 在现代通信中,电子邮件是不可或缺的一部分,而Java作为一种广泛应用的编程语言,提供了多种方式来实现邮件的发送。本文将深入探讨如何使用Java的Socket编程实现SMTP(简单...

    笔者根据smtp协议使用Java Socket写了一个发送邮件的程序,将此与各位分享.doc

    在Java中,我们可以使用Socket编程来实现SMTP客户端,从而发送邮件。下面我们将深入探讨如何使用Java Socket和SMTP协议来构建一个邮件发送程序。 首先,我们需要理解SMTP的工作流程: 1. **连接邮件服务器**:...

    Socket-IO模型全接触.rar_Sockets mail_delphi io_delphi socket_delphi 邮

    在Delphi中,通过Socket连接SMTP服务器,然后按照SMTP命令规范发送一系列指令,如HELO(你好)、MAIL FROM(发件人地址)、RCPT TO(收件人地址)、DATA(邮件内容)和QUIT(结束会话)。 在“Socket-IO模型全接触...

    Hello-Name-LPII:Helo名称LPII

    7. **编译与运行**:理解如何使用JDK的`javac`编译Java源代码,并用`java`命令运行可执行文件。 由于没有具体的代码内容,以上分析是基于一般Java项目的常规结构和功能做出的假设。实际项目中,Matheus Amilton de ...

    java-正则表达式-分组引用介绍

    - **确定多分组的编号**:按照从左至右的顺序,每个左括号遇到时即标记为一个新的分组开始,从1开始计数,后续分组编号依次递增。 #### 正则表达式外部引用分组 除了在正则表达式内部引用分组之外,有时还需要在...

    hello的一个war包

    通过将"hello"的WAR包部署到Glassfish服务器,初学者可以学习和理解如何在实际环境中运行和管理Java Web应用。 【标签】"war包"强调了这个压缩包的核心内容。WAR文件的结构有明确的规定,主要包括以下几个部分: 1...

    基于java的开发源码-邮件服务器源程序.zip

    Java提供JSSE(Java Secure Socket Extension)库来支持SSL/TLS,以及JASPI(Java Authentication and Security Service Provider Interface)来处理认证。 七、邮件解析与MIME标准 邮件内容可能包含多种格式,如...

    Socket收发邮件

    使用Java Socket,我们可以创建一个连接到SMTP服务器的Socket实例,然后通过`PrintWriter`对象向服务器发送如"HELO"、"AUTH LOGIN"、"MAIL FROM:"、"RCPT TO:"和"DATA"等SMTP命令,最后发送邮件内容并关闭连接。...

    r3_helo_

    标题 "r3_helo_" 暗示我们讨论的主题与Java Web开发有关,特别是使用Tomcat服务器和JSP(JavaServer Pages)技术进行文件解析。在这个场景中,"helo"可能是一个简写或者错误拼写,实际可能是“hello”,在编程中常...

Global site tag (gtag.js) - Google Analytics