java的serversocket_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. 简单客户端和服务端

服务器端:

importjava.io.BufferedReader;importjava.io.BufferedWriter;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.net.ServerSocket;importjava.net.Socket;public classJabberServer {public static final int PORT = 8080;public static void main(String[] args) throwsIOException{

ServerSocket server= newServerSocket(PORT);

System.out.println("开始: " +server);try{

Socket socket=server.accept();

System.out.println("Connection socket: " +socket);try{

BufferedReader in= new BufferedReader(newInputStreamReader(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

客户端:

importjava.io.BufferedReader;importjava.io.BufferedWriter;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.net.InetAddress;importjava.net.Socket;/*** 根据服务器ip和端口/服务器地址等,创建Socket

* Socket可以获取输入和输出流,默认是使用AbstractPlainSocketImpl类中的SocketInputStream和SocketOutputStream**/

public classJabberClient {public static void main(String[] args) throwsException{//服务器端信息,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(newInputStreamReader(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: hello0Echoing: hello1Echoing: hello2Echoing: hello3Echoing: hello4Echoing: hello5Echoing: hello6Echoing: hello7Echoing: hello8Echoing: hello9CLosing....

客户端输出:

address = localhost/127.0.0.1Socket= Socket[addr=localhost/127.0.0.1,port=8080,localport=35702]

hello0hello1hello2hello3hello4hello5hello6hello7hello8hello9Closing 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 {@code0} 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}.

*

* The maximum queue length for incoming connection indications (a

* request to connect) is set to {@code50}. If a connection

* indication arrives when the queue is full, the connection is refused.

*

* If the application has specified a server socket factory, that

* factory's {@codecreateSocketImpl} method is called to create

* the actual socket implementation. Otherwise a "plain" socket is created.

*

* If there is a security manager,

* its {@codecheckListen} method is called

* with the {@codeport} argument

* as its argument to ensure the operation is allowed.

* This could result in a SecurityException.

*

*

*@paramport the port number, or {@code0} to use a port

* number that is automatically allocated.

*

*@exceptionIOException if an I/O error occurs when opening the socket.

*@exceptionSecurityException

* if a security manager exists and its {@codecheckListen}

* method doesn't allow the operation.

*@exceptionIllegalArgumentException if the port parameter is outside

* the specified range of valid port values, which is between

* 0 and 65535, inclusive.

*

*@seejava.net.SocketImpl

*@seejava.net.SocketImplFactory#createSocketImpl()

*@seejava.net.ServerSocket#setSocketFactory(java.net.SocketImplFactory)

*@seeSecurityManager#checkListen*/

public ServerSocket(int port) throwsIOException {this(port, 50, null);

}/*** Create a server with the specified port, listen backlog, and

* local IP address to bind to. The bindAddr argument

* can be used on a multi-homed host for a ServerSocket that

* will only accept connect requests to one of its addresses.

* If bindAddr 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 {@code0} 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}.

*

*

If there is a security manager, this method

* calls its {@codecheckListen} method

* with the {@codeport} argument

* as its argument to ensure the operation is allowed.

* This could result in a SecurityException.

*

* The {@codebacklog} 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 {@code0}. If it is less than or equal to

* {@code0}, then an implementation specific default will be used.

*

*@paramport the port number, or {@code0} to use a port

* number that is automatically allocated.

*@parambacklog requested maximum length of the queue of incoming

* connections.

*@parambindAddr the local InetAddress the server will bind to

*

*@throwsSecurityException if a security manager exists and

* its {@codecheckListen} method doesn't allow the operation.

*

*@throwsIOException if an I/O error occurs when opening the socket.

*@exceptionIllegalArgumentException if the port parameter is outside

* the specified range of valid port values, which is between

* 0 and 65535, inclusive.

*

*@seeSocketOptions

*@seeSocketImpl

*@seeSecurityManager#checkListen

*@sinceJDK1.1*/

public ServerSocket(int port, int backlog, InetAddress bindAddr) throwsIOException {

setImpl();if (port < 0 || port > 0xFFFF)throw newIllegalArgumentException("Port value out of range: " +port);if (backlog < 1)

backlog= 50;try{

bind(newInetSocketAddress(bindAddr, port), backlog);

}catch(SecurityException e) {

close();throwe;

}catch(IOException e) {

close();throwe;

}

}

实际调用的构造方法为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.

*

*

A new Socket {@codes} is created and, if there

* is a security manager,

* the security manager's {@codecheckAccept} method is called

* with {@codes.getInetAddress().getHostAddress()} and

* {@codes.getPort()}

* as its arguments to ensure the operation is allowed.

* This could result in a SecurityException.

*

*@exceptionIOException if an I/O error occurs when waiting for a

* connection.

*@exceptionSecurityException if a security manager exists and its

* {@codecheckAccept} method doesn't allow the operation.

*@exceptionSocketTimeoutException if a timeout was previously set with setSoTimeout and

* the timeout has been reached.

*@exceptionjava.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

*

*@returnthe new Socket

*@seeSecurityManager#checkAccept

* @revised 1.4

* @spec JSR-51*/

public Socket accept() throwsIOException {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);returns;

}

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

/*** Creates an unconnected Socket with a user-specified

* SocketImpl.

*

*@paramimpl an instance of a SocketImpl

* the subclass wishes to use on the Socket.

*

*@exceptionSocketException if there is an error in the underlying protocol,

* such as a TCP error.

*@sinceJDK1.1*/

protected Socket(SocketImpl impl) throwsSocketException {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.

*

* If the application has specified a socket factory, that factory's

* {@codecreateSocketImpl} method is called to create the

* actual socket implementation. Otherwise a "plain" socket is created.

*

* If there is a security manager, its

* {@codecheckConnect} method is called

* with the host address and {@codeport}

* as its arguments. This could result in a SecurityException.

*

*@paramaddress the IP address.

*@paramport the port number.

*@exceptionIOException if an I/O error occurs when creating the socket.

*@exceptionSecurityException if a security manager exists and its

* {@codecheckConnect} method doesn't allow the operation.

*@exceptionIllegalArgumentException if the port parameter is outside

* the specified range of valid port values, which is between

* 0 and 65535, inclusive.

*@exceptionNullPointerException if {@codeaddress} is null.

*@seejava.net.Socket#setSocketImplFactory(java.net.SocketImplFactory)

*@seejava.net.SocketImpl

*@seejava.net.SocketImplFactory#createSocketImpl()

*@seeSecurityManager#checkConnect*/

public Socket(InetAddress address, int port) throwsIOException {this(address != null ? new InetSocketAddress(address, port) : null,

(SocketAddress)null, true);

}privateSocket(SocketAddress address, SocketAddress localAddr,boolean stream) throwsIOException {

setImpl();//backward compatibility

if (address == null)throw newNullPointerException();try{

createImpl(stream);if (localAddr != null)

bind(localAddr);

connect(address);

}catch (IOException | IllegalArgumentException |SecurityException e) {try{

close();

}catch(IOException ce) {

e.addSuppressed(ce);

}throwe;

}

}

首先,setImpl(),与服务端相似,设置系统默认类型SocketImpl,其是服务端和客户端套接字创建、连接、交互等操作的核心

其次,createImpl(stream),根据stream布尔值创建socket实现,true时创建基于流的socket(或者面向连接),false时创建无连接UDP套接字

最后,connect(address),连接服务器,连接一直处于阻塞状态,直到连接成功,或者超时或报错等

(4)SocketImpl类

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

/*** The abstract class {@codeSocketImpl} is a common superclass

* of all classes that actually implement sockets. It is used to

* create both client and server sockets.

*

* A "plain" socket implements these methods exactly as

* described, without attempting to go through a firewall or proxy.

*

*@authorunascribed

*@sinceJDK1.0*/

public abstract class SocketImpl implementsSocketOptions {/*** The actual Socket object.*/Socket socket= null;

ServerSocket serverSocket= null;/*** The file descriptor object for this socket.*/

protectedFileDescriptor fd;/*** The IP address of the remote end of this socket.*/

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

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

protected int localport;

类结构如下:

64fd090edd1644935df485fd5182d3ba.png

(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 extendsFileInputStreamclass SocketOutputStream extends FileOutputStream

3. 总结

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值