第20章 Socket通信
1. 网络传输基础知识
20世纪60年代以来,计算机网络得到了飞速增长。各大厂商为了在数据通信网络领域占据主导地 位,纷纷推出了各自的网络架构体系和标准,如IBM公司的SNA,Novell IPX/SPX协议,Apple公司的AppleTalk协议,DEC公司的DECnet,以及广泛流行的TCP/IP协议。同时,各大厂商针对自己的协议生产出了不同的硬件和软件。各个厂商的共同努力促进了网络技术的快速发展和网络设备种类的迅速增长。但由于多种协议的并存,也使网络变得越来越复杂;而且,厂商之间的网络设备大部分不能兼容,很难进行通信。
为了解决网络之间的兼容性问题,帮助各个厂商生产出可兼容的网络设备,国际标准化组织ISO于1984年提出了OSI RM(OpenSystem Interconnection Reference Model,开放系统互连参考模型)。OSI 参考模型很快成为计算机网络通信的基础模型。在设计OSI 参考模型时,遵循了以下原则:各个层之间有清晰的边界,实现特定的功能;层次的划分有利于国际标准协议的制定;层的数目应该足够多,以避免各个层功能重复。
OSI7层协议
通常OSI参考模型第一层到第三层称为底层(lower layer),又叫介质层(media layer),底层负责数据在网络中的传送,网络互连设备往往位于下三层,以硬件和软件的方式来实现。OSI参考模型的第五层到第七层称为高层(upper layer),又叫住几层(host layer),高层用于保障数据的正确传输,以软件方式来实现。
OSI7层功能
由于OSI模型和协议比较复杂,所以并没有得到广泛的应用。
而TCP/IP(transfer control protocol/internet protocol,传输控制协议/网际协议)模型因其开放性和易用性在实践中得到了广泛的应用,TCP/IP协议栈也成为互联网的主流协议。
TCP/IP协议与OSI模型对照
两台计算机间进行通讯需要以下三个条件:
- 协议(Protocal)
数据传输协议和方式
- IP地址(IP)
为实现网络中不同计算机之间的通信,计算机在网络上的唯一标识
- 端口号(Port)
区分一台主机的多个不同应用程序,端口号范围为0-65535,其中0-1023位为系统保留。
2.TCP/IP通信原理和Java中处理方式
Java中的Socket和ServerSocket是基于TCP/IP协议的,其形式是“请求-响应”式,当一方发起请求后,必须在得到另一方的应答后才能继续请求。发送请求和接收响应是通过输出流和输入流完成的。
Socket类的对象负责两台计算机之间的通信
ServerSocket类负责充当服务器,监听连接请求并创建Socket连接
总体上参照下图可以理解为
- B创建Socket进行连接
- A通过ServerSocket监听连接并创建Socket负责接收消息
- B发起请求
- A接收请求并给出回应
- B接收A的回应,再次请求
- A接收请求并给出回应
...
3. 通信演示
分别创建两个项目ProjectA和ProjectB表示两台计算机
创建项目
- ProjectA作为服务器
- ProjectB作为客户端
3.1 简单通信
3.1.1 ProjectA服务器
public class StartServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(40001);
Socket s = ss.accept();
DataInputStream dis = new DataInputStream(s.getInputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
String message = dis.readUTF();
if(message.equals("你好")) {
dos.writeUTF("你好");
}else {
dos.writeUTF("Hello World!");
}
dos.flush();
}
}
3.1.2 ProjectB客户端
public class StartClient {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket s = new Socket("localhost", 40001);
DataInputStream dis = new DataInputStream(s.getInputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
Scanner sc = new Scanner(System.in);
System.out.println("请输入您对服务器说的话:");
String str = sc.nextLine();
dos.writeUTF(str);
dos.flush();
String rec = dis.readUTF();
System.out.println("服务器回应:"+rec);
}
}
先启动ProjectA的服务器程序,再启动ProjectB的客户端程序
在客户端的控制台上进行如下测试
测试
可以看到服务器给出了相应的应答
3.2 多次通信
3.1小节的例子可以在服务器和客户端进行单次的消息通信,通信后两方程序执行完毕,各自退出。如果要保证两方多次通信,需要将通信代码部分设置为循环,直至客户端提出“88”再结束通信。
3.2.1 ProjectA服务器
public class StartServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(40001);
Socket s = ss.accept();
DataInputStream dis = new DataInputStream(s.getInputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
while(true) {
String message = dis.readUTF();
if(message.equals("你好")) {
dos.writeUTF("你好");
}else if(message.equals("88")){
break;
}else {
dos.writeUTF("Hello World!");
}
dos.flush();
}
}
}
3.2.2 ProjectB客户端
public class StartClient {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket s = new Socket("localhost", 40001);
DataInputStream dis = new DataInputStream(s.getInputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
while(true) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入您对服务器说的话:");
String str = sc.nextLine();
dos.writeUTF(str);
dos.flush();
if(str.equals("88")) {
break;
}
String rec = dis.readUTF();
System.out.println("服务器回应:"+rec);
}
}
}
先启动ProjectA的服务器程序,再启动ProjectB的客户端程序
在客户端的控制台上进行如下测试
测试及运行结果
3.3 多客户端通信
实际应用情况中,单个客户端在退出时是不能影响服务器的,一个服务器也会同时服务多个客户端,这样,我们需要修改服务器的处理程序为多线程方式。每个线程对象处理一个客户端的交互,将监听得到的s对象交给线程处理
3.3.1 ProjectA服务器
处理客户端交互的线程类
public class ClientThread implements Runnable{
private Socket s;
public ClientThread(Socket s) {
this.s = s;
}
@Override
public void run() {
try {
DataInputStream dis = new DataInputStream(s.getInputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
while(true) {
String message = dis.readUTF();
if(message.equals("你好")) {
dos.writeUTF("你好");
}else if(message.equals("88")){
break;
}else {
dos.writeUTF("Hello World!");
}
dos.flush();
}
} catch (IOException e) {
System.out.println("服务器线程出错");
return;
}
}
}
启动服务器
public class StartServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(40001);
while(true) {
Socket s = ss.accept();
ClientThread ct = new ClientThread(s);
Thread t = new Thread(ct);
t.start();
}
}
}
3.3.2 ProjectB客户端
与3.2小节代码一致,不再重述。
这样,在3.2小节的基础上,服务器可以同时处理多个客户端请求,且客户端关闭后不会影响服务器的运行。