下面写一个简单的UDP客户端服务器流程 思路: 对于服务器端:读取请求,并解析–> 根据解析出的请求,做出响应(这里是一个回显,)–>把响应写回客户端 对于客户端:从控制台读取用户输入的内容–>从控制台读取用户输入的内容–>从控制台读取用户输入的内容–>将其显示在屏幕上 全部代码如下: 服务器端:

package network;

import java.io.IOException;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.SocketException;

//UDP的回显服务器 客户端发出的请求是啥,服务器返回的响应就是啥

public class UdpEchoServer {

private DatagramSocket socket=null;

// 指定服务器的port

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 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 udpEchoServer=new UdpEchoServer(9090);

udpEchoServer.start();

}

}

客户端:

package network;

import java.io.IOException;

import java.net.*;

import java.util.Scanner;

public class UdpEchoClient {

//由于客户端的port是自动分配的,所以这里不会像服务器那样配置port

//但是,客户端需要向服务器发送请求,所以,这里我们需要知道服务器的ip和port

private DatagramSocket socket=null;

private String serverIp;

private int serverPort;

//外部指定服务器的ip和port

public UdpEchoClient(String ip,int port) throws SocketException {

this.serverIp=ip;

this.serverPort=port;

//客户端的port是自动分配的

socket=new DatagramSocket();

}

// 让这个客户端反复的从控制台读取用户输入的内容. 把这个内容构造成 UDP 请求, 发给服务器. 再读取服务器返回的 UDP 响应

// 最终再显示在客户端的屏幕上.

public void start() throws IOException {

Scanner scanner=new Scanner(System.in);

System.out.println("客户端开始启动");

while(true){

//1. 从控制台读取用户输入的内容

System.out.println("->");

String requset=scanner.next();

//2.构造请求对象,发送给服务器

DatagramPacket requsetPacket=new DatagramPacket(requset.getBytes(),requset.getBytes().length,

InetAddress.getByName(serverIp),serverPort);

socket.send(requsetPacket);

//3.读取服务器的响应,并解析出其内容

DatagramPacket responsePacket=new DatagramPacket(new byte[4096],4096);

socket.receive(responsePacket);

String response=new String(responsePacket.getData(),0,responsePacket.getLength());

//4 。将其显示在屏幕上

System.out.println(response);

}

}

public static void main(String[] args) throws IOException {

UdpEchoClient udpEchoClient=new UdpEchoClient("127.0.0.1",9090);//127.0.0.1 本机ip

udpEchoClient.start();

}

}

运行结果如下 对上述过程中的一些谈论和分析:

多个客户端向一个服务器发送请求

下面写一个简单的翻译服务器

重写的服务器端的代码如下:

package network;

import java.io.IOException;

import java.net.SocketException;

import java.util.HashMap;

import java.util.Map;

public class UdpDictServer extends UdpEchoServer{

//使用HashMap保存中英文翻译的键值对

private Map dict =new HashMap<>();

//实现父类的构造方法

public UdpDictServer(int port) throws SocketException {

super(port);

//一些原始的键值对

dict.put("cat","猫");

dict.put("dog","狗");

dict.put("people","人");

}

//与原始的UdpEachServer相比,这里对于请求的处理过程是不一样的

//重写process方法

@Override

public String process(String request) {

//找到对应的翻译,并返回

//getOrDefault方法,找到key所对应的value值,如果没有找到,则返回defaultValue(即第二个参数)

return dict.getOrDefault(request,"该词没有查询到");

}

public static void main(String[] args) throws IOException {

UdpDictServer server=new UdpDictServer(9090);

// start 不需要重新再写一遍了. 直接就复用了之前的 start

server.start();

}

}

执行结果如下:

下面写一个基于TCP 的回显流程

思路: 服务器端:先从队列中拿到一个“连接”–> 读取请求并解析–>根据请求计算响应–>把响应写回给客户端 客户端:从控制台输入字符串–>把请求发送给服务器–>从服务器读取响应.–>把响应打印出来

全部代码如下: 服务器端代码:

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;

//基于TCP的回显服务器

