1 网络模型及相关协议
目前计算机网络模型有两种:OSI七层模型和TCP/IP五层模型,模型示意图见下图
计算机网络发送和接收数据的过程是:
- 发送数据:对发送的数据内容按照上述七层模型进行层层加包封装后发送出去
- 接收数据:对接受的数据按照上述七层模型进行层层拆包后显示出数据内容
计算机网络协议:计算机在网络中通信所遵守的的约定或规则就是计算机网络协议,计算机网络协议可以对速率、传输结构、差错控制等制定统一标准。计算机网络协议中最常用的协议是TCP和UDP协议,下面将详细介绍这两种传输协议。
TCP协议:TCP传输控制协议,是一种面向连接的传输协议,支持全双工的字节流通信方式,可以进行大量数据的传输,在传输过程中可以保证数据传输的可靠性和有序性,缺点是传输效率较低。
UDP协议:UDP用户数据报协议,是一种面向非连接的传输协议,支持全双工的数据报通信方式,每个数据报大小限制在64KB以内,不能保证数据传输的可靠性和有序性,但是传输效率较高。
IP地址:是互联网中主机的唯一标识,由32位二进制组成的整数,叫做IPv4。需要注意的是127.0.0.1是回环地址,也就是本机地址。
端口号:IP地址可以定位到某一台主机,而端口号是定位到该设备上的某个进程,表示范围是:0~65535,其中0~1024之间的端口号被系统占用。被我们熟知的端口号有,FTP 21、Tomcat 8080 等。
2 TCP协议编程模型
在介绍TCP协议编程模型之前,先介绍C/S模式,所谓C/S模型是指客户向服务器发出服务请求,服务器接受请求后提供服务。
TCP协议编程模型也是典型的C/S模式,分为客户端和服务端,详细介绍如下:
-
服务器:(1)创建ServerSocket类型的对象并提供端口号
(2) 等待客户端的连接请求,调用accept()方法
(3) 使用输入输出流通信
(4) 关闭Socket
-
客户端:(1)创建Socket类型的对象并提供服务器地址和端口号
(2) 使用输入输出流通信
(3) 关闭Socket
上面提到的ServerSocket类和Socket类都是实现TCP编程模型所需要使用的,下面介绍这两个类的常用方法:
ServerSocket类主要描述服务器套接字信息,常用方法如下:
| 常用方法 | 功能介绍 |
|---|---|
| ServerScoket(int port) | 根据参数端口构造对象 |
| Socket accept() | 监听并接受到此套接字的连接请求 |
| void close() | 关闭套接字 |
Scoket类主要描述客户端套接字信息,常用方法如下:
| 常用方法 | 功能介绍 |
|---|---|
| Socket(String host,int port) | 根据主机名和端口号构造对象 |
| InputStream getInputStream() | 获取当前套接字的输入流 |
| OutputStream getOutputStream() | 获取当前套接字的输出流 |
| void close() | 关闭套接字 |
TCP编程模型实现代码:
服务器端
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class ServiceString {
public static void main(String[] args) {
ServerSocket ser= null;
Socket st=null;
try {
ser = new ServerSocket(8888);
while(true) {
System.out.println("等待客户端的连接请求");
st = ser.accept();
System.out.println("客户端" + st.getInetAddress() + "连接成功");
new ServerThread(st).start();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null!=ser){
try {
ser.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class ClientString {
public static void main(String[] args) {
Socket st=null;
PrintStream p=null;
Scanner sc=null;
BufferedReader br=null;
try {
st=new Socket("127.0.0.1",8888);
System.out.println("服务器连接成功");
sc=new Scanner(System.in);
p=new PrintStream(st.getOutputStream());
br=new BufferedReader(new InputStreamReader(st.getInputStream()));
while (true){
System.out.println("请输入你要发送的内容:");
String s=sc.next();
p.println(s);
System.out.println("客户端内容发送完毕");
if("bye".equalsIgnoreCase(s)){
System.out.println("聊天结束");
break;
}
String str=br.readLine();
System.out.println("服务器返回的消息是:"+str);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null!=br){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(null!=sc){
sc.close();
}
if(null!=p){
p.close();
}
if(null!=st){
try {
st.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
多线程
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;
public class ServerThread extends Thread{
private Socket st;
public ServerThread(Socket st) {
this.st = st;
}
@Override
public void run() {
BufferedReader br=null;
PrintStream ps=null;
try {
br=new BufferedReader(new InputStreamReader(st.getInputStream()));
ps=new PrintStream(st.getOutputStream());
while (true){
String str=br.readLine();
InetAddress inetAddress=st.getInetAddress();
System.out.println("客户端"+inetAddress+"发来的消息是:"+str);
if("bye".equals(str)){
System.out.println("客户端"+inetAddress+"已下线");
break;
}
ps.println("I received");
System.out.println("服务器发送数据成功");
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null!=ps){
ps.close();
}
if(null!=br){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(null!=st){
try {
st.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
由上面的实现代码,可以注意到以下几点:
- 客户端Socket与服务器端Socket对应,都包含输入流和输出流
- 客户端的socket.getInputStream()连接服务器端的socket.getOutputStream()
- 客户端的socket.getOutputStream()连接服务器端的socket.getInputStream()
3 UDP协议编程模型
关于UDP编程模型分为接收方和发送方,具体实现步骤如下:
接收方:
- 创建DatagramSocket类型的对象并提供端口号
- 创建DatagramPacket类型的对象并提供缓冲区
- 通过Socket接受数据内容存放到Packet中,调用send方法
- 关闭Socket
发送方:
- 创建DatagramSocket类型的对象
- 创建DatagramPacket类型的对象并提供接收方地址
- 通过Socket将Packet中的数据内容发送出去,调用receive方法
上面提到的DatagramSocket类和DatagramPacket类都是实现UDP编程模型所需要使用的,下面介绍这两个类的常用方法:
DatagramSocket类主要用于描述发送和接收数据报的套接字,其常用方法如下:
| 常用方法 | 功能介绍 |
|---|---|
| DatagramSocket() | 无参方式构造对象 |
| DatagramSocket(int port) | 根据参数端口构造对象 |
| void receive(DatagramPocket p) | 将接收到的数据存放到参数指定位置 |
| void send(DatagramPocket p) | 将参数内容发送出去 |
| void close() | 关闭套接字 |
DatagramPacket类主要用来描述数据报,数据报用来实现无连接包裹投递服务,其常用方法如下:
| 常用方法 | 功能介绍 |
|---|---|
| DatagramPocket(byte[] b,int len) | 根据参数指定的数组来构造对象,用于接受长度为len的数据报 |
| DatagramPocket(byte[] b,int len,InetAdress add,int port) | 根据参数指定数组构造对象,将数据报发送到指定地址和端口 |
| InetAdress getAdress() | 获取发送方或接受方的地址 |
| int getPort() | 获取发送方或接受方的端口号 |
| int getLength() | 获取发送数据或接收数据的长度 |
InetAdress类主要描述互联网通信地址信息,常用方法如下:
| 常用方法 | 功能介绍 |
|---|---|
| static InetAdress getLocalHost() | 获取本主机的地址 |
| static InetAdress getByname(String host) | 根据参数指定的主机名获取地址 |
UDP编程模型实现代码:
发送方
import java.io.IOException;
import java.net.*;
public class SendTest {
public static void main(String[] args) {
DatagramSocket ds=null;
try {
ds=new DatagramSocket();
byte[] by="hello java".getBytes();
DatagramPacket dp=new DatagramPacket(by,by.length, InetAddress.getLocalHost(),8888);
ds.send(dp);
System.out.println("数据发送成功");
byte[] brr=new byte[20];
DatagramPacket dp1=new DatagramPacket(brr,brr.length);
ds.receive(dp1);
System.out.println("接收到的数据是"+new String(brr,0,dp1.getLength()));
} catch (SocketException | UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null!=ds){
ds.close();
}
}
}
}
接收方
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class ReceiveTest {
public static void main(String[] args) {
DatagramSocket ds=null;
try {
ds=new DatagramSocket(8888);
byte[] brr=new byte[20];
DatagramPacket dp=new DatagramPacket(brr, brr.length);
System.out.println("等待数据的到来");
ds.receive(dp);
System.out.println("接收到的数据是"+new String(brr,0,dp.getLength()));
byte[] br="I received".getBytes();
DatagramPacket dp1=new DatagramPacket(br,br.length,dp.getAddress(),dp.getPort());
ds.send(dp1);
System.out.println("回发数据成功");
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null!=ds){
ds.close();
}
}
}
}
4 URL类
URL类主要表示统一的资源定位器,也就是指向万维网上“资源”的指针,这个资源可以是文件或目录。通过URL可以访问万维网上的资源,URL基本结构是:<传输协议>://<主机名>:<端口号></资源地址>.
URL类常用方法:
| 常用方法 | 功能介绍 |
|---|---|
| URL(String s) | 根据参数指定的字符串信息构造对象 |
| String getProtocol() | 获取协议名称 |
| String getHost() | 获取主机名称 |
| int getPort() | 获取端口号 |
| String getPath() | 获取路径信息 |
| String getFile() | 获取文件名 |
| URLConnection openConnection() | 获取URLConnection类的实例 |
URLConnection类是个抽象类,表示应用程序和URL之间的通信链接的所有类的超类,其实现类有支持HTTP功能的HTTPURLConnection类,HTTPURLConnection类的常用方法有:
| 常用方法 | 功能介绍 |
|---|---|
| InputStream getInputStream() | 获取输入流 |
| void disconnect() | 断开连接 |
实例代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class URLTest {
public static void main(String[] args) {
URL url= null;
try {
url = new URL("https://www.lagou.com");
} catch (MalformedURLException e) {
e.printStackTrace();
}
System.out.println("获取到的协议是"+url.getProtocol());
System.out.println("获取到的主机名是"+url.getHost());
System.out.println("获取到的主机号是"+url.getHost());
try {
HttpURLConnection htt=(HttpURLConnection) url.openConnection();
InputStream inputStream=htt.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(inputStream));
String str=null;
while((str=br.readLine())!=null){
System.out.println(str);
}
htt.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}