JAVA I/O(四)网络Socket和ServerSocket

《Thinking in Enterprise Java》中第一章描述了用Socket和Channel的网络编程,核心即为Socket和Channel,本文简单讲述Socket的应用。

Socket可以认为是两个互联机器终端应用软件的抽象,即对于一个网络连接,两端都有一个Socket,应用可以通过套接字进行交互通信。

在Java中,创建Socket连接另一台机器,可以从Socket中获取InputStream和OutputStream,将其作为输入输出流,使应用程序与操作本地文件IO类似。存在2个基于流的Socket类:ServerSocket和Socket。

  • ServerSocket用于服务器端,监听客户端连接
  • Socket用于客户端与服务端交互
  • 服务段accept()方法处于阻塞状态,直到有客户端连接,创建一个服务端Socket,与客户端交互

另外,当创建ServerSocket时,只需要提供一个端口号,IP信息为本机默认信息;创建Socket时,必须提供IP和端口号;由ServerSocket.accept( )创建的不需要,其已包含所有信息。

1. 简单客户端和服务端

服务器端:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class JabberServer {

    public static final int PORT = 8080;
    public static void main(String[] args) throws IOException{

        ServerSocket server = new ServerSocket(PORT);
        System.out.println("开始: " + server);
        try {
            Socket socket = server.accept();
            System.out.println("Connection socket: " + socket);
            try {
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                //Output is automatically flushed by PrintWrite
                PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
                while(true) {
                    String str = in.readLine();
                    if("END".equals(str))
                        break;
                    System.out.println("Echoing: " + str);
                    out.println(str);
                }
            } finally {
                System.out.println("CLosing....");
                socket.close();
            }
        } finally {
            server.close();
        }
    }
}

输出:

开始: ServerSocket[addr=0.0.0.0/0.0.0.0,localport=8080]

大致步骤:

  • 创建ServerSocket,绑定端口8080
  • 调accept()方法监听连接,并返回套接字Socket
  • 获取输入流,并通过InputStreamReader转为字符,缓存在BufferdReader中
  • 获取输出流,通过OutputStreamWriter将BufferedWriter中的字符转换为字节,并通过PrintWriter格式化输出,同时自动flush
  • 根据输入流读取的字符,如果是END则结束会话
  • 关闭套接字和ServerSocket

客户端:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;

/**
 * 根据服务器ip和端口/服务器地址等,创建Socket
 * Socket可以获取输入和输出流,默认是使用AbstractPlainSocketImpl类中的SocketInputStream和SocketOutputStream
 *
 */
public class JabberClient {

    public static void main(String[] args) throws Exception{

        //服务器端信息,address和8080;后台连接服务器,还会绑定客户端
        InetAddress address = InetAddress.getByName(null);
        System.out.println("address = " + address);
        Socket socket = new Socket(address, 8080);

        try {
            System.out.println("Socket = " +socket);
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);

            for(int i = 0; i < 10; i++) {
                out.println("hello " + i);
                String str = in.readLine();
                System.out.println(str);
            }
            out.println("END");
        } finally {
            System.out.println("Closing socket...");
            socket.close();
        }
    }
}

大致步骤与服务端相似,不同之处在于创建客户端套接字,需要指定服务端地址和端口号。

服务器端输出:

Connection socket: Socket[addr=/127.0.0.1,port=35702,localport=8080]
Echoing: hello 0
Echoing: hello 1
Echoing: hello 2
Echoing: hello 3
Echoing: hello 4
Echoing: hello 5
Echoing: hello 6
Echoing: hello 7
Echoing: hello 8
Echoing: hello 9
CLosing....

客户端输出:

address = localhost/127.0.0.1
Socket = Socket[addr=localhost/127.0.0.1,port=8080,localport=35702]
hello 0
hello 1
hello 2
hello 3
hello 4
hello 5
hello 6
hello 7
hello 8
hello 9
Closing socket...

2. 源码分析

