一,传输层的两个协议:
UDP:User Datagram Protocol 用户报文协议
TCP:Transmission Control Protocol 传输控制协议
联系与区别
相同点:都是传输层协议,都是进程与进程之间的通信
不同点:
UDP没有做任何处理,保持网络的原生态(不可靠)
TCP对网络传输做了一些控制,是通信变得可靠(可靠)
所以将
UDP称为:不可靠,无连接,面向数据报文(字符流)的一种协议。
TCP可靠的有连接的面向字节流的协议。
二,网络层的重要协议
网络层主要协议:IP协议 : Internet Protocol
网络层的重要概念:IP地址
传输层的重要概念:Port端口 0~65535 一个进程可以拥有很多端口,一个端口只能一个进程
数据链路层的重要概念:MAC地址
IP地址+端口 + 传输层协议 =》 五元组信息
五元组:
1,站在通信数据角度:源IP,源端口,目的IP,目的端口,传输层协议(UDP,TCP)
2,站在通信双方:本地IP,本地端口,远端IP,远端端口,传输层协议(TCP,UDP)
套接字(Socket):
站在应用层角度,要实现进程之间的通信,需要使用传入层提供的服务,传输层向应用层提供Socket,用来接收应用层发来的数据,
类比传输层有两家公司(TCP,UDP)应用层(用户)要向传输层沟通,只能通过传输层提供的Socket接口,实现沟通。
Socket的构造方法:
UDP协议相关的类
DatagramSocket(报文套接字)
DatagramSocket():未指定端口,系统会分配一个端口
DatagramSocket(int port):绑定指定端口,方便客户端通信,存在端口号被占用的风险
关闭Socket连接:
发送数据报文:
接收数据报文:
一旦UDP在逻辑上通信成功,双方都可以发送和接收数据,双方地位平等(P2P模式)通信结束后,双方都应该调用close()方法,进行资源回收。
数据包:DatagramPacket类
构造方法
接收方:在接收数据时,只需要提供数据的容器和长度(byte[] buf,int length)
获取发送的数据包
Byte[] getData();接收方获取接收到的数据的内容
作为发送方:在发送数据时,需要提供发送的数据包,长度,偏移,目的地址/目的端口(byte[] buf + int offset + int lenght + (InternetAddress aimAdd + int port) -> 会被封装成SocketAddress对象);
服务器获取客户端的地址和端口
服务器与客户端的通信模式
1,请求响应模式(客户端请求一次,服务器响应一次)
2,订阅推送模式(客户端订阅一次请求,服务器在今后每次有此类请求的更新时,服务器就会主动推送给客户端)
P2P模式(不区分客户端和服务器)
客户端服务器模式:
UDP演示网络编程
UDP协议不区分客户端与服务器,因此双方都可以作为发送方和接收方,所以可以双方都采用DatagramSocket类创建对象,通过双方的端口号建立通信,将发送和接收的数据分别存在DatagramPacket对象中,在通过字符编码的形式,转换为正确的数据报。
UDP服务器
package 网络编程.udp;
import 网络编程.Log;
import java.io.IOException;
import java.net.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class TranslateServer {
//公开的IP和端口
public final static int port = 9999;
private static HashMap<String, String> map = new HashMap<>();
static {
map.put("apple","苹果");
map.put("book","书");
map.put("bicycle","自行车");
Log.println("字典初始化完成");
}
public static void main(String[] args) throws Exception {
Log.println("准备创建UDP Socklet ,端口是:" + port);
DatagramSocket socket = new DatagramSocket(port);//参加Socket关键字
Log.println("UDP Socket 创建完成");
//服务器接受请求,被动接收,所以是一直处在运行态
while (true){
//1,参加Socket
//参加容器接收数据,1024缓冲的大小
byte[] buf = new byte[1024];
//参加获取请求的数据包
DatagramPacket receivePacket = new DatagramPacket(buf,buf.length);
//获取从客户端发来的请求,如果客户端没有发送请求,就会一直再次阻塞
//2,获取请求的信息
Log.println("准备接收请求,容器大小" + buf.length);
socket.receive(receivePacket);
Log.println("接受到请求");
//获取对方的IP和port
InetAddress address = receivePacket.getAddress();
int port1 = receivePacket.getPort();
Log.println("请求方的端口 : " + port1);
Log.println("请求方的IP : " + address);
//获取对方的IP+port
SocketAddress socketAddress = receivePacket.getSocketAddress();
//获取请求内容
byte[] data = receivePacket.getData();
//获取接收数据的大小
int length = receivePacket.getLength();
Log.println("接收到请求的长度 : " + length);
//3,解析请求
//字符集解码
String request = new String(data,0,length,"UTF-8");
String engWord = request;
Log.println("接受到请求的数据为 : " + engWord);
//4,执行业务
String chiWord = tranlaste(engWord);
Log.println("翻译后的结果为: " + chiWord);
//5,封装响应
String response = chiWord;
byte[] sendBuf = response.getBytes("UTF-8");
//发送数据,要将接收方的信息一并封装到包中socketAddress
DatagramPacket responsePacket = new DatagramPacket(sendBuf,0,sendBuf.length,socketAddress);
Log.println("接收方发送响应 ... ");
//6,发送响应
socket.send(responsePacket);
Log.println("接收方发送响应成功,发送的数据为 : " + chiWord);
//此时请求完成,等待下一次响应
}
//服务器关闭时,关闭所有的套接字资源
// socket.close();
}
private static String tranlaste(String engWord) {
String chiWord = map.getOrDefault(engWord,"查无此词");
return chiWord;
}
}
UDP客户端
package 网络编程.udp;
import 网络编程.Log;
import java.io.UnsupportedEncodingException;
import java.net.*;
import java.util.Scanner;
public class ClientVersion1 {
public static final int port = 8888;
public static void main(String[] args) throws Exception {
//1,创建DatagramSocket
Log.println("开始创建UDP Socket");
DatagramSocket socket = new DatagramSocket(port);
Log.println("UDP Socket创建完成");
Scanner sc = new Scanner(System.in);
System.out.print("输入要翻译的单词:");
while (sc.hasNextLine()){
//2,创建请求数据包
String request = sc.nextLine();
Log.println("请求的数据为" + request);
byte[] buf = request.getBytes("UTF-8");
Log.println("准备创建请求数据包");
DatagramPacket requestPacket = new DatagramPacket(buf,0,buf.length, InetAddress.getLoopbackAddress(),TranslateServer.port);
Log.println("请求数据包创建完成");
//3,发送请求
Log.println("发送请求");
socket.send(requestPacket);
Log.println("请求发送成功");
//4,接收响应
byte[] bytes = new byte[1024];
DatagramPacket responsePacket = new DatagramPacket(bytes,bytes.length);
Log.println("开始接收响应");
socket.receive(responsePacket);
byte[] data = responsePacket.getData();
String res = new String(data,0,responsePacket.getLength(),"UTF-8");
Log.println("请求结果为:" + res);
Log.println("对方的IP为" + requestPacket.getAddress());
Log.println("对方的端口为" + requestPacket.getPort());
System.out.print("输入要翻译的单词:");
}
//5,关闭资源
socket.close();
Log.println("关闭Socket");
}
}
Log日志打印
package 网络编程;
import java.text.DateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
//日志类
public class Log {
public static void println(Object o){
LocalDateTime localDateTime =LocalDateTime.now(ZoneId.of("Asia/Shanghai"));//该类的构造方法私有,通过静态方法获取时间戳
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
String time = format.format(localDateTime);
//所以要打印的信息就是时间+对象信息
System.out.println(time +" : "+ (o == null ? null : o.toString()));
}
public static void main(String[] args) {
println(null);
}
}
TCP协议相关类
tcp有连接,需要先建立连接,才能进行通信
ServerSocket类创建 TCPSocket服务器对象
构造方法,socket的构造方法是给客户端使用的,因为服务区的socket是通过accept()获取的
服务器的方法:
服务器,返回建立连接的socket对象,服务器的socket是通过accept()方法获取的
关闭连接
一旦建立连接,就只区分发送方和接收方
返回对方的连接地址
返回套接字的输入输出流
客户端的方法:
长连接:连接建立以后,可以多次发送请求
短链接:建立连接后只发送一个请求
TCP网络编程
因为TCP区分客户端与服务器,
服务器需要参加SeverSocket对象来获取客户端的Socket连接对象,通过serverSocket.accept()获取Socket,在获取socket的输入输出流,通过输入输出流进行现场通信
客户端参加链接则需要使用Socket类,将自己的IP和对方的端口号构造一个socket对象,来建立连接,发送请求使用输出流,接收响应使用输入流。
TCP 长连接多线程服务器:
package 网络编程.tcp;
import 网络编程.Log;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Scanner;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TCPService {
public static final int port = 9999;
private static HashMap<String, String> map = new HashMap<>();
static {
map.put("apple","苹果");
map.put("book","书");
map.put("bicycle","自行车");
Log.println("字典初始化完成");
}
private static class Task implements Runnable{
private static Socket socket;
public Task(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
Log.println("准备获取请求方法信息");
Log.println("请求方IP:" + socket.getInetAddress());//获取客户端的IP)
Log.println("请求方端口:" + socket.getPort());//获取客户端的端口
Log.println("请求方IP + port :" + socket.getRemoteSocketAddress());//获取客户端的IP+port
//3,获取客户端的输入流
InputStream inputStream = socket.getInputStream();
Log.println("请求方的输入流:" + inputStream);
Scanner sc = new Scanner(inputStream);//将得到的输入流封装到Scanner,输入流都用Scanner就可以接受
//5,响应请求
OutputStream outputStream = socket.getOutputStream();
Log.println("请求方的输出流 :" + outputStream);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, "UTF-8");
PrintWriter writer = new PrintWriter(outputStreamWriter);
while (true) {
if (!sc.hasNextLine()) {
break;
}
String request = sc.nextLine();//获取到数据
String engLish = request;
Log.println("请求内容 :" + request);
//4,执行业务
String chiWord = translate(engLish);
String response = chiWord;
//5
writer.printf("%s\r\n", response);
writer.flush();
Log.println("开始响应请求");
Log.println("响应内容:" + response);
}
//6,释放socket资源
socket.close();
Log.println("释放请求方socket");
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) throws Exception{
//1.创建TCP套接字
ServerSocket serverSocket = new ServerSocket(port);
//2,获取客户端的套接字,没有请求时会阻塞
ThreadPoolExecutor pool = new ThreadPoolExecutor(3,3,10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());
while (true){
Log.println("等待连接建立");
Socket socket = serverSocket.accept();
Log.println("客户段连接建立,将新的连接提交到线程池");
//创建新的任务提交到线程池
pool.submit(new Task(socket));
}
}
private static String translate(String engWord) {
String chiWord = map.getOrDefault(engWord,"查无此词");
return chiWord;
}
}
TCP客户端
package 网络编程.tcp;
import 网络编程.Log;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TCPClient {
public static void main(String[] args) throws Exception{
//客户端不需要SeverSocket,直接创建Socket对象
//1,创建Socket并将其连接到指定的IP下的指定端口,即连接到服务器
Log.println("准备与服务器创建连接");
Socket socket = new Socket("127.0.0.1",9999);
InputStream inputStream = socket.getInputStream();
Scanner socInput = new Scanner(inputStream,"UTF-8");
OutputStream outputStream = socket.getOutputStream();
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream,"UTF-8");
PrintWriter writer = new PrintWriter(outputStreamWriter);
Log.println("连接建立成功,请输入英文:");
//2,输入请求
Scanner sc = new Scanner(System.in);
while (true){
if (!sc.hasNextLine()){
break;
}
String engWord = sc.nextLine();
String request = engWord + "\r\n";
//3,将请求保存到输出流,发送请求
Log.println("准备发送请求");
writer.write(request);
writer.flush();
Log.println("请求发送成功");
//4,接受响应
Log.println("准备接受响应");
String response = socInput.nextLine();//nextLine返回的值,直接将换行去掉
Log.println("请求:" + engWord);
Log.println("响应:" + response);
}
//5,关闭资源
socket.close();
Log.println("关闭资源");
}
}