Java语言Scoket编程及实现原理浅析

上一篇文章中介绍了C语言的Socket编程,其主要目的是说明Socket实现是基于网络层IP协议和传输层TCP协议或UDP协议的,网络层、传输层的协议又是操作系统来实现的,所以在C语言中不同操作系统对应着不同的函数库调用,而Java语言具有平台无关性,也就是说不会因操作系统不同而依赖不同的类库,这篇文章将介绍基本的Socket编程及Java层面的实现原理。

一、Java语言Socket编程

服务端:

package edu.haye.socket;


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


public class SocketServer {
    private final static int PORT = 6666;//指定监听端口

    public static void main(String[] args) {

        try {
            //创建ServerSocket对象实例
            ServerSocket serverSocket = new ServerSocket(PORT);
            //调用accept()方法进入监听状态
            Socket socket = serverSocket.accept();
            //获取输入流
            InputStream is = socket.getInputStream();
            //获取输出流
            OutputStream os=socket.getOutputStream();
            PrintWriter pw=new PrintWriter(os);
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br=new BufferedReader(isr);

            Scanner sc=new Scanner(System.in);
            pw.println("连接成功!你先发言");
            pw.flush();
            while (true){
                System.out.println("来自客户端的消息:"+br.readLine());
                pw.println(sc.nextLine());
                pw.flush();
            }


        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

客户端:

package edu.haye.socket;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class SocketClient {
    private final static String HOST="127.0.0.1";
    private final static int PORT=6666;
    public static void main(String[] args) {

        try {
            //创建Socket实例对象
            Socket socket = new Socket(HOST,PORT);
            //获取输出流
            OutputStream os = socket.getOutputStream();
            //获取输入流
            InputStream is=socket.getInputStream();
            OutputStreamWriter osr=new OutputStreamWriter(os);
            InputStreamReader isr=new InputStreamReader(is);
            BufferedReader br=new BufferedReader(isr);
            PrintWriter pw=new PrintWriter(osr);
            Scanner sc=new Scanner(System.in);
            while (true){
                System.out.println("来自服务端的消息:"+br.readLine());
                pw.println(sc.nextLine());
                pw.flush();
            }



        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

得益于Java面向对象的加持,Socket在Java上的编码相比于C更加简单易懂,服务端、客户端大致可简单归纳为以下几个步骤;

1、服务端:

1)创建SocketServer对象,构造器传入参数为监听端口号;

2)调用SocketServer对象的accpet()方法,获取Socket对象,此时程序进入阻塞状态直到链接出现;

3)通过Socket对象的getInputStream/getOutputStream方法获取输入/输出流;

4)依靠字节流进行数据的传输;

2、客户端:

1)创建Socket对象,构造器参数传入服务端IP及端口号;

2)通过Socket对象的getInputStream/getOutputStream方法获取输入/输出流;

3)依靠字节流进行数据的传输;

二、Java层面实现原理

以SocketServer对象的构建为例

public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
        setImpl();
        if (port < 0 || port > 0xFFFF)
            throw new IllegalArgumentException(
                       "Port value out of range: " + port);
        if (backlog < 1)
          backlog = 50;
        try {
            bind(new InetSocketAddress(bindAddr, port), backlog);
        } catch(SecurityException e) {
            close();
            throw e;
        } catch(IOException e) {
            close();
            throw e;
        }
    }

构造方法中除了对端口号范围的检查还调用了两个私有方法setImpl()以及bind(),从函数名中可以简单推断两个方法的作用,setImpl()用来实现实例,bind()用来绑定IP以及端口,我们重点看一下setImpl()方法:

setImpl()

private void setImpl() {
        if (factory != null) {
            impl = factory.createSocketImpl();
            checkOldImpl();
        } else {
            // No need to do a checkOldImpl() here, we know it's an up to date
            // SocketImpl!
            impl = new SocksSocketImpl();
        }
        if (impl != null)
            impl.setServerSocket(this);
    }

方法首先判断了工厂实例是否存在,如果存在则使用工厂方法创建SocksSocketImpl实例对象,工厂可以通过ServerSocket类的静态方法setSocketFactory配置,且该配置方法仅可以调用一次,多次调用将抛出异常,我们的代码中并没有指定工厂对象,所以我们关注JDK提供的默认 SocksSocketImpl对象。

Java语言Scoket编程及实现原理浅析

从上图中我们可以看到SocksSocketImpl是PlainScoketImpl的直接子类,而在PlainScoketImpl中定义了大量的native方法:

native void socketCreate(boolean isServer) throws IOException;

    native void socketConnect(InetAddress address, int port, int timeout)
        throws IOException;

    native void socketBind(InetAddress address, int port)
        throws IOException;

    native void socketListen(int count) throws IOException;

    native void socketAccept(SocketImpl s) throws IOException;

    native int socketAvailable() throws IOException;

    native void socketClose0(boolean useDeferredClose) throws IOException;

    native void socketShutdown(int howto) throws IOException;

    static native void initProto();

    native void socketSetOption0(int cmd, boolean on, Object value)
        throws SocketException;

    native int socketGetOption(int opt, Object iaContainerObj) throws SocketException;

    native void socketSendUrgentData(int data) throws IOException;

这些方法完成了Socket从创建到监听/链接、关闭的所有步骤,但是他们并非由Java代码实现,native调用将由JVM实现。JVM是由C/C++编写而成,前面已经说过了,C语言的Socket编程需要根据操作系统的不同而引入不同的函数库,所以JVM并不是通用的,不同的操作系统有着不同的JVM环境,他会根据不同的操作系统作出不同的实现,而暴露出相同的接口供Java代码调用。

上一篇:scoket


下一篇:01-MySql的前戏