无论是创建ServerSocket还是Socket,都需要与底层实现SocketImpl关联,以实现具体网络交互的逻辑。

(1)服务端创建ServerSocket

ServerSocket server = new ServerSocket(PORT);

具体构造过程如下,构造参数为服务端监听端口:

 /**
     * Creates a server socket, bound to the specified port. A port number
     * of {@code 0} means that the port number is automatically
     * allocated, typically from an ephemeral port range. This port
     * number can then be retrieved by calling {@link #getLocalPort getLocalPort}.
     * <p>
     * The maximum queue length for incoming connection indications (a
     * request to connect) is set to {@code 50}. If a connection
     * indication arrives when the queue is full, the connection is refused.
     * <p>
     * If the application has specified a server socket factory, that
     * factory‘s {@code createSocketImpl} method is called to create
     * the actual socket implementation. Otherwise a "plain" socket is created.
     * <p>
     * If there is a security manager,
     * its {@code checkListen} method is called
     * with the {@code port} argument
     * as its argument to ensure the operation is allowed.
     * This could result in a SecurityException.
     *
     *
     * @param      port  the port number, or {@code 0} to use a port
     *                   number that is automatically allocated.
     *
     * @exception  IOException  if an I/O error occurs when opening the socket.
     * @exception  SecurityException
     * if a security manager exists and its {@code checkListen}
     * method doesn‘t allow the operation.
     * @exception  IllegalArgumentException if the port parameter is outside
     *             the specified range of valid port values, which is between
     *             0 and 65535, inclusive.
     *
     * @see        java.net.SocketImpl
     * @see        java.net.SocketImplFactory#createSocketImpl()
     * @see        java.net.ServerSocket#setSocketFactory(java.net.SocketImplFactory)
     * @see        SecurityManager#checkListen
     */
    public ServerSocket(int port) throws IOException {
        this(port, 50, null);
    }

/**
     * Create a server with the specified port, listen backlog, and
     * local IP address to bind to.  The <i>bindAddr</i> argument
     * can be used on a multi-homed host for a ServerSocket that
     * will only accept connect requests to one of its addresses.
     * If <i>bindAddr</i> is null, it will default accepting
     * connections on any/all local addresses.
     * The port must be between 0 and 65535, inclusive.
     * A port number of {@code 0} means that the port number is
     * automatically allocated, typically from an ephemeral port range.
     * This port number can then be retrieved by calling
     * {@link #getLocalPort getLocalPort}.
     *
     * <P>If there is a security manager, this method
     * calls its {@code checkListen} method
     * with the {@code port} argument
     * as its argument to ensure the operation is allowed.
     * This could result in a SecurityException.
     *
     * The {@code backlog} argument is the requested maximum number of
     * pending connections on the socket. Its exact semantics are implementation
     * specific. In particular, an implementation may impose a maximum length
     * or may choose to ignore the parameter altogther. The value provided
     * should be greater than {@code 0}. If it is less than or equal to
     * {@code 0}, then an implementation specific default will be used.
     * <P>
     * @param port  the port number, or {@code 0} to use a port
     *              number that is automatically allocated.
     * @param backlog requested maximum length of the queue of incoming
     *                connections.
     * @param bindAddr the local InetAddress the server will bind to
     *
     * @throws  SecurityException if a security manager exists and
     * its {@code checkListen} method doesn‘t allow the operation.
     *
     * @throws  IOException if an I/O error occurs when opening the socket.
     * @exception  IllegalArgumentException if the port parameter is outside
     *             the specified range of valid port values, which is between
     *             0 and 65535, inclusive.
     *
     * @see SocketOptions
     * @see SocketImpl
     * @see SecurityManager#checkListen
     * @since   JDK1.1
     */
    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;
        }
    }

实际调用的构造方法为ServerSocket(int port, int backlog, InetAddress bindAddr),port表示端口号,backlog表示服务端请求连接队列最大数(默认为50),bindAddr表示服务器要绑定的本地地址(默认为null)。

  • setImpl(),设置系统默认类型SocketImpl,其是服务端和客户端套接字创建、连接、交互等操作的核心
  • bind(new InetSocketAddress(bindAddr, port), backlog),绑定对应的地址和端口,并设置最大连接数(超过连接数,服务器拒绝连接)

(2)服务端accept

Socket socket = server.accept();

服务端会阻塞等待客户端连接,直到有客户端连接,并创建一个服务端Socket,与客户端交互。

/**
     * Listens for a connection to be made to this socket and accepts
     * it. The method blocks until a connection is made.
     *
     * <p>A new Socket {@code s} is created and, if there
     * is a security manager,
     * the security manager‘s {@code checkAccept} method is called
     * with {@code s.getInetAddress().getHostAddress()} and
     * {@code s.getPort()}
     * as its arguments to ensure the operation is allowed.
     * This could result in a SecurityException.
     *
     * @exception  IOException  if an I/O error occurs when waiting for a
     *               connection.
     * @exception  SecurityException  if a security manager exists and its
     *             {@code checkAccept} method doesn‘t allow the operation.
     * @exception  SocketTimeoutException if a timeout was previously set with setSoTimeout and
     *             the timeout has been reached.
     * @exception  java.nio.channels.IllegalBlockingModeException
     *             if this socket has an associated channel, the channel is in
     *             non-blocking mode, and there is no connection ready to be
     *             accepted
     *
     * @return the new Socket
     * @see SecurityManager#checkAccept
     * @revised 1.4
     * @spec JSR-51
     */
    public Socket accept() throws IOException {
        if (isClosed())
            throw new SocketException("Socket is closed");
        if (!isBound())
            throw new SocketException("Socket is not bound yet");
        Socket s = new Socket((SocketImpl) null);
        implAccept(s);
        return s;
    }

① 指定SocketImpl,创建一个非连接的Socket构造方法,如下:

 /**
     * Creates an unconnected Socket with a user-specified
     * SocketImpl.
     * <P>
     * @param impl an instance of a <B>SocketImpl</B>
     * the subclass wishes to use on the Socket.
     *
     * @exception SocketException if there is an error in the underlying protocol,
     * such as a TCP error.
     * @since   JDK1.1
     */
    protected Socket(SocketImpl impl) throws SocketException {
        this.impl = impl;
        if (impl != null) {
            checkOldImpl();
            this.impl.setSocket(this);
        }
    }

② implAccept(s)方法

上一步创建Socket的参数SocketImpl为null,该方法为Socket创建具体的SocketImpl,绑定地址和文件描述符,具体可见源码。

(3)客户端创建套接字

Socket socket = new Socket(address, 8080);

具体创建过程如下,构造参数为InetAddress和port:

/**
     * Creates a stream socket and connects it to the specified port
     * number at the specified IP address.
     * <p>
     * If the application has specified a socket factory, that factory‘s
     * {@code createSocketImpl} method is called to create the
     * actual socket implementation. Otherwise a "plain" socket is created.
     * <p>
     * If there is a security manager, its
     * {@code checkConnect} method is called
     * with the host address and {@code port}
     * as its arguments. This could result in a SecurityException.
     *
     * @param      address   the IP address.
     * @param      port      the port number.
     * @exception  IOException  if an I/O error occurs when creating the socket.
     * @exception  SecurityException  if a security manager exists and its
     *             {@code checkConnect} method doesn‘t allow the operation.
     * @exception  IllegalArgumentException if the port parameter is outside
     *             the specified range of valid port values, which is between
     *             0 and 65535, inclusive.
     * @exception  NullPointerException if {@code address} is null.
     * @see        java.net.Socket#setSocketImplFactory(java.net.SocketImplFactory)
     * @see        java.net.SocketImpl
     * @see        java.net.SocketImplFactory#createSocketImpl()
     * @see        SecurityManager#checkConnect
     */
    public Socket(InetAddress address, int port) throws IOException {
        this(address != null ? new InetSocketAddress(address, port) : null,
             (SocketAddress) null, true);
    }

private Socket(SocketAddress address, SocketAddress localAddr,
                   boolean stream) throws IOException {
        setImpl();

        // backward compatibility
        if (address == null)
            throw new NullPointerException();

        try {
            createImpl(stream);
            if (localAddr != null)
                bind(localAddr);
            connect(address);
        } catch (IOException | IllegalArgumentException | SecurityException e) {
            try {
                close();
            } catch (IOException ce) {
                e.addSuppressed(ce);
            }
            throw e;
        }
    }
  • 首先,setImpl(),与服务端相似,设置系统默认类型SocketImpl,其是服务端和客户端套接字创建、连接、交互等操作的核心
  • 其次,createImpl(stream),根据stream布尔值创建socket实现,true时创建基于流的socket(或者面向连接),false时创建无连接UDP套接字
  • 最后,connect(address),连接服务器,连接一直处于阻塞状态,直到连接成功,或者超时或报错等

(4)SocketImpl类

SocketImpl类是服务器和客户端连接的核心,源码如下,包含Socket、ServerSocket、文件描述符、IP地址、端口号和套接字连接的本地端口号:

/**
 * The abstract class {@code SocketImpl} is a common superclass
 * of all classes that actually implement sockets. It is used to
 * create both client and server sockets.
 * <p>
 * A "plain" socket implements these methods exactly as
 * described, without attempting to go through a firewall or proxy.
 *
 * @author  unascribed
 * @since   JDK1.0
 */
public abstract class SocketImpl implements SocketOptions {
    /**
     * The actual Socket object.
     */
    Socket socket = null;
    ServerSocket serverSocket = null;

    /**
     * The file descriptor object for this socket.
     */
    protected FileDescriptor fd;

    /**
     * The IP address of the remote end of this socket.
     */
    protected InetAddress address;

    /**
     * The port number on the remote host to which this socket is connected.
     */
    protected int port;

    /**
     * The local port number to which this socket is connected.
     */
    protected int localport;

类结构如下:

(5)套接字输入输出流

SocketImpl默认子类是AbstractPlainSocketImpl,大部分套接字的创建、连接等操作都通过该类进行。套接字通过获取输入输出流,使应用可以像操作本地I/O流一样,操作网络数据。

Socket获取输入输出流的方法是getInputStream()和getOutputStream(),底层调AbstractPlainSocketImpl的方法获取,实际流对象为SocketInputStream和SocketOutputStream,具体细节此处不阐述。

socket.getInputStream()//获取输入流
socket.getOutputStream()//获取输出流

AbstractPlainSocketImpl类中包含2个套接字输入输出流属性,如下:

 private SocketInputStream socketInputStream = null;
 private SocketOutputStream socketOutputStream = null;

他们分别继承自FileInputStream和FileOutputStream,以表示输入输出源。并且他们都不是public类型的,一般不会直接使用。

class SocketInputStream extends FileInputStream
class SocketOutputStream extends FileOutputStream 

3. 总结

1. 网络连接的核心是套接字Socket,服务端ServerSocket监听连接,创建套接字;客户端创建套接字,绑定服务端ip和端口

2. SocketImpl类和子类AbstractPlainSocketImpl是服务端和客户端套接字创建、连接、交互的核心

3. 通过套接字获取输入输出流,应用程序可以与本地I/O流操作一样

原文地址:https://www.cnblogs.com/shuimuzhushui/p/10322995.html

时间: 2024-10-11 02:00:12

JAVA I/O(四)网络Socket和ServerSocket的相关文章

java之十四 网络连接

1969年,KenThompson和Dennis Ritchie在MurrayHill,New Jersey的贝尔电话实验室开发了与C语言一致的UNIX.很多年来,UNIX的发展停留在贝尔实验室和一些大学及研究机构,用特意设计的DEC PDP机器运行.到了1978 年,Bill Joy在Cal Berkeley领导了一个项目,给UNIX增添新的特性,例如虚拟内存和全屏显示功能.到了1984年早期,当Bill正准备建立Sun Microsystems,它发明了4.2BSD,即众所周知的Berkel

JAVA套接字(Socket)101七天系列—第四天【一个简单示例】

一个简单示例  1. 背景 我们将在本部分讨论的示例将阐明在 Java 代码中如何使用 Socket 和 ServerSocket.客户机用Socket 连接到服务器.服务器用 ServerSocket 在端口 3000 侦听.客户机请求服务器 C: 驱动器上的文件内容. 为清楚起见,我们把示例分解成客户机端和服务器端.最后我们将把它们组合起来以使您能看到整体模样. 我们在使用 JDK 1.2 的 IBM VisualAge for Java 3.5 上开发这些代码.要自己创建这个示例,您应有完

Java网络编程(ServerSocket和Socket)

服务器端 package org.tcp; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; public class HelloServer { public static void main(String[] args) throws Exception{ ServerSocket server = null; Socket client = null; PrintStream

JAVA与网络开发(TCP:Socket、ServerSocket;UDP:DatagramSocket、DatagramPacket;多线程的C/S通讯、RMI开发概述)

通过TCP建立可靠通讯信道 1)为了对应TCP协议里的客户端和服务器端,Socket包提供了Socket类和ServerSocket类. 2)Socket类构造函数及相关方法 Public Socket(); public Socket(InetAddress address,int port);//本机IP和端口 public Socket(Striing host,int port);//本机IP和端口 public void connect(SocketAddress endpoint);

JAVA: Socket和ServerSocket网络编程

面是本次学习的笔记.主要分异常类型.交互原理.Socket.ServerSocket.多线程这几个方面阐述. 异常类型 在了解Socket的内容之前,先要了解一下涉及到的一些异常类型.以下四种类型都是继承于IOException,所以很多之后直接弹出IOException即可. UnkownHostException:    主机名字或IP错误 ConnectException:    服务器拒绝连接.服务器没有启动.(超出队列数,拒绝连接) SocketTimeoutException:   

Java基础知识—网络Socket(六)

概述 网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来. java.net 包中提供了两种常见的网络协议的支持: TCP:TCP 是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信.通常用于互联网协议,被称 TCP / IP. UDP:UDP 是用户数据报协议的缩写,一个无连接的协议.提供了应用程序之间要发送的数据的数据包. Socket编程 套接字使用TCP提供了两台计算机之间的通信机制. 客户端程序创建一个套接字,并尝试连接服务器的套接字.当连接建立时,服务器

Java从零开始学四十五(Socket编程基础)

一.网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机. 而TCP层则提供面向应用的可靠(tcp)的或非可靠(UDP)的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的. 目前较为流行的网络编程模型是客户机/服务器(C/S)结构.即通信双方一方作为服务器等待客户提出请求并予以响应.客户则

JAVA基础知识之网络编程——-TCP/IP协议,socket通信,服务器客户端通信demo

OSI模型分层 OSI模型是指国际标准化组织(ISO)提出的开放系统互连参考模型(Open System Interconnection Reference Model,OSI/RM),它将网络分为七层:物理层,数据链路层,网络层,传输层,会话层,表示层,应用层 TCP/IP协议 TCP/IP是一系列网络通信协议的统称,其中最核心的两个协议是TCP和IP.TCP称为传输控制协议,IP称为互联网络协议. 网络分层除了OSI模型分层,还有TCP/IP模型分层,将网络划分为四层,应用层.传输层.网际层

JAVA学习之TCP网络编程,Socket使用

ServerSocket 此类实现服务器套接字. ServerSocket常用的构造方法有以下几个, ServerSocket() 创建非绑定服务器套接字. ServerSocket(int port) 创建绑定到特定端口的服务器套接字. ServerSocket(int port, int backlog) 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号. ServerSocket(int port, int backlog, InetAddress bindAddr