湛江专业自助建站详情,最新设计网站大全,正保建工网校,南京好的网站制作公司JavaEE 网络编程 文章目录 JavaEE 网络编程引子1. 网络编程-相关概念1.1 基本概念1.2 发送端和接收端1.3 请求和响应1.4 客户端和服务端 2. Socket 套接字2.1 数据包套接字通信模型2.2 流套接字通信模型2.3 Socket编程注意事项 3. UDP数据报套接字编程3.1 DatagramSocket3.2 Da…JavaEE 网络编程 文章目录 JavaEE 网络编程引子1. 网络编程-相关概念1.1 基本概念1.2 发送端和接收端1.3 请求和响应1.4 客户端和服务端 2. Socket 套接字2.1 数据包套接字通信模型2.2 流套接字通信模型2.3 Socket编程注意事项 3. UDP数据报套接字编程3.1 DatagramSocket3.2 DatagramPacket 4. TCP流套接字编程4.1 ServerSocket4.2 Socket 引子
如今我们在任意浏览器打开在线视频网站如b站去看视频实质上便是通过网络来获取网络上的视频资源 与本地打开视频文件类似只是当前的视频文件资源的来源是网络相比于本地资源来说网络提供了更为丰富的网络资源: 所谓的网络资源其实就是在网络中可以获取的各种数据资源。
而所有的网络资源都是通过网络编程来进行数据传输的。
1. 网络编程-相关概念
1.1 基本概念 网络编程指网络上的主机通过不同的进程以编程的方式实现网络通信(或称为网络数据传输) 网络编程的目的便是提供网络上不同的主机基于网络来传输数据资源如图
进程A编程来获取网络资源进程B编程来提供网络资源
1.2 发送端和接收端
在一次网络数据传输时 发送端: 数据的发送方进程称为发送端。发送端主机即网络通信中的源主机; 接收端: 数据的接受方进程称为接收端。接收端主机即网络通信中的目的主机; 收发端: 发送端和接收端两端也简称收发端 注发送端和接收端只是相对的只是一次网络数据传输产生数据流向后的概念。
1.3 请求和响应
一般来说获取一个网络资源涉及到两次网络数据传输
第一次请求数据的发送第二次响应数据的发送
好比在饭店点一份炒饭先要发起请求点一份炒饭再有饭店提供对应的响应提供一份炒饭 1.4 客户端和服务端
服务端: 在常见的网络数据传输场景下把提供服务的一方进程称为服务端可以提供对外服务客户端: 获取服务的一方进程称为客户端;
一般的客户端服务端模型提供以下操作 客户端获取服务资源 客户端保存资源在服务端
最常见的模型是客户端是指给用户使用的程序服务端是提供用户服务的程序
客户端先发送请求到服务端服务端根据请求数据执行相应的业务处理服务端返回响应发送业务处理结果客户端根据响应数据展示处理结果(展示获取的资源或提示保存数据资源的处理结果) 2. Socket 套接字 Socket套接字是由系统提供用于网络通信的技术是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程 Socket套接字主要针对传输层协议分为如下三类
数据报套接字: 使用传输层UDP协议 UDP即User Datagram Protocol (用户数据报协议)传输层协议。 特点:
无连接不可靠传输面向数据报有接收缓冲区无发送缓冲区大小受限一次最多传输64K
对于数据报来说可以简单理解为传输数据是一块一块的发送一块数据假如要100个字节必须一次发送接收也必须一次接收100个字节而不能分100次每次接收1个字节。
流套接字: 使用传输层TCP协议 TCP即Transmission Control Protocol (传输控制协议)传输层协议。 特点:
有连接可靠传输面向字节流有接收缓冲区也有发送缓冲区大小不限
对于字节流来说可以简单理解为传输数据是基于IO流的流式数据的特征就是在IO流没有关闭的情况下是无边界的数据可以多次发送也可以分开多次接收。
原始套接字: 用于自定义传输层协议用于读写内核没有处理的IP协议数据(了解)。
2.1 数据包套接字通信模型
对于UDP协议来说具有无连接面向数据报的特征即每次都是没有建立连接并且一次发送全部数据报一次接收全部的数据报。
Java中使用UDP协议通信主要基于 DatagramSocket类来创建数据包套接字并使用 DatagramPacket作为发送或接收的UDP数据报。
一般发送及接收UDP数据报的流程如下 2.2 流套接字通信模型
对于TCP协议来说其具有连接且面向字节流的特征在进行网络通信前需要通过accept()接收连接请求建立连接后才可进行通信 2.3 Socket编程注意事项 客户端和服务端开发时经常是基于一个主机开启两个进程作为客户端和服务端但真实的场景一般是不同主机 需要注意目的IP和目的端口号标识了一次数据传输时要发送数据的终点主机和进程 如果一个进程A已经绑定了一个端口再启动一个进程B绑定该端口就会报错这种情况也叫端口被占用。对于Java进程来说端口被占用常见报错信息如下 对此如果占用端口的进程A不需要运行可以关闭A后再启动需要绑定该端口的进程B如果需要运行A进程则可以修改进程B的绑定端口换为其它没有使用的端口。
3. UDP数据报套接字编程
以下是UDP数据报编程相关的API:
3.1 DatagramSocket
DatagramSocket是UDP Socket 用于发送和接收UDP的数据报。
DatagramSocket构造方法
方法签名方法说明DatagramSocket()创建一个UDP数据报套接字的Socket绑定到本机任意一个随机端口一般用于客户端DatagramSocket(int port)创建一个UDP数据报套接字的Socket绑定到本机指定的端口一般用于服务端
DatagramSocket方法
方法签名方法说明void receive(DatagramPacket p)从此套接字接收数据报如果没有接收到数据报该方法会阻塞等待void send(DatagramPacket p)从此套接字发送数据报不会阻塞等待直接发送void close()关闭此数据报套接字
3.2 DatagramPacket
DatagramPacket是UDP Socket发送和接收的数据报传递的数据内容参数都存储在由它构建的对象中。
DatagramPacket构造方法
方法签名方法说明DatagramPacket(byte[] buf, int length)构造一个DatagramPacket用来接收数据报接收的数据报保存在字节数组第一个参数buf中接收指定长度第二个参数lengthDatagramPacket(byte[] offset, int length, SocketAddress address)构造一个DatagramPacker用来发送数据报发送的数据为字节数组第一个参数buff中从0到指定长度第二个参数length。address指定目的主机的IP和端口号
DatagramPacket方法
方法签名方法说明InetAddress getAddress()从接收的数据报中获取发送端主机IP地址或从发送的数据报中和获取接收端主机IP地址int getPort()从接收的数据报中获取发送端主机的端口号或从发送的数据报中获取接收端主机端口号byte[] getData()获取数据报中的数据
在UDP客户端发送数据报时需要传入SocketAddress该对象可以使用InetSocketAddress来创建
方法签名方法说明InetSocketAddress(InetAddress addr, int port)创建一个Socket地址包含IP地址和端口号
代码示例
UDP Echo Server(服务器)
package netWork;import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;/* 回显服务器 */
public class UdpEchoServer {private DatagramSocket socket null;public UdpEchoServer(int port) throws SocketException {socket new DatagramSocket(port);}public void start() throws IOException {System.out.println(服务器启动);while (true) {// 每次循环就是处理一个请求-响应的过程// 1. 读取请求并解析DatagramPacket requestPacket new DatagramPacket(new byte[4096], 4096);socket.receive(requestPacket); // 在客户端没有请求时此处会进入阻塞// 读到的字节数组转成String方便后续的逻辑处理String request new String(requestPacket.getData(), 0, requestPacket.getLength());// 2. 根据请求计算响应 (对于 回显服务器 来说这一步啥都不用做)String response process(request);// 3. 把响应返回给客户端DatagramPacket responsePacket new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress());socket.send(responsePacket);System.out.printf([%s:%d] req: %s, resp: %s\n, requestPacket.getAddress().toString(), requestPacket.getPort(), request, response);}}// 根据请求计算响应由于是回显程序响应内容和请求完全一样public String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpEchoServer server new UdpEchoServer(9090);server.start();}
}UDP Echo Client(客户端)
package netWork;import java.io.IOException;
import java.net.*;
import java.util.Scanner;/* 客户端 */
public class UdpEchoClient {private DatagramSocket socket null;private String serverIp;private int serverPort;// 此处ip使用的字符串点分十进制风格 192.168.2.100public UdpEchoClient(String serverIp, int serverPort) throws SocketException {this.serverIp serverIp;this.serverPort serverPort;socket new DatagramSocket();}public void start() throws IOException {System.out.println(客户端启动);Scanner scanner new Scanner(System.in);while (true) {System.out.print(-); // 提示用户接下来要输入内容// 1. 从控制台读取要发送的请求数据if (!scanner.hasNext()) {break;}String request scanner.next();// 2. 构造请求并发送DatagramPacket requestPacket new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIp), serverPort);socket.send(requestPacket);// 3. 读取服务器的响应DatagramPacket responsePacket new DatagramPacket(new byte[4096], 4096);socket.receive(responsePacket);// 4. 把响应显示到控制台上String response new String(responsePacket.getData(), 0, responsePacket.getLength());System.out.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient client new UdpEchoClient(127.0.0.1, 9090);client.start();}
}UDP Dict Server(字典服务器)
编写一个英译汉的服务器只需要重写process
package netWork;import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;public class UdpDictServer extends UdpEchoServer{HashMapString, String map new HashMap();public UdpDictServer(int port) throws SocketException {super(port);map.put(cat, 猫);map.put(dog, 狗);map.put(apple, 苹果);}Overridepublic String process(String request) {return map.getOrDefault(request, 所查单词不存在);}public static void main(String[] args) throws IOException {UdpDictServer server new UdpDictServer(9090);server.start();}
}注因为此处使用的字典服务器与上述回显服务器端口号相同如果同时启动会报错因此需要关闭其中一个服务器才能使用
4. TCP流套接字编程
以下是TCP流编程相关的API
4.1 ServerSocket
ServerSocket是创建TCP服务端Socket的API
ServerSocket构造方法
方法签名方法说明ServerSocket(int port)创建一个服务端流套接字Socket并绑定到指定端口
ServerSocket方法
方法签名方法说明Socket accept()开始监听指定端口创建时绑定的端口有客户端连接后返回一个服务端Socket对象并基于该Socket建立与客户端的连接否则阻塞等待void close()关闭此套接字
4.2 Socket
Socket有两种存在形式
一种是客户端Socket另一种是服务端中接收到客户端建立连接(accept方法)的请求后返回的服务端Socket。
Socket构造方法
方法签名方法说明Socket(String host, int port)创建一个客户端流套接字Socket并于对应IP的主机上对应端口的进程建立连接
Socket方法
方法签名方法说明InetAddress getInetAddress()返回套接字所连接的地址InputStream getInputStream()返回此套接字的输入流OutputStream getOutputStream()返回此套接字的输出流
代码示例
TCP Echo Server(服务器)
package netWork;import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*TCP 回显服务器*/
public class TcpEchoServer {private ServerSocket serverSocket null;public TcpEchoServer(int port) throws IOException {serverSocket new ServerSocket(port);}public void start() throws IOException {System.out.println(服务器启动);while(true) {// 通过 accept 方法来“接听电话”然后才能进行通信Socket clientSocket serverSocket.accept(); // 在未接收到客户端连接请求前到此处将处于阻塞状态processConnection(clientSocket);}}// 通过这个方法来处理一次连接连接建立的过程中就会涉及到多次的请求响应交互public void processConnection(Socket clientSocket) {System.out.printf([%s:%d] 客户端上线!\n, clientSocket.getInetAddress(), clientSocket.getPort());// 循环的读取客户端的请求并返回响应try (InputStream inputStream clientSocket.getInputStream(); OutputStream outputStream clientSocket.getOutputStream()) {while(true) {Scanner scanner new Scanner(inputStream);if (!scanner.hasNext()) {// 读取完毕客户端断开连接就会产生读取完成!System.out.printf([%s:%d] 客户端下线!\n, clientSocket.getInetAddress(), clientSocket.getPort());break;}// 1. 读取请求并解析这里注意隐藏的约定next 读的时候要读到空白符才会解释// 因此要求客户端发来的请求必须带有空白符结尾比如 \n 或者空格String request scanner.next();// 2. 根据请求计算响应String response process(request);// 3. 把响应返回给客户端// 通过这种方式可以写回但是这种方式不方便给返回的响应中添加 \n//outputStream.write(response.getBytes(), 0, response.getBytes().length);// 可以给 outputStream 套上一层完成更方便的写入PrintWriter printWriter new PrintWriter(outputStream);printWriter.println(response);printWriter.flush(); // 刷新缓冲区System.out.printf([%s:%d] req: %s, resp: %s\n, clientSocket.getInetAddress(), clientSocket.getPort(), request, response);}} catch (IOException e) {throw new RuntimeException(e);}finally {try {clientSocket.close();} catch (IOException e) {throw new RuntimeException(e);}}}public String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server new TcpEchoServer(9090);server.start();}
}TCP Echo Client(客户端)
package netWork;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;public class TcpEchoClient {private Socket socket null;public TcpEchoClient(String serverIp, int serverPort) throws IOException {// 此处可以把这里的 ip 和 port 直接传给socket对象// 因为此处 tcp 是有连接的因此 socket 里面就会保存好这两信息// 因此此处 TcpEchoClient 类就不必保存socket new Socket(serverIp, serverPort);}public void start() throws IOException {System.out.println(客户端启动!);try (InputStream inputStream socket.getInputStream();OutputStream outputStream socket.getOutputStream()) {Scanner scannerConsole new Scanner(System.in);Scanner scannerNetWork new Scanner(inputStream);PrintWriter printWriter new PrintWriter(outputStream);while (true) {System.out.print(-);// 1. 从控制台读取输入的字符串if (!scannerConsole.hasNext()) {break;}String request scannerConsole.next();// 2. 将请求发送给服务器// 这里需要使用 println 来发送为了让发送的请求末尾带有 \n , 这里与服务器的 scanner.next 相呼应printWriter.println(request);// 通过这个 flush 主动刷新缓冲区确保数据真的发出去了printWriter.flush();// 3. 从服务器中接受响应String response scannerNetWork.next();// 4. 把响应显示出来System.out.println(response);}} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) throws IOException {TcpEchoClient client new TcpEchoClient(127.0.0.7, 9090);client.start();}
}为了能够响应多个客户端请求可以进行以下修改
注如果想在IDEA中同时打开多个相同的客户端可以按一下步骤修改设置 给每个客户端都分配一个线程 public void start() throws IOException {System.out.println(服务器启动);while(true) {// 通过 accept 方法来“接听电话”然后才能进行通信Socket clientSocket serverSocket.accept();// processConnection(clientSocket);Thread t new Thread(() - {processConnection(clientSocket);});t.start();}}为了避免频繁创建销毁线程可以引入线程池 public void start() throws IOException {System.out.println(服务器启动);// 可通过调用线程池的方式完成多个客户端的同时连接ExecutorService pool Executors.newCachedThreadPool();while(true) {// 通过 accept 方法来“接听电话”然后才能进行通信Socket clientSocket serverSocket.accept();pool.submit(new Runnable() {Overridepublic void run() {processConnection(clientSocket);}});}}