网络上的两个程序通过一个双向的通信连接实现数据的交换,这个双向链路的一端称为一个Socket。
任何一个Socket都是由IP地址和端口号唯一确定的。
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
Socket也称为套接字,可以用来实现不同虚拟机或不同计算机之间的通信。在Java语言中,Socket可以分为两种类型:
(1)面向连接的Socket通信协议(TCP,传输控制协议)
(2)面向无连接的Socket通信协议(UDP,用户数据报协议)
首先,Server端Listen指定的某个端口是否有连接请求(建议端口号大于1024);
其次,Client端向Server端发出Connect请求;
最后,Server端向Client端发回Accept消息。
一个连接就建立起来了,会话随即产生。
Server端和Client端都可以通过Send、Writer等方法与对方通信。
Socket的生命周期分为3个阶段:
打开Socket、使用Socket收发数据、关闭Socket。
在java语言中,可以使用java.net.ServerSocket来作为服务器端,java.net.Socket作为客户端来实现网络通信。
(1)port(服务器的端口号)
(2)backlog(最大客户端等待队列数,可选填)
用于ServerSocket,配置ServerSocket的最大客户端等待队列。等待队列的意思。
下面这个服务器最多可以同时连接3个客户端,其中2个等待队列:
int backlog = 2;
ServerSocket serverSocket = new ServerSocket(port, backlog);
这个参数设置为-1表示无限制,默认是50个最大等待队列,如果设置无限制,那么你要小心了,如果你服务器无法处理那么多连接,那么当很多客户端连到你的服务器时,每一个TCP连接都会占用服务器的内存,最后会让服务器崩溃的。
InputStream 是字节输入流的所有类的超类。
InputStreamReader 是字节流通向字符流的桥梁,它将字节流转换为字符流,通俗的说,就是一个流格式转换器。
PrintWriter和BufferedWriter都是继承java.io.Writer,所以很多功能都一样。
不过PrintWriter提供println()方法可以写不同平台的换行符,即自动换行,而BufferedWriter可以任意设定缓冲大小,但不能自动换行,需手动。
OutputStream可以直接传给PrintWriter(BufferedWriter不能接收),如:
PrintWriter pw
= new PrintWriter(new BufferedOutputStream(new FileOutputStream("foo.out")));
或者用OutputStreamWriter来将OutputStream转化为Wrtier.这时就可以用BufferedWriter了。
这是端口被占用的结果,那么我们需要修改一下端口。
//首先,创建一个名为Server.java的服务器端代码
import java.io.*;
import java.net.*;
public class Server {
public static void main(String[] arg){
BufferedReader br =null;
PrintWriter pw = null;
try{
ServerSocket serverSocket = new ServerSocket(99);
Socket socket = serverSocket.accept();
//获取输入流
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//获取输出流
pw = new PrintWriter(socket.getOutputStream(),true);
//获取接收的信息
String str = br.readLine();
//发送相同的数据给客户端
pw.println(str);
}catch(Exception e){
e.printStackTrace();
}finally{
try{
br.close();
pw.close();
}catch(Exception e){
//e.printStackTrace();
}
}
}
}
//其次,创建一个Client.java的客户端程序
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] arg){
BufferedReader br = null;
PrintWriter pw = null;
try{
Socket socket = new Socket("localhost", 99);
//获取输入流与输出流
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"gbk"));//InputStreamReader第二个参数可选填,默认为GBK
pw = new PrintWriter(socket.getOutputStream(),true);
//向服务器发送数据
pw.println("你好!");
//接收服务器发回的消息
String str = null;
while(true){//如果接收到的消息为空(没有接收到消息),则继续此循环
str = br.readLine();
if(str!=null)
break; //如果接收到的消息不为空(接收到了消息),则跳出此循环
}
System.out.println(str);
}catch(IOException e){
e.printStackTrace();
}finally{
try {
br.close();
pw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
最后启动Server,再启动Client。
OIO是读写阻塞,NIO是读写非阻塞。就是说服务器等待客户端连接这块都是阻塞的,一旦建立连接了,
OIO下,我读取客户端发来的信息会因为网络延时问题又一次阻塞,你发消息也是一样;
而NIO下,我的Selector.select()如果注册(添加)了【IO事件(connect、read或者write等)】,
当客户端消息到达服务器端时,【IO事件】从阻塞中醒来提醒我【“消息已经到达服务器,可以进行读写操作了”】,
换句话说,不是说阻塞消失了,而是我想多处阻塞等待还是一处阻塞,然后我干别的事,等数据到了再通知我处理。
NIO非阻塞的实现主要采用了Reactor(反应器)设计模式,这个模式与Observer模式类似,只不过Observer模式只能处理一个事件源,而Reactor模式可以处理多个事件源。