可靠传输意为发送方可以感知到接受方有没有接收到数据。
不可靠传输意为发送方不知道接收方有没有接收到数据。
面向字节流:数据是以字节为单位,进行传输的。
这个就非常类似于 文件操作中的文件内容相关的操作中的字节流。
网络传输也是一样!
假设,现有100个字节的数据。
我们可以一直发完。
也可以 一次发 10个字节,发送十次。
也可以 一次发 2 个字节,发送50次。
…
面向数据报:
以数据报为单位,进行传输。
一个数据报都会明确大小。
一次 发送/接收 必须是 一个 完整的数据报。
不能是半个,也不能是一个半,必须是整数个。
全双工:一条链路双向通信
半双工:一条链路,单向通信
1)DatagramSocket(数据报套接字)
2)DatagramPacket(数据报 的数据包)
这两个都是关于UDP数据报的类:
因为刚开始比较简单,因此这个程序就是我们发送什么,他就返回什么。
回显服务器代码:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
//1.网络编程第一步先准备好socket实例
private DatagramSocket socket = null;
//port是服务器的端口号
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
//2.启动服务器
public void start() throws IOException {
System.out.println("启动服务器");
//UDP是不需要建立连接的,直接接收客户端发来的数据即可
while (true){
//1.读取客户端发来的请求
DatagramPacket datagramPacket = new DatagramPacket(new byte[1024],1024);
//为了接收数据,需要事先准备好一个空的DatagramPacket对象,用receive来填充数据
socket.receive(datagramPacket);
//将datagramPacket解析成一个String
String request = new String(datagramPacket.getData(),0,datagramPacket.getLength(),"UTF-8");
//2.根据请求计算响应
String response = process(request);
//3.把响应写回客户端
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
datagramPacket.getSocketAddress());
socket.send(responsePacket);
//打印具体的IP地址、端口号、请求和响应
System.out.printf("[%s:%d]resquest: %s,request: %s\n",datagramPacket.getAddress().toString()
,datagramPacket.getPort(),//客户端端口号
request,//请求
response);//响应
}
}
private static String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
因为我们没有写客户端,因此没有请求,自然服务器也就没有响应
代码展示:
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
//站在客户端的角度:
//源IP:本机IP
//源端口:系统分配的端口
//目的IP:服务器IP
//目的端口:服务器的端口
//协议类型:UDP
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIP;
private int serverPort;
public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
//此处的 serverPort 是服务器的端口
// 服务器启动的时候,不需要 socket来指定窗口,客户端自己的端口是系统随机分配的。
socket = new DatagramSocket();
this.serverIP = serverIP;
this.serverPort = serverPort;
}
// 启动客户端
public void start() throws IOException {
Scanner sc = new Scanner(System.in);
while(true){
//1、先从控制台读取用户输入的字符串
System.out.println("->");
String request = sc.next();
//2、把这个用户输入的内容,构成一个 UDP 请求,并发送给服务器
//构造的请求里面包含两个信息:1、数据的内容(request 字符串);2、数据要发给谁,服务器的 IP + 端口
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(serverIP),serverPort);
socket.send(requestPacket);
//3、从服务器读取响应数据,并解析
DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);
socket.receive(responsePacket);
String response = new String(responsePacket.getData(),0,responsePacket.getLength(),"utf-8");
//4、把响应效果显示到控制台上
System.out.printf("[%s:%d] request: %s,response: %s\n",
serverIP,// 服务器IP
serverPort,// 服务器端口
request,//请求
response);// 响应
}
}
public static void main(String[] args) throws IOException {
//由于服务器 和 客户端 在同一个机器上,所以使用的 IP,仍然是 127.0.0.1,如果是在不同的机器上,这里IP就需要更改了。
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
这就是我们的服务器和客户端来进行请求和响应的代码。平常都是一个服务器,多个客户端。这一个服务器来处理多个客户端的请求。
要先将之前的process的private改为public。
简单字典服务器程序:
import network.UdpEchoServer;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
// 创建一个类 UdpDictionaryServer 来表示 字典服务器
// 因为代码的逻辑几乎是一样,且所有的办法都是public的
// 所以我们在这里就直接继承,就可以使用内部所有的方法,并且可以进行重写操作。
public class UdpDictionaryServer extends UdpEchoServer {
HashMap map = new HashMap<>();//利用HashMap 来构建词库
public UdpDictionaryServer(int port) throws SocketException {
super(port);
map.put("cat","小猫");
map.put("dog","小狗");
map.put("pig","佩奇");
}
@Override
public String process(String request) {
// 如果查询的单词在“词库”中存在,就返回其 键值对/对应的中文,
//反之,如果查询的单词在 “词库”中 不存在,返回 没有对应的词。
return map.getOrDefault(request,"没有对应的词义");
}
public static void main(String[] args) throws IOException {
UdpDictionaryServer dictionaryServer = new UdpDictionaryServer(9090);
dictionaryServer.start();
}
}
构造一个ServerSocket对象:
package network;
import java.io.IOException;
import java.net.ServerSocket;
public class TcpEchoServer {
// listen 的 中文意思是 监听
// 但是,在Java socket 中是体现不出来 “监听”的含义
// 之所以这么叫,其实是 操作系统原生的 API 里有一个操作叫做 listen
// 而 ServerSocket 确实起到了一个监听的效果
// 所以,取个 listenSocket 的名字
private ServerSocket listenSocket = null;
public TcpEchoServer(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
}
TCP服务器代码展示:
public class TcpEchServer {
private ServerSocket listenSocket = null;
public TcpEchServer(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
while (true){
//由于TCP是有连接的,因此不能刚开始就读取数据,需要先建立连接
//accept就是在接电话,前提是有人给你打电话,即有客户端给你发送请求
Socket clientSocket = listenSocket.accept();
processConnection(clientSocket);//处理连接成功的客户端请求
}
}
private void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s,%d] 与客户端建立连接\n",clientSocket.getInetAddress().toString(),//获取客户端IP地址
clientSocket.getPort());//获取客户端端口号
//接下来就是处理需求和返回响应
//这里的针对服务端的读写,和文件操作的读取一样
try(InputStream inputStream = clientSocket.getInputStream()){
try(OutputStream outputStream = clientSocket.getOutputStream()){
Scanner scanner = new Scanner(inputStream);
//循环处理每个请求,分别返回响应
while (true){
//1.读取请求
//如果没有下一个结果,直接结束循环
if(!scanner.hasNext()){
System.out.printf("[%s:%d 客户端断开连接 \n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
//此处用Scanner更方便
String request = scanner.next();
//2.根据请求,计算响应
String response = process(request);
//3.将相应返回给客户端
//为了方便起见,可以使用PrintWriter把OutputStream包裹一下
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();//刷新缓存区
//如果没有这个flush,客户端可能不能第一时间看到响应的结果
System.out.printf("[%s:%d] request:%s,reponse:%s\n",clientSocket.getInetAddress(),
clientSocket.getPort(),
request,
response);
}
}
}catch (IOException e){
e.printStackTrace();
}finally {
clientSocket.close();
}
}
private String process(String request){
return request;
}
}
TCP客户端总代码展示:
package network;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
// 用普通的 Socket 即可,不用 ServerSocket 了
private Socket socket = null;
//此处也不用手动给客户端指定端口号,由系统自动分配(隐式)
public TcpEchoClient(String serverIP,int serverPort) throws IOException {
// 其实这里是可以给定端口号的,但是这里给了之后,含义是不同的。
// 这里传入的 IP 与 端口号 的 含义: 表示的不是自己绑定,而是表示 和 这个IP 端口 建立连接
socket = new Socket(serverIP,serverPort);// 这里表示 与 IP 为serverIP的主机上的 端口为9090的程序,建立连接。
}
public void start(){
System.out.println("和进服务器连接成功!");
Scanner sc = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream()){
try (OutputStream outputStream = socket.getOutputStream()){
while(true){
//1、从控制台读取字符串
System.out.println("->");
String request = sc.next();
//2、根据读取的自妇产,构造请求,把请求发送给服务器
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);// 看似是一个输出语句,其实已经将数据写到服务器里面去了
printWriter.flush();// 记得 立即刷新缓冲区,确保 服务器 第一时间 感知到 请求。
//3、从服务器读取响应,并解析
Scanner scanner = new Scanner(inputStream);
String response = scanner.next();
//4、把结果显示到控制台上。
System.out.printf("request:%s,response:%s\n ",request,response);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
client.start();
}
}
package network;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TcpThreadEchoServer {
// listen 的 中文意思是 监听
// 但是,在Java socket 中是体现不出来 “监听”的含义
// 之所以这么叫,其实是 操作系统原生的 API 里有一个操作叫做 listen
// 而 ServerSocket 确实起到了一个监听的效果
// 所以,取个 listenSocket 的名字
private ServerSocket listenSocket = null;
public TcpThreadEchoServer(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
// 启动服务器
public void start() throws IOException {
System.out.println("服务器启动!");
while(true){
//由于 TCP 是有连接的、因此,不能一上来就读取数据,需要先建立连接
// accept 就是在“接电话”,接电话的前提是:有人给你打电话【有客户端发送请求】
Socket clientSocket = listenSocket.accept();
Thread t = new Thread(()->{
try {
processThreadConnection(clientSocket);// 处理连接成功的客户端请求
} catch (IOException e) {
e.printStackTrace();
}
});
t.start();
}
}
private void processThreadConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s,%d] 客户端建立连接\n",clientSocket.getInetAddress().toString(),// 获取客户端IP地址
clientSocket.getPort());//获取客户端端口
//接下来,就可以来处理请求 和 响应
// 这里的针对 TCP socket 的读写 和 文件操作 的读取一模一样!
try(InputStream inputStream = clientSocket.getInputStream()){
try(OutputStream outputStream = clientSocket.getOutputStream()){
Scanner sc = new Scanner(inputStream);
// 循环处理每个请求,分别返回响应
while(true){
//1、读取请求
// 如果没有下一个结果,直接结束循环。
if(!sc.hasNext()){
System.out.printf("[%s:%d] 客户端断开连接!\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
//此处使用 Scanner 更方便
// 如果不用 Scanner,而使用原生的 InputStream 的 read 也是可以的。
// 但是很麻烦!它需要构造一个 字节数组 来存储 read 读取的数据。
// read 还会返回字节的个数,如果为-1,即为没有后续数据了,读完了。
String request = sc.next();
//2、根据请求,计算响应
String response = process(request);
//3、将响应返回给客户端
//为了方便起见,可以使用 PrintWriter 把 OutputStream 包裹一下
PrintWriter printWriter =new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();// 刷新缓冲区。
// 如果没有这个 flush,可能 客户端就不能第一时间看到响应的结果
System.out.printf("[%s:%d] request:%s,response:%s\n",
clientSocket.getInetAddress(),// IP
clientSocket.getPort(),// 端口
request,//请求
response);// 响应
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
clientSocket.close();
}
}
// 因为是回显服务,不涉及业务。只需要直接返货就可以了
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpThreadEchoServer server = new TcpThreadEchoServer(9090);
server.start();
}
}
package network;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpThreadPool {
private ServerSocket listenSocket = null;
public TcpThreadPool(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
ExecutorService pool = Executors.newCachedThreadPool();
while(true){
Socket clientSocket = listenSocket.accept();
pool.submit(new Runnable() {
@Override
public void run() {
processConnection(clientSocket);
}
});
}
}
private void processConnection(Socket clientSocket) {
System.out.printf("[%s:%d] 客户端建立连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
try (InputStream inputStream = clientSocket.getInputStream()){
try (OutputStream outputStream = clientSocket.getOutputStream()){
Scanner sc = new Scanner(inputStream);
while (true){
if(!sc.hasNext()){
System.out.printf("[%s:%d] 客户端断开连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
String request = sc.next();
String response = process(request);
PrintWriter printWriter= new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();
System.out.printf("[%s:%d] request:%s response:%s\n",
clientSocket.getInetAddress(),
clientSocket.getPort(),
request,
response);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpThreadPool pool = new TcpThreadPool(9090);
pool.start();
}
}
package network;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
// 用普通的 Socket 即可,不用 ServerSocket 了
private Socket socket = null;
//此处也不用手动给客户端指定端口号,由系统自动分配(隐式)
public TcpEchoClient(String serverIP,int serverPort) throws IOException {
// 其实这里是可以给定端口号的,但是这里给了之后,含义是不同的。
// 这里传入的 IP 与 端口号 的 含义: 表示的不是自己绑定,而是表示 和 这个IP 端口 建立连接
socket = new Socket(serverIP,serverPort);// 这里表示 与 IP 为serverIP的主机上的 端口为9090的程序,建立连接。
}
public void start(){
System.out.println("和服务器连接成功!");
Scanner sc = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream()){
try (OutputStream outputStream = socket.getOutputStream()){
while(true){
//1、从控制台读取字符串
System.out.println("->");
String request = sc.next();
//2、根据读取的字符串,构造请求,把请求发送给服务器
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);// 看似是一个输出语句,其实已经将数据写到服务器里面去了
printWriter.flush();// 记得 立即刷新缓冲区,确保 服务器 第一时间 感知到 请求。
//3、从服务器读取响应,并解析
Scanner scanner = new Scanner(inputStream);
String response = scanner.next();
//4、把结果显示到控制台上。
System.out.printf("request:%s,response:%s\n ",request,response);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
client.start();
}
}