一、基本概念
1.1 计算机网络
也称计算机通信网,其是利用通信线路将地理上分散的、具有独立功能的计算机系统和通信设备按不同的形式连接起来,以功能完善的网络软件及协议实现资源共享和信息传递的系统。最简单的计算机网络就只有两台计算机和连接它们的一条链路,即两个节点和一条链路。
通过编程的方式,使得计算机网络中不同计算机上的应用程序间能够进行数据的传输,这就是网络编程要做的事情。
1.2 软件结构
常见的软件结构有C/S和B/S:
Client/Server(C/S结构),表示 客户端/服务器 的软件结构,例如QQ、微信、百度网盘客户端等,只要是需要我们下载安装,并且和服务器通信的这一类软件,都属于C/S的软件结构。
Browser/Server(B/S结构),表示 浏览器/服务器 的软件结构,例如淘宝网、新浪新闻、凤凰网等,只要是需要使用浏览器,并且和服务器通信的这 一类软件,都属于B/S的软件结构。
C/S和B/S对比:
- C/S在图形的表现能力上以及运行的速度上肯定是强于B/S的
- C/S/需要运行专门的客户端,并且它不能跨平台,用c++在windows下写的程序肯定是不能在linux下运行
- B/S不需要专门的客户端,只要系统中安装了浏览器即可访问,方便用户的使用
- B/S是基于网页语言的、与操作系统无关,所以跨平台也是它的优势
无论是C/S结构的软件,还是B/S结构的软件,都必须依赖网络编程。
1.3 通信三要素
如果两台计算机上的应用程序能够实现通信,那么必须解决3个问题:找到对方计算机,找到对方应用程序,采用相同的通信协议。
- IP地址
可以认为IP地址是个标识号,通过这个标识号能够找到网络世界中唯一的那个计算机。
- 端口
端口号可以用来标识计算机中唯一的那个应用程序。网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,在网络通信时,就采用端口号进行区分这些应用程序。
- 协议
当我们和其他人沟通交流的时候都要使用互相能听懂的语言。计算机也一样,计算机与计算机通过网络进行数据和信息交换的时候,也要使用同样的 “语言”,这个语言被称为网络通讯协议。 网络通信协议对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议。
二、通信协议
计算机网络中,不同设备进行连接和通信的规则被称为网络通信协议。通信协议,对两台计算机之间所传输数据的传输格式、传输步骤等做了统一规定要求, 通信双方必须同时遵守才能完成数据交换。
OSI(Open System Interconnect),即开放式网络互连标准。 一般叫OSI参考模型,是ISO(国际标准化组织)在1985年研究的网络互连模型,它共包含七层, 具体可参考下图。
TCP/IP网络模型,是事实上的国际标准,它被简化为了四个层,从下到上分别依次是应用层、传输层、网络层、网络接口层。
应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。
传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
三、TCP和UDP
虽然完整的通信过程比较复杂,但是JavaAPI中把这些通信实现的细节进行了封装,使得我们可以直接使用相应的类和接口,来进行网络程序开发,而不用考虑通信的细节。
java.net 包中对常见的两种通信协议进行了封装和支持:UDP和TCP
UDP,用户数据报协议(User Datagram Protocol)
UDP是无连接通信协议,在数据传输时,数据的发送端和接收端不建立连接,也不能保证对方能接收成功。 例如,当一台计算机向另外一台计算机发送数据时(UDP),发送端不会确认接收端是否存在,就会直接发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。 但是在传输重要数据时,不建议使用UDP协议,因为它不能保证数据传输的完整性。
TCP,传输控制协议 (Transmission Control Protocol)
TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立连接,然后再传输数据,它提供了两台计算机之间可靠的、无差错的数据传输。 在TCP连接中,将计算机明确划分为客户端与服务器端,并且由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”的过程,四次挥手断开连接。
TCP的三次握手:
TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
- 第一次握手,客户端向服务器端发出连接请求,等待服务器确认
- 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
- 第三次握手,客户端再次向服务器端发送确认信息,确认连接
TCP的四次挥手:
用于断开连接
四、TCP网络编程
4.1 概述
在TCP通信协议下,计算机网络中不同设备上的应用程序之间可以通信,通信时需严格区分客户端(Client)与服务器端(Server)。
在Java中,对于这样基于TCP协议下连接通信的客户端和服务端,分别进行了抽象:
- java.net.ServerSocket 类表示服务端
- java.net.Socket 类表示客户端
使用 Socket 和 ServerSocket 进行的编程,也称为套接字编程。
4.2 通信流程
TCP客户端和服务器进行通信,其通信流程是固定的,具体如下:
服务器端:
- 创建ServerSocket(需绑定端口,方便客户端连接)
- 调用ServerSocket对象的accept()方法接收一个客户端请求,得到一个Socket
- 调用Socket的getInputStream()和getOutputStream()方法获取和客户端相连的IO流
- 输入流可以读取客户端发送过来的数据
- 输出流可以发送数据到客户端
- 操作完成,关闭资源
注意:accept方法是阻塞的,作用是等待客户端连接,如果有客户端连接则立马返回,如果没有客户端连接则一致阻塞等待。
客户端:
- 创建Socket连接服务端(需指定服务器ip地址、端口),找对应的服务器进行连接
- 调用Socket的getInputStream()和getOutputStream()方法获取和服务端相连的IO流
- 输入流可以读取服务端输出流写出的数据
- 输出流可以写出数据到服务端的输入流
- 操作完成,关闭资源
注意,在整个过程中,服务端不能主动连接客户端,必须由客户端先行发起连接才行
4.3 基础案例
搭建TCP客户端,发送信息到服务器
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TcpClient {
public static void main(String[] args) throws Exception {
//1.创建socket对象(指定服务器IP port) 连接到服务器
Socket socket = new Socket("127.0.0.1",8989);
System.out.println("成功连接到服务器:"+socket);
//2.获取数据传输的io流
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
//3.数据传输【需跟服务器配合】
//先发送数据给服务器
os.write("hello server,我是tcp客户端".getBytes());
System.out.println("数据发送完成!");
//再接收从服务器返回的数据
byte[] buff = new byte[1024];
int len = is.read(buff);
System.out.println("read:"+new String(buff,0,len));
//4.关闭资源
os.close();
is.close();
socket.close();
}
}
搭建TCP服务器端,接收客户端发送的信息,并返回数据给客户端
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer {
public static void main(String[] args) throws Exception {
//1.创建ServerSocket(需绑定端口,方便客户端连接)
ServerSocket server = new ServerSocket(8989);
System.out.println("服务器启动成功");
//2.调用ServerSocket地accept方法接收客户端请求,得到一个socket
//如果客户端成功连接到服务器,直接返回 套接字对象(通信)
//如果没有客户端连接服务器,则堵塞
Socket socket = server.accept();
System.out.println("客户端成功连接:"+socket);
//3.获取网络通信的io流对象
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
//4.数据传输
//先读取客户端发送来的数据 并输出
byte[] buff = new byte[1024];
int len = is.read(buff);
System.out.println("成功读取:");
System.out.println(new String(buff,0,len));
//再返回一条数据给客户端
System.out.println("即将发送数据给客户端...");
os.write("hello client".getBytes());
System.out.println("数据发送成功!");
//5.关闭资源
os.close();
is.close();
socket.close();
server.close();
}
}
4.4 传输对象
准备一个stud.txt文件,放到src/dir目录下,内容如下:
搭建TCP客户端,逐行读取stud.txt中数据,然后转化为Student对象,最后将所有对象发送到服务器端。
Student基础类:
import java.io.Serializable;
// 注意,必须实现序列化接口
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String name;
private int age;
// 无参构造方法
public Student() {
}
// 有参构造方法
public Student(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
// getter 和 setter 方法
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 重写 toString 方法,方便打印对象信息
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
客户端代码:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class Test_ObjectClient {
public static void main(String[] args) throws Exception {
//1.搭建客户端
Socket socket = new Socket("127.0.0.1",8989);
System.out.println("客户端成功连接:"+socket);
//2.获取流对象 并包装增强
OutputStream os = socket.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
//3.解析文件 得到对象 并发送
BufferedReader br = new BufferedReader(new FileReader("src/dir/stud.txt"));
String line = null;
List<Student> list = new ArrayList<>();
while ((line = br.readLine()) != null) {
//拆分数据
String[] arr = line.split("[.]");
String id = arr[0];
String name = arr[1];
int age = Integer.parseInt(arr[2]);
//封装学生对象 并添加到list集合
Student s = new Student(id,name,age);
list.add(s);
}
//发送集合(含所有学生)
oos.writeObject(list);
System.out.println("学生发送成功,数量:"+list.size());
//4.关闭资源
br.close();
oos.close();
socket.close();
}
}
搭建TCP服务器,接收从客户端发送过来的所有学生,然后遍历输出。
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
public class Test_ObjectServer {
public static void main(String[] args) throws Exception {
//1.搭建服务器,指定端口
ServerSocket server = new ServerSocket(8989);
System.out.println("服务器启动成功,端口:8989...");
//2.接收客户端的连接
Socket socket = server.accept();
System.out.println("客户端成功连接:"+socket);
//3.获取输入流并包装增强
InputStream is = socket.getInputStream();
ObjectInputStream ois = new ObjectInputStream(is);
//4.接收 遍历数据
List<Student> list = (List<Student>)ois.readObject();
System.out.println("成功接收学生数量:"+list.size());
for (Student s : list) {
System.out.println(s);
}
//5.关闭资源
ois.close();
socket.close();
server.close();
}
}