1. 网络编程概念
1.1 软件结构
- C/S结构(Client/Server):客户端与服务器结构;
- B/S结构(Browser/Server):浏览器与服务器结构;
1.2 网络编程三要素
1.2.1 IP地址
- 互联网协议地址(Internet Protocol Address),网络设备的唯一标识
- IP地址的两种常用的地址形式:
- IPv4: 32位的二进制数,分为4个字节,格式为
a.b.c.d
,每个字节范围是0~255; - IPv6: 128位的二进制数,每16个字节一组,共八组,格式为
a:b:c:d:e:f:g:h
,每个字节范围是0~255.
- IPv4: 32位的二进制数,分为4个字节,格式为
1.2.2 端口号
- 设备中进程的唯一标识,实际上是一个十进制的整数。
- 端口号是用两个字节表示的整数,取值范围0~65535。
- 注意:0~1024一下的端口号是系统保留使用的,程序员要使用1024以上的端口号。
1.2.3 通信协议
- 规定计算机之间数据传输的格式。
*通信协议中两种常用的协议:
TCP(Transmission Control Prorocol): 传输控制协议。TCP是面向连接的通信协议,在发送端和接收端监理逻辑连接,再传输数据
-
-
-
- 特点:保证传输数据的安全
UDP(User Datagram Protocol): 用户数据报包协议。UDP是面向无连接的协议,是不可靠协议,传输速度快,但是容易丢失数据
根据Ip找主机,根据端口号找程序,根据协议确定传输数据的格式
1.3 InetAddress类的概述
- 一个该类对象代表一个IP地址。
1.3.1常用方法
(无构造方法)
静态方法:
- public static InetAddress getLocalHost() 获得本地主机IP地址对象(主机名/IP地址字符串); - public static InetAddress getName() 根据主机名或IP地址字符串获得IP地址对象。
成员方法
- public String getHostName():获得主机名; - public String getHostAddress():获得IP地址字符串。
2. UDP通信程序
2.1 概述
UDP在数据传输时,数据的发送端和接收端不建立逻辑连接。
特点:
面向无连接协议;
资源消耗少,通信效率高,但不可靠;
- 只负责发送,发送过程不会确认接收端是否接收到;
- 基于数据包传输数据:将需发送的数据、源和接收端IP地址、端口号等信息封装成数据包发送;
- 每个数据包的大小限制在64K内;
使用场景:
- 即时通讯(如qq)
- 在线视频(如直播)
- 网络语音通话(如微信语音聊天)
2.2 UDP通讯使用的类
2.2.1 DatagramPacket类(数据包)
2.2.1.1 概述及作用
- 用来封装发送端或接收端要发送或接收的数据,是数据包对象
2.2.1.2 构造方法
- 创建发送端数据包:
- DatagramPacket(byte[] buf, int length, InetAddress address, int port)
用于创建发送端数据包对象。
其中:buf:字节数组,封装要发送的数据
length:要发送的内容长度,单位:字节
address:接收端的ip地址对象
port:接收端的端口号
- 创建接收端数据包:
- DatagramPacket(byte buf[],int length) : 用于创建接收端数据包对象
其中:buf:用于接收数据的数组
length: 能够接受内容的长度,即buf数组的长度,单位:字节
2.2.1.2 常用成员方法
- public int getLength():获得发送端实际发送的字节数或接收端实际接收到的字节数;
- public int getPort():获得发送端或接收端端口号
2.2.2 DatagramSocket类(发送数据包)
2.2.2.1 概述及作用
- DatagramSocket类实例对象可用来发送和接收数据包对象
2.2.2.2 构造方法
- DatagramSocket():创建发送端的发送对象(端口号随机生成)
- DatagramSocket(int port):根据指定端口号创建Socket对象(一般用在接收端)
2.2.2.3 常用成员方法
- public void send(DatagramPacket p):发送数据包
- public void receive(DatagramPacket p):接收数据包
- public void close():关闭资源,释放端口号
2.2.3 示例代码
/*
发送端
*/
public class Notes01_UCPsender {
public static void main(String[] args) throws Exception{
//定义字符串,包含要发送的内容
byte[] content = "你好".getBytes();
//创建数据包对象
DatagramPacket dp = new DatagramPacket(content, content.length, InetAddress.getLocalHost(),6666);//假设给本机的端口号为6666的程序发送数据
/*注意,这里的内容长度不能直接用“你好”.length,因为此时的值是字符的个数而不是字节的个数!*/
//创建发送对象
DatagramSocket ds = new DatagramSocket(); //不指定端口号会随机生成端口号
//使用发送对象发送数据包
ds.send(dp);
//关闭socket释放端口号
ds.close();
}
}
/*
接收端
*/
public class Notes01_UCPreceiver {
public static void main(String[] args) throws IOException {
//创建接收端的Socket
DatagramSocket ds = new DatagramSocket(6666);//假设端口是6666
//创建字节数组:存储接收到的实际内容
byte[] buf = new byte[1024];
//创建数据包对象,封装接收到的数据
DatagramPacket dp = new DatagramPacket(buf, buf.length);
//接收数据包
ds.receive(dp);
/*
以下的操作是对接收到的数据进行操作,不执行不会影响文件的传输
接收到的数据都在dp中,所以需要用dp调用方法
*/
//获得实际接收到的字节个数
int len = dp.getLength();
System.out.println("接收到的字节个数为:" + len);
//将字节数组转换为字符串输出
System.out.println(new String(buf,0,len));
//获得发送端的IP地址
String sendIP = dp.getAddress().getHostAddress();//getAddress()是获得发送端地址信息
//获得发送端的端口号
int sendPort = dp.getPort();
System.out.println("sendIP = " + sendIP);
System.out.println("sendPort = " + sendPort);
//关闭socket释放端口号
ds.close();
}
}
2.3 UCP通信图解
3. TCP通信程序
3.1 TCP协议概述
- TCP协议是面向连接的通信协议,即在传输数据前先在客户端和服务器端建立逻辑连接,然后再传输数据
- 特点:
- 面向连接的协议;
- 通过三次握手建立连接,形成传输数据的通道;
- 通过四次挥手断开连接;
- 效率稍低但是可靠协议;
- TCP是基于IO流传输数据;
- 传输数据大小不受限制。
- 使用场景:
- 文件传输
- 发送或接受邮件
- 远程登录
- 流程(三次握手):
- 客户端向服务器段发出连接请求,等待服务器确认;
- 服务器端向客户端会送一个响应,通知客户端已收到连接请求;
- 客户端再次向服务器段发送确认信息,确认连接。
- 然后客户端和服务器可以开始数据传输
3.2 TCP相关的两个类
3.2.1 Socket类(客户端)
3.2.1.1 概念
该类的对象就代表一个客户端程序;
该类实现客户端套接字
套接字指的是两台设备之间通讯的端点。也就是Socket对象
3.2.1.2 常用构造方法
- public Socket(String host, int port)
创建套接字对象并将其连接到指定逐级上的指定端口号。
其中:host:服务器IP地址(字符串形式)
port:服务器端口号
一旦执行该方法,就会立即连接指定的服务器,如果服务器没有开启,则连接失败抛出异常!!!
3.2.1.3 成员方法
- public InputStream getInputStream():返回此套接字的输入流
- public OutputStream getOutputStream():返回此套接字的输出流
- 如果此Socket具有相关联的通道,则生成的InputStream/OutputStream的所有操作也关联该通道;
- 关闭生成的InputStream/OutputStream也将关闭相关的Socket。
- public void close() :关闭此套接字。
- 一旦一个Socket被关闭,它不 可再使用;
- 关闭此Socket也将关闭相关的InputStream/OutputStream。
- public void shutdownOutput()
禁用此套接字的输出流。(也就是告知服务器数据已传输完毕)
- 任何先前写出的数据将被发送,随后终止输出流。
3.2.1.4 TCP客户端代码实现步骤
- 创建客户端的Socket对象并指定需要连接的服务器的端口号;
- 调用Socket对象的getOutputStream方法获得字节输出流对象;
- 调用字节输出流对象的write方法往服务器端输出数据;
- 调用Socket对象的getInputStream方法获得字节输入流对象;
- 调用字节输入流对象的read方法读取服务器端返回的数据;
- 关闭Socket对象断开连接。
3.2.2 ServerSocket类(服务器端)
3.2.2.1 概念
该类的对象就代表一个服务器端程序
该类实现了服务器套接字,相当于开启一个服务器,该对象等待通过网络的请求。
3.2.2.2 构造方法
- public ServerSocket(int port)
使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。
3.2.2.3 成员方法
- public Socket accept()
等待客户端连接,并返回一个与客户端相关的Socket对象,用于和客户端实现通信。
该方法会一直阻塞直到建立连接。
- public void close() :关闭serverSocket,相当于关闭服务器,一般不会关闭
3.2.2.4 TCP服务器端代码实现步骤
- 创建ServerSocket对象并指定端口号;
- 调用ServerSocket对象的accept方法等待客户端连接并获得与客户端相关的Socket对象;
- 调用Socket对象的getInputStream方法获得字节输入流对象;
- 调用字节输入流对象的read方法读取客户端发送的数据;
- 调用Socket对象的getOutputStream方法获得字节输出流对象;
- 调用字节输出流对象的write方法往客户端返回数据;
- 调用close方法关闭Socket和ServerSocket对象。(实际开发不会关闭ServerSocket)
3.2.3 TCP示例代码
/*
客户端实现
*/
public class Notes02_TCPclient {
public static void main(String[] args) throws Exception{
//创建客户端Socket对象,相当于开启一个客户端程序
//假设给本地传输数据,端口假设为8888
//注意,客户端的端口号要与服务器的对应,否则会无法连接而报错
Socket socket = new Socket("127.0.0.1",8888);
//调用Socket对象的getOutputStream方法获得字节输出流对象
OutputStream os = socket.getOutputStream();
//调用字节输出流对象的write方法往服务器端输出数据
os.write("你好吗?".getBytes());
//注意要用shutdownOutput方法终止客户端传输数据!!!否则服务器有可能会一直处于接收数据阶段
socket.shutdownOutput();
/*
从服务器端接收信息
*/
//调用Socket对象的getInputStream方法获得字节输入流对象
InputStream is = socket.getInputStream();
//调用字节输入流对象的read方法读取服务器端返回的数据
byte[] content = new byte[1024];
int len = -1;
while ((len = is.read(content)) != -1) {
System.out.println(new String(content,0,len));
}
//关闭Socket对象断开连接,自动将相关联的流关闭
socket.close();
}
}
/*
服务器端实现
*/
public class Notes02_TCPserver {
public static void main(String[] args) throws IOException {
//创建ServerSocket对象并指定端口号
ServerSocket serverSocket = new ServerSocket(8888);
//调用ServerSocket对象的accept方法等待客户端连接并获得与客户端相关的Socket对象
Socket socket = serverSocket.accept();
//调用Socket对象的getInputStream方法获得字节输入流对象
InputStream is = socket.getInputStream();
//调用字节输入流对象的read方法读取客户端发送的数据
byte[] buf=new byte[1024];
int len = -1;
while((len=is.read(buf)) != -1){
System.out.println(new String(buf,0,len));
}
/*
返回信息给客户端
*/
//调用Socket对象的getOutputStream方法获取字节输出流对象
OutputStream os = socket.getOutputStream();
//调用字节输出流对象的write方法往客户端返回数据
os.write("很好,已经连上了".getBytes());
//调用close方法关闭Socket和ServerSocket对象
socket.close();
//实际上一般不会关闭服务器
serverSocket.close();
}
}
3.3 TCP通信分析
1. 【服务端】启动,创建ServerSocket对象,等待连接。
2. 【客户端】启动,创建Socket对象,请求连接。
3. 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
4. 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
5. 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。
此时客户端向服务端发送数据成功
此后服务端向客户端回写数据
6. 【服务端】Socket对象,获取OutputStream,向客户端回写数据。
7. 【客户端】Scoket对象,获取InputStream,解析回写数据。
8. 【客户端】释放资源,断开连接。
图解如下:
4. 疑问
Q:在传输数据时,什么场景下用shutdownOutput?为何有时不用shutdownOutput终止数据输出?
A:包含但不限于以下的情形,shutdownOutput可以不使用:
- 数据输出后不需要接受对方放回的数据,直接close的情况。在该情况下socket对象在close时会自动告知对方数据已传输结束。此后这个socket就不能再数据传输;
- 传输数据时使用字符输出/输入流,在调用wrtie方法输出一行数据后再调用newLine()方法时,newLine()相当于在当行数据末尾加上一个终止的标识,那么对方用readLine()的时候就会识别出该行数据已传输结束。此时不再需要用shutdownOutput关闭输出流来告知对方该行数据已传输结束。
另外要留意:
- 输出数据时一般需要结束标识告知对方数据已传输完毕,如果没有,则需要调用shutdownOutput来告知。不过一旦调用,该socket就不能输出数据。
- 一旦调用了shutdownOutput,该socket就不能再使用输出流,再次用getOutputStream也不会再重新打开,只能重新连接(重新获得socket)。
- 一旦调用close,则整个socket都不能用,包括相关联的输出/输入流