public class TcpEachServer {

private ServerSocket serverSocket=null;

//绑定端口号

public TcpEachServer(int port) throws IOException {

serverSocket=new ServerSocket(port);

}

//启动服务器

public void start() throws IOException {

System.out.println("服务器开始启动");

while(true){

//从管理连接的队列中拿出一个“连接”出来

Socket clientSocket=serverSocket.accept();

//处理这个连接内的请求

processConnection(clientSocket);

}

}

//这个方法用来处理连接中的逻辑

private void processConnection(Socket clientSocket) throws IOException {

//日志

System.out.printf("[%s:%d] 客户端上线\n",clientSocket.getInetAddress().toString(),

clientSocket.getPort());

//下面开始读取请求,计算响应,返回响应 三步曲

//Socket对象内部包含两种字节流对象InputStream和OutputStream,可以先把这两个对象流获

// 取到,方便后续处理过程种的读写工作

try(InputStream inputStream=clientSocket.getInputStream();

OutputStream outputStream=clientSocket.getOutputStream()){

//不同于UDP协议中的无连接,在客户端的一次连接过程中,可能涉及多次请求/响应过程

//因此。这里使用一个while循环,直到该连接中的所有请求处理完毕

while(true){

//1,读取请求并解析

Scanner scanner=new Scanner(inputStream);

//hasNext的作用是,检测输入流中是否有结束输入的控制符,比如0x1A(EOF,Ctrl-Z)

//用于检测一个连接是否结束

if(!scanner.hasNext()){

//一个连接处理完毕

System.out.printf("[%s:%d] 客户端本次连接处理完毕,下线!\n",clientSocket.getInetAddress().toString(),

clientSocket.getPort());

break;

}

// 这个代码暗含一个约定, 客户端发过来的请求, 得是文本数据, 同时, 还得带有空白符作为分割. (比如换行这种)

//next():当输入到空白符结束

String request=scanner.next();

//2.根据请求计算响应

String response=process(request);

//3. 把响应写回客户端,把OutputStream用PrintWriter(此处的PrintWriter相当于Scanner)包裹一下,便于发送数据

//将outputStream和PrintWriter关联起来

PrintWriter writer=new PrintWriter(outputStream);

//使用 PrintWriter 的 println 方法,打印到输出流中 把响应返回给客户端.

//此处用 println, 而不是 print 就是为了在结尾加上 \n . 方便客户端读取响应, 使用 scanner.next 读取.

writer.println(response);

//这里还需要加一个 "刷新缓冲区" 操作.将缓冲区的数据强制输出,用于清空缓冲区

writer.flush();

//日志 记录当前的请求和响应

System.out.printf("[%s:%d] req: %s,resp: %s\n",clientSocket.getInetAddress().toString(),

clientSocket.getPort(),request,response);

}

}

}

//回显,只需要再返回这个字符串

public String process(String requset){

return requset;

}

public static void main(String[] args) throws IOException {

TcpEachServer tcpEachServer=new TcpEachServer(9090);

tcpEachServer.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 {

private Socket socket=null;

//服务器端的ip和port

public TcpEchoClient(String serverIp,int serverPort) throws IOException {

//这个new的动作完成后,完成了tcp的建立

socket=new Socket(serverIp,serverPort);

}

public void start() throws IOException {

System.out.println("客户端启动");

Scanner scannerConsole=new Scanner(System.in);

//Socket对象内部包含两种字节流对象InputStream和OutputStream,可以先把这两个对象流获

// 取到,方便后续处理过程种的读写工作

try(InputStream inputStream=socket.getInputStream();

OutputStream outputStream=socket.getOutputStream()){

while(true){

//1.从控制台输入字符串

System.out.println("-->");

String request=scannerConsole.next();

//2.把请求发送给服务器 需要对request进行包装,使用PrintWriter

PrintWriter printWriter=new PrintWriter(outputStream);

//使用 println 带上换行. 后续服务器读取请求, 就可以使用 scanner.next 来获取了

printWriter.println(request);//发送请求

printWriter.flush();

//3.从服务器中接收响应

Scanner scannerNetwork=new Scanner(inputStream);

String response=scannerNetwork.next();

//4.把响应打印出来

System.out.println(response);

}

}

}

public static void main(String[] args) throws IOException {

TcpEchoClient tcpEchoClient=new TcpEchoClient("127.0.0.1",9090);

tcpEchoClient.start();

}

}

当开多个线程时,发现只有一个线程在被处理,其它线程都在等待, 当被处理的线程下线后,其他线程的逻辑才开始被处理

原因在于 Socket clientSocket = serverSocket.accept();和processConnection(clientSocket);都是主线程进行处理的且在同一次循环体中,只有一个clinetSocket连接被处理完后,才会去队列中accept下一个连接,为此,这里我们可以采用多线程进行处理。

修改为多线程后,可以看到 有多个客户端可以访问服务器

考虑到一个现实的情况,许多客户端需要频繁的访问服务器,那就是需要频繁的断开/连接,我们这里可以使用线程池 同样也可以实现多个客户端同时访问服务器。

最终的服务器的代码如下:

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;

//基于TCP的回显服务器

public class TcpEachServer {

private ServerSocket serverSocket=null;

//创建一个非固定数目的线程池

private ExecutorService service= Executors.newCachedThreadPool();

//绑定端口号

public TcpEachServer(int port) throws IOException {

serverSocket=new ServerSocket(port);

}

//启动服务器

public void start() throws IOException {

System.out.println("服务器开始启动");

while(true){

//从管理连接的队列中拿出一个“连接”出来

Socket clientSocket=serverSocket.accept();

//处理这个连接内的请求

service.submit(new Runnable() {

@Override

public void run() {

try {

processConnection(clientSocket);

} catch (IOException e) {

e.printStackTrace();

}

}

});

/* Thread t=new Thread(() ->{

try {

processConnection(clientSocket);

} catch (IOException e) {

e.printStackTrace();

}

});

t.start();*/

}

}

//这个方法用来处理连接中的逻辑

private void processConnection(Socket clientSocket) throws IOException {

//日志

System.out.printf("[%s:%d] 客户端上线\n",clientSocket.getInetAddress().toString(),

clientSocket.getPort());

//下面开始读取请求,计算响应,返回响应 三步曲

//Socket对象内部包含两种字节流对象InputStream和OutputStream,可以先把这两个对象流获

// 取到,方便后续处理过程种的读写工作

try(InputStream inputStream=clientSocket.getInputStream();

OutputStream outputStream=clientSocket.getOutputStream()){

//不同于UDP协议中的无连接,在客户端的一次连接过程中,可能涉及多次请求/响应过程

//因此。这里使用一个while循环,直到该连接中的所有请求处理完毕

while(true){

//1,读取请求并解析

Scanner scanner=new Scanner(inputStream);

//hasNext的作用是,检测输入流中是否有结束输入的控制符,比如0x1A(EOF,Ctrl-Z)

//用于检测一个连接是否结束

if(!scanner.hasNext()){

//一个连接处理完毕

System.out.printf("[%s:%d] 客户端本次连接处理完毕,下线!\n",clientSocket.getInetAddress().toString(),

clientSocket.getPort());

break;

}

// 这个代码暗含一个约定, 客户端发过来的请求, 得是文本数据, 同时, 还得带有空白符作为分割. (比如换行这种)

//next():当输入到空白符结束

String request=scanner.next();

//2.根据请求计算响应

String response=process(request);

//3. 把响应写回客户端,把OutputStream用PrintWriter(此处的PrintWriter相当于Scanner)包裹一下,便于发送数据

//将outputStream和PrintWriter关联起来

PrintWriter writer=new PrintWriter(outputStream);

//使用 PrintWriter 的 println 方法,打印到输出流中 把响应返回给客户端.

//此处用 println, 而不是 print 就是为了在结尾加上 \n . 方便客户端读取响应, 使用 scanner.next 读取.

writer.println(response);

//这里还需要加一个 "刷新缓冲区" 操作.将缓冲区的数据强制输出,用于清空缓冲区

writer.flush();

//日志 记录当前的请求和响应

System.out.printf("[%s:%d] req: %s,resp: %s\n",clientSocket.getInetAddress().toString(),

clientSocket.getPort(),request,response);

}

} finally {

clientSocket.close();

}

}

//回显,只需要再返回这个字符串

public String process(String requset){

return requset;

}

public static void main(String[] args) throws IOException {

TcpEachServer tcpEachServer=new TcpEachServer(9090);

tcpEachServer.start();

}

}

上述过程中的一些思路

精彩内容

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: