创建一个ServerSocket类,同时在运行该语句的计算机的指定端口处建立一个监听服务,如:
ServerSocket MyListener=new ServerSocket(8080);
这里指定提供监听服务的端口是8080,一台计算机可以同时提供多个服务,这些不同的服务之间通过端口号来区别,不同的端口号上提供不同的服务。
为了随时监听可能的Client请求,执行如下的语句:
Socket LinkSocket=MyListener.accept();
该语句调用了ServerSocket对象的accept()方法,这个方法的执行将使Server端的程序处于等待状态,程序将一直阻塞直到捕捉到一个来自Client端的请求,并返回一个用于与该Client通信的Socket对象Link-Socket。
此后Server程序只要向这个Socket对象读写数据,就可以实现向远端的Client读写数据。结束监听时,关闭ServerSocket对象:
Mylistener.close();
当Client程序需要从Server端获取信息及其他服务时,创建一个Socket对象:
Socket MySocket=new Socket(“ServerComputerName”,8080);
Socket类的构造函数有两个参数,第一个参数是欲连接到的Server计算机的主机地址,第二个参数是该Server机上提供服务的端口号。
Socket对象建立成功之后,就可以在Client和Server之间建立一个连接,并通过这个连接在两个端点之间传递数据。利用Socket类的方法getOutputStream()和getInputStream()分别获得向Socket读写数据的输入/输出流,最后将从Server端读取的数据重新返还到Server端。
当Server和Client端的通信结束时,可以调用Socket类的close()方法关闭Socket,拆除连接。
ServerSocket 一般仅用于设置端口号和监听,真正进行通信的是服务器端的Socket与客户端的Socket,在ServerSocket 进行accept之后,就将主动权转让了。
Java 为TCP协议提供两个类:Socket 类和 ServerSocket 类。
ServerSocket的构造方法,其中有:
ServerSocket() throws IOException
ServerSocket(int port) throws IOException
ServerSocket(int port, int backlog) throws IOException
ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException
构造器的参数:
port 监听的端口、
backlog 客户连接请求队列的长度(操作系统默认最大值50)
bindAddr 服务器要绑定的ip地址 (一个服务器有多个网卡,一个网卡1个IP地址)
1、端口已经被其他服务进程占用。
2、有些操作系统,如果不是root权限,不允许绑定1-1023之间的端口。
3、如果参数port设为0,表示操作系统会随机分配一个可用的端口。
在下面3种情况下仍会采用操作系统默认的队列的最大长度:
1、backlog值大于操作系统限定的队列最大长度50的时候。例如backlog=60,仍将按照50来执行。
2、backlog的值小于或等于0
3、在ServerSocket构造方法中没有设置backlog的值。
accept()方法从连接的请求队列中获取一个客户端的请求连接,然后创建这个客户连接的Socket对象,并将它返回。
如果队列中没有连接请求,accept()会一直阻塞在那里等待,直到监听到了连接请求才返回。
注意:accept()是阻塞并监听队列的连接请求,并创建和返回连接请求的Socket对象。
建立TCP连接是由操作系统来完成的,连接创建后放到了队列里。accept()只是从连接队列里取出连接,而不是创建连接。它起到的作用就是阻塞和监听,并把操作系统建立的TCP连接封装成Socket对象返回。
参考:ServerSocket的用法
Java Socket 之 TCP Socket
InetSocketAddress是IP套接字地址(IP地址+端口号),也可以是一对(主机名+端口号)。
构造器:
InetSocketAddress(InetAddress addr, int port) (根据IP地址和端口号创建套接字地址。
)
InetSocketAddress(int port) (创建一个套接字地址,其中IP地址为通配符地址,端口号为指定值。)
InetSocketAddress(String hostname, int port) (根据主机名和端口号创建套接字地址。
)
InetSocketAddress(IP套接字地址对象),只能用于bind操作。无论bio还是nio都要使用套接字地址通信。
在bio中,ServerSocket代码如下:
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 {
//InetSocketAddress套接字对象进行bind绑定
bind(new InetSocketAddress(bindAddr, port), backlog);
} catch(SecurityException e) {
close();
throw e;
} catch(IOException e) {
close();
throw e;
}
}
在nio中,InetSocketAddress需要和服务器通道ServerSocketChannel进行bind绑定:
public class Server implements Runnable{
//1 多路复用器(管理所有的通道)
private Selector seletor;
//2 建立缓冲区
private ByteBuffer readBuf = ByteBuffer.allocate(1024);
//3
private ByteBuffer writeBuf = ByteBuffer.allocate(1024);
public Server(int port){
try {
//1 打开路复用器(选择器)
this.seletor = Selector.open();
//2 打开服务器通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//3 设置服务器通道为非阻塞模式
ssc.configureBlocking(false);
//4 绑定InetSocketAddress套接字地址对象
ssc.bind(new InetSocketAddress(port));
//5 把服务器通道注册到多路复用器上,并且监听阻塞事件
ssc.register(this.seletor, SelectionKey.OP_ACCEPT);
System.out.println("Server start, port :" + port);
} catch (IOException e) {
e.printStackTrace();
}
}
InetAddress 代表IP地址封装对象。而InetSocketAddress是IP套接字对象。
TCP本身并没有长短连接的区别,长短连接取决于我们怎么用它。因此长、短连接通常是对应用层来说的。
短连接: 每次通信时,创建 Socket;一次通信结束,调用 socket.close()。这就是一般意义上的短连接。
短连接的好处是管理起来比较简单,存在的连接都是可用的连接,不需要额外的控制手段。
长连接: 每次通信完毕后,不会关闭连接,这样可以做到连接的复用。
长连接的好处是省去了创建连接的耗时,提升了性能。
长连接,需要担心各种问题:比如端对端连接的维护,连接的保活 。
长连接还常常被用来做数据的推送,我们大多数时候对通信的认知还是request/response 模型,但 TCP 双工通信的性质决定了它还可以被用来做双向通信。在长连接之下,可以很方便的实现 push 模型。
IP协议: 提供 主机与主机 间的通信。
TCP协议: 在IP协议提供的主机间通信的基础上,实现2个主机上进程与进程的通信。
1、客户端向服务端发送一个SYN,假如sequence number (seq)为x。这个x是根据操作系统自己随机生成的一个随机数字。
2、服务端收到这个SYN,会再向客户端发送一个SYN,seq number = y,(y是服务端操作系统生成的随机数),回复 ACK x+1,告诉客户端已经收到发送的SYN。
3、客户端收到服务端发送的SYN,回复一个ACK y+1。这个ACK是告诉服务端,SYN已经收到,服务端可以发送数据了。
需要注意的是:连接是客户端主动发起的。
四层网络模型由上往下依次分为:应用层,传输层,网络层,数据链路层,简单概述一下各层的功:
应用层:对应每一个应用(可以理解为App),协议的最终体现
传输层:对应端对端的链接(可以理解为,网络中的两个机器通过指定的端口链接)
网络层:主要用于在网络中如何找到目标主机(通过IP寻路)
数据链路层:数据的二进制流传输,真正的在物理链路上传输
协议,是定义了使用规则而已,
协议只是规则,并不能直接使用。
一个协议会对用好多种实现,只要实现能够满足该协议定义的规则即可。
Http是应用层协议,传输层使用的是tcp协议,换句话说就是在tcp的基础上又定义了一系列的上层规则。其中java中的HttpClient工具包可以说是Http协议的具体实现。
tcp、udp是传输层协议,
不同之处在于,tcp保证传输的可靠性,当然保证可靠就需要额外的开销,因此相对来说性能就会慢。
udp不保证可靠性,因此性能较高。
在java中,socket就是tcp协议的具体实现,而DatagramSocket是udp的具体实现。
需要注意的是,这里的可靠性是相对于传输层来说。并不代表不可靠的udp不能用在可靠的场景中,因为我们完全可以将可靠性交由应用层来保证。这一点一定要区分开。
上面已经提及socket是tcp协议的具体实现。其满足tcp协议所定义的所有规则。
每一个socket对象,都存在一个消息发送队列和消息接收队列。这两个队列可以对应于tcp协议中的滑动窗口,起到流量控制、重发、保证可靠性的作用。
每一个socket对象A的连接请求,服务端都会创建一个socket对象B与A通信,其中A的输入流即为B的输出流,A的输出流即为B的输入流。而创建socket对象B的是ServerSocket对象。
ServerSocket对象主要负责绑定端口,等待请求的接入。其维持两个队列:请求连接但未完成连接(三次握手)的Socket队列,已经完成连接的Socket队列,两个队列的元素和不能超过backlog的值,因此backlog代表中可以接受的最大连接数
原理图如下:
1代表客户端发起握手请求,这时ServerSocket会监听到有请求连接自己绑定的端口,ServerSocket会创建一个与请求对用的socket B,并将其放在第一个队列中,表示该链接在建立阶段,还不可用。然后响应客户端的请求,也就是红线2,表示服务端已经准备好,客户端可以连接了,最后客户端发起连接确认请求,也就是红线3,服务端接收到该请求时,表明该链接创建成功,socket B会从第一个队列中移出,并放入到链接建立的socket队列中,表示该链接现在是可用状态。到此客户端和服务端的链接建立完毕。
在java中,IO分为Bio、Nio、Aio三类,三者有本质的区别,下面主要讲解其在网络IO中的区别。
同步阻塞式IO,客户端类为Socket,服务端类为ServerSocket。该类中提供的方法全部为阻塞方法,即该操作完成后,该方法才会返回。
举例说明,如果调用了Socket输入流的readLine方法,那么该方法必须在读取到换行符才会返回。
优点:实现简单
缺点:针对每一个客户端请求都需要创建一个IO线程进行处理,并且IO操作在等待条件未满足时,仍然占用已持有资源等待,受网络波动的影响较大。
同步非阻塞式IO,客户端类为SocketChannel,服务端类为ServerSocketChannel。在Nio中提供了阻塞和非阻塞两种模式,一般我们会使用Nio的非阻塞模式,即每一个方法不会等待条件满足就会立即返回。
举例说明:如果调用SocketChannel的read方法时,不管读没读到数据都会立即返回,那么带来的问题也可想而知,对于IO操作的判断逻辑会变得更加复杂(因为执行IO操作时,并不知道该操作有没有完成)。
优点:Nio中使用一个IO线程(Selector轮询检查每一个注册的Channel)去处理所有的客户端连接,且IO操作会立即返回,不会产生BIO中保持资源等待的情况。
缺点:由于IO操作的完成情况是不确定的,导致了处理逻辑会比Nio复杂,其次IO线程通过轮询的方式检测,当注册在Selector上的Channel非常多时,会成为系统的瓶颈
异步非阻塞IO,客户端类为AsynchronousSocketChannel,服务端类为AsynchronousServerSocketChannel。
相比于只使用一个IO线程处理IO操作的Nio而言,Aio中不存在额外的IO线程,而是通过事件回调的方式来执行。
优点:不需要IO线程,相比于轮询Channel的Nio而言,采用事件响应模式可以有更小的开销