如果涉及到两台主机之间进行通信,那么就要引入网络的概念。计算机网络把不同主机连接起来,实现一些资源和数据的共享。
以TCP/IP五层模型为例,回顾一下每一层的作用(从低到高):
- 物理层:定义物理设备如何进行数据的传输,各种硬件设备、网线、接口等,主要以比特为单位传输;
- 数据链路层:将上面层的报文封装成帧,具有差错检验等功能(循环冗余码、海明码);
- 网络层:以ip数据报为单位,每个主机或者路由器都有自己的ip地址,主要解决的是设备之间的数据点对点传播问题;
- 传输层:解决的是源主机和目标主机之间端到端的通信问题,主要包括UDP和TCP两个协议;
- 应用层:在操作系统之上,直接使用软件获取相关的网络服务,一般协议由提供相关服务的软件实现。
在Java网络编程中,数据链路层及物理层的细节一般涉及不到;而网络层和传输层用的相对较多。在这里有一个 Socket 的概念非常重要,Socket也称为套接字,是一个ip地址和一个端口号的集合。
ip地址对应的是网络层中的概念,每一台主机都有自己的ip地址,其他主机若想访问某台主机,必须要知道其ip地址;端口号对应的是传输层中的概念,每个端口对应一种服务或一个进程。每台主机总共有65536个可用端口,其中1024以下的都是一些常用端口,例如:80端口代表http,21端口代表ftp。1024以上的多为自定义端口。
在两台主机进行通信时,一般来说,分为一台服务器和一台客户机。那么在Java网络编程中,两台主机之间建立通信的过程如下:
- 服务器打开一个端口,并监听客户机有无连接;
- 客户机通过自己的Socket,向服务器的这个端口发出请求;
- 服务器接收到这个请求后,生成自己的Socket;
- 两台主机之间就可以通过 Stream 进行通信了。
看一段更加具体一点的代码:
服务器代码:
public class server_2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
//通过ServerSocket打开6666端口,等待客户端请求
ServerSocket ss = new ServerSocket(6666);
System.out.println("打开端口:6666");
//接收到客户端请求后,服务端生成Socket
Socket s = ss.accept();
//通过输入流来将客户端的数据读进来
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
String str = dis.readUTF();
System.out.println(str);
is.close();
s.close();
ss.close();
}catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
客户端代码
public class client_2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
//新建Socket,指明想要连接的主机ip和端口号
Socket s = new Socket("127.0.0.1",6666);
//打开输出流,将数据输出给服务器端
OutputStream os = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("520");
os.close();
s.close();
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
首先在服务器主机上运行代码,服务端打开端口6666,并一直监听是否有客户端连接到自己的6666端口;之后在客户端运行客户端代码,客户端通过Socket,找到了服务器的ip以及打开的端口,并请求服务器进行服务。服务器接到客户端的请求后,通过accept方法建立了自己的Socket,用于两台主机之间的通信。之后客户端打开了输出流,并把字符串“520”送入流中;服务端通过套接字得到输入流,即可得到客户端发送来的内容。