《Thinking in Enterprise Java》中第一章描述了用Socket和Channel的網絡編程,核心即爲Socket和Channel,本文簡單講述Socket的應用。java
Socket能夠認爲是兩個互聯機器終端應用軟件的抽象,即對於一個網絡鏈接,兩端都有一個Socket,應用能夠經過套接字進行交互通訊。編程
在Java中,建立Socket鏈接另外一臺機器,能夠從Socket中獲取InputStream和OutputStream,將其做爲輸入輸出流,使應用程序與操做本地文件IO相似。存在2個基於流的Socket類:ServerSocket和Socket。緩存
另外,當建立ServerSocket時,只須要提供一個端口號,IP信息爲本機默認信息;建立Socket時,必須提供IP和端口號;由ServerSocket.accept( )建立的不須要,其已包含全部信息。服務器
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]
大體步驟:app
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(); } } }
大體步驟與服務端類似,不一樣之處在於建立客戶端套接字,須要指定服務端地址和端口號。less
服務器端輸出:socket
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....
客戶端輸出:ide
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...
不管是建立ServerSocket仍是Socket,都須要與底層實現SocketImpl關聯,以實現具體網絡交互的邏輯。源碼分析
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)。
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,綁定地址和文件描述符,具體可見源碼。
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; } }
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;
類結構以下:
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
1. 網絡鏈接的核心是套接字Socket,服務端ServerSocket監聽鏈接,建立套接字;客戶端建立套接字,綁定服務端ip和端口
2. SocketImpl類和子類AbstractPlainSocketImpl是服務端和客戶端套接字建立、鏈接、交互的核心
3. 經過套接字獲取輸入輸出流,應用程序能夠與本地I/O流操做同樣