此篇原理不讲直接附代码~~~
服务器
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
// 回显服务器
public class UdpEchoServer {
// 要创建一个服务器,先打开一个socket文件
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为基本单位
DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(requestPacket); // 把接收到的数据包放到该方法的参数中(输出型参数)如果此时客户端没有发送请求,就该方法就陷入阻塞等待中
// 2、对请求进行解析,把DatagramPacket转成一个String
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
// 3.处理请求
String response = process(request);
// 4、把响应构造成DatagramPacket对象
// 构造响应对象,要搞清楚,对象要发给谁,谁给咱们发的请求,咱就发给谁,response.length返回的是字符个数,response.getBytes().length返回的是字节个数
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress());
// 5、发送响应给客户
socket.send(responsePacket);
// 打印日志, requestPacket.getAddress()返回读取的客户端IP地址, requestPacket.getPort()返回读取的客户端端口号
System.out.printf("发送方的ip和端口号:[%s:%d] request:%s, response:%s", requestPacket.getAddress().toString(), requestPacket.getPort(), request, response);
System.out.println();
}
}
// 对请求进行响应处理
public String process(String request) {
return request; // 回显处理就是请求发的是什么就响应什么
}
public static void main(String[] args) throws IOException {
UdpEchoServer udpEchoServer = new UdpEchoServer(8080);
udpEchoServer.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.nio.charset.StandardCharsets;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
public UdpEchoClient() throws SocketException {
socket = new DatagramSocket(); // 在客户端,我们一般不自己指定端口号,而是由系统分配
}
public void start() throws IOException {
while (true) {
System.out.println("当前是客户端!");
System.out.print(">");
// 1、客户端从键盘输入请求的具体内容
Scanner scanner = new Scanner(System.in);
String request = scanner.nextLine();
// 要说明要给谁发送请求,他的ip地址和端口号是多少(服务器的)
// DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.length(), InetAddress.getByName("127.0.0.1"), 8000);
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
InetAddress.getByName("127.0.0.1"), 8080);
// 2、客户端向服务器发送请求
socket.send(requestPacket);
// 3、客户端接收服务器返回的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePacket); // 注意我们是把服务器的响应存放到了responsePacket中,不要写错
// 4、客户端显示服务器返回的响应信息,进行解析转换成String类型的信息
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
System.out.printf("发送方的ip地址和端口号:[%s:%s], request: %s, response: %s\n", requestPacket.getAddress(), requestPacket.getPort(), request, response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient();
client.start();
}
}
服务器
package network;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
// 字典服务器
public class UdpDicServer extends UdpEchoServer {
public Map<String, String> map = new HashMap<>();
public UdpDicServer(int port) throws SocketException { // 构造方法
super(port);
map.put("synchronized", "同步的");
map.put("volatile", "易变的");
}
@Override
public String process(String request) {
return map.getOrDefault(request, "抱歉,当前尚未收录此词!");
}
public static void main(String[] args) throws IOException {
UdpDicServer server = new UdpDicServer(8080);
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.nio.charset.StandardCharsets;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
public UdpEchoClient() throws SocketException {
socket = new DatagramSocket(); // 在客户端,我们一般不自己指定端口号,而是由系统分配
}
public void start() throws IOException {
while (true) {
System.out.println("当前是客户端!");
System.out.print(">");
// 1、客户端从键盘输入请求的具体内容
Scanner scanner = new Scanner(System.in);
String request = scanner.nextLine();
// 要说明要给谁发送请求,他的ip地址和端口号是多少(服务器的)
// DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.length(), InetAddress.getByName("127.0.0.1"), 8000);
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
InetAddress.getByName("127.0.0.1"),8080);
// 2、客户端向服务器发送请求
socket.send(requestPacket);
// 3、客户端接收服务器返回的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePacket); // 注意我们是把服务器的响应存放到了responsePacket中,不要写错
// 4、客户端显示服务器返回的响应信息,进行解析转换成String类型的信息
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
System.out.printf("发送方的ip地址和端口号:[%s:%s], request: %s, response: %s\n", requestPacket.getAddress(), requestPacket.getPort(), request, response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient();
client.start();
}
}
服务器
package network;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpEchoServer {
ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
ExecutorService service = Executors.newCachedThreadPool();// 此处不太适合使用 "固定个数的"
while (true) {
// 服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket
// 如果当前没有客户端来建立连接, 就会阻塞等待!!
Socket clientSocket = serverSocket.accept();
// processConnection(clientSocket); // 单线程模式——即该服务器无法同时为多个客户端同时提供服务
// [版本2] 多线程版本. 主线程负责拉客, 新线程负责通信
// 虽然比版本1 有提升了, 但是涉及到频繁创建销毁线程, 在高并发的情况下, 负担比较重的.
// Thread thread = new Thread(() -> {
// try {
// processConnection(clientSocket);
// } catch (IOException e) {
// e.printStackTrace();
// }
// });
// thread.start();
// [版本3] 使用线程池, 来解决频繁创建销毁线程的问题.
// 此处不太适合使用 "固定个数的"
service.submit(new Runnable() {
@Override
public void run() {
try {
processConnection(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
// 通过这个方法, 给当前连上的这个客户端, 提供服务!!
// 这里我们建立长连接——一次连接中,会有 N次请求,N次响应
public void processConnection(Socket clientSocket) throws IOException {
System.out.printf("连接成功![%s:%d]\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream() ) {
Scanner scannerNet = new Scanner(inputStream); // 对我们的读取进行嵌套
PrintWriter printWriter = new PrintWriter(outputStream);
while (true) {
if (!scannerNet.hasNext()) {
// 说明此时连接中断,服务器无法继续从客户端读取到请求
// 连接断开. 当客户端断开连接的时候, 此时 hasNext 就会返回 false
System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
break;
}
// 1、读取客户端的请求
String request = scannerNet.nextLine();
// 从客户端读取到的请求,如果客户端在输入请求的时候,只是通过nextLine敲了回车,然后就通过printWrite.write发送给我们的服务器,
// 这时服务器接收到的数据是不带换行符的,换行符在我们的客户端nextLine输入的过程中就已经被吞吃了,所有为了避免String request = scanner.nextLine();无法正常读取(nextLine只有遇到换行才结束)
// 所以在我们的客户端在发送时要通过PrintWrite.println()来发送,会给我们自动添加一个换行符
// 2、处理客户端发来的请求
String response = process(request);
// 3、把响应发送给客户端
printWriter.println(response);
// printWriter.write(response); // 这样写有bug,因为我们当前的response中没有带换行符,
printWriter.flush(); // 刷新缓冲区
// 打印日志
System.out.printf("[%s:%d] request: %s, response: %s\n",
clientSocket.getInetAddress(), clientSocket.getPort(),
request, response);
}
}
finally {
clientSocket.close(); // 每一次建立连接都会创建一个clientSocket,该连接结束后要及时关闭,避免内存泄漏
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(8080);
server.start();
}
}
客户端
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 {
Socket socket = null;
public TcpEchoClient() throws IOException {
// 指定客户端要连接的服务器
socket = new Socket("127.0.0.1", 8080);
// 程序走到了这里,说明客户端和服务器就已经成功建立了连接
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("当前是客户端,请输入请求");
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) { // InputStream用于读取, OutputStream用于发送
// 对我们的读取和发送进行写包装,使得读取和包装更方便
Scanner scannerNet = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
// 这里我们建立长连接——一次连接中,会有 N次请求,N次响应
while (true) {
System.out.print("> ");
// 1、客户端从键盘输入请求
String request = scanner.nextLine();
// 在这里我们虽然在输入内容后用换行符来进行了结束,但我们用于接收的request并没有读取的换行符——它被nextLine()给吞吃了
// 2、把请求发送给服务器
printWriter.println(request);
// printWriter.write(request);
// 这么写会产生一个bug, 要printWriter.println()会自动给我们要写入的添加一个换行符
// 可以让服务器在读取请求时遇到换行就结束,不至于陷入阻塞
printWriter.flush(); // 刷新缓冲区
// 3、从服务器读取响应内容
String response = scannerNet.nextLine();
// 4、打印日志
System.out.printf("request: %s, response: %s\n", request, response);
}
}
finally {
socket.close();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient();
client.start();
}
}
服务器
package network;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class TcpDicServer extends TcpEchoServer{
Map<String, String> map = new HashMap<>();
public TcpDicServer(int port) throws IOException {
super(port);
map.put("cat", "小猫");
map.put("dog", "小狗");
}
@Override
public String process(String request) {
return map.getOrDefault(request, "抱歉!暂时未收录此词!");
}
public static void main(String[] args) throws IOException {
TcpDicServer tcpDicServer = new TcpDicServer(8080);
tcpDicServer.start();
}
}
客户端
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 {
Socket socket = null;
public TcpEchoClient() throws IOException {
// 指定客户端要连接的服务器
socket = new Socket("127.0.0.1", 8080);
// 程序走到了这里,说明客户端和服务器就已经成功建立了连接
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("当前是客户端,请输入请求");
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) { // InputStream用于读取, OutputStream用于发送
// 对我们的读取和发送进行写包装,使得读取和包装更方便
Scanner scannerNet = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
// 这里我们建立长连接——一次连接中,会有 N次请求,N次响应
while (true) {
System.out.print("> ");
// 1、客户端从键盘输入请求
String request = scanner.nextLine();
// 在这里我们虽然在输入内容后用换行符来进行了结束,但我们用于接收的request并没有读取的换行符——它被nextLine()给吞吃了
// 2、把请求发送给服务器
printWriter.println(request);
// printWriter.write(request);
// 这么写会产生一个bug, 要printWriter.println()会自动给我们要写入的添加一个换行符
// 可以让服务器在读取请求时遇到换行就结束,不至于陷入阻塞
printWriter.flush(); // 刷新缓冲区
// 3、从服务器读取响应内容
String response = scannerNet.nextLine();
// 4、打印日志
System.out.printf("request: %s, response: %s\n", request, response);
}
}
finally {
socket.close();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient();
client.start();
}
}