Java网络编程之UDP和TCP套接字
文章目录
- 一. 网络编程概述
- 二. UDP网络编程
- 1. UDP套接字
- 2. UDP客户端回显服务器程序
- 2.1 UDP回显服务器
- 2.2 UDP客户端
- 2.3 UDP实现查词典的服务器
- 三. TCP网络编程
- 1. TCP套接字
- 2. TCP客户端回显服务器程序
- 2.1 TCP回显服务器
- 2.2 TCP客户端
- 2.3 解决服务器无法同时出力多个客户端的问题
- 2.4 TCP实现查词典的服务器
一. 网络编程概述
我们知道在网络通信中, 数据的发送是从应用层开始, 一直封装到物理层然后进行发送的, 应用层要将数据交给传输层进行封装; 而接收方拿到数据后是从物理层到应用层进行分用, 传输层要将拿到的数据再分用给应用层进行使用, 网络编程实际操作中最关键的就是我们所能控制的应用层和传输层之间的交互, 而在操作系统中提供了一组API即socket
, 用来实现应用层和传输层之间的交互, Java当中把操作系统提供的API进行了进一步封装以便我们进行使用.
常见传输层协议有UDP和TCP两种, 其中UDP
的特点是无连接, 不可靠传输, 面向数据报, 全双工; TCP
的特点是有连接, 可靠传输, 面向字节流, 全双工.
使用TCP协议, 必须是通信双方先建立连接才能进行通信(想象打电话的场景), 而使用UDP协议在无连接的情况下可以进行通信(想象发微信, 短信的场景).
这里的可靠与不可靠传输指的不是安全性质, 而是说你发送出数据后, 能不能判断对方已经收到, 如果能够确定对方是否收到则就是可靠传输, 否则就是不可靠传输.
面向字节流就类文件读写数据的操作, 是 “流” 式的; 而面向数据报的话数据传输则是以一个个的 “数据报” 为基本单位(一个数据报可能是若干个字节, 是带有一定的格式的).
全双工是指一条通信链路, 可以双向传输(同一时间既可以发, 也可以收); 而半双工是一条链路, 只能单向通信.
二. UDP网络编程
1. UDP套接字
UDP
类型的Socket, 涉及两个核心类, 一个是DatagramSocket
, 其实例的对象表示UDP版本的Socket, 操作系统中将网卡这种硬件设备也抽象成了文件进行处理, 这个Soket对象也就成了文件描述表上面的一项, 通过操作这个Socket文件来间接的操作网卡, 就可以通信了.
有一个Socket对象就可以与另一台主机进行通信了, 但如果要和不同的主机通信, 就需要创建多个Socket对象, 使用DatagramSocket既可以发, 也可以收, 体现了UDP全双工的特点.
构造方法 | 作用 |
---|---|
DatagramSocket() | 创建一个UDP数据报套接字的Socket, 绑定到任意一个随机端口号(一般用于客户端) |
DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket, 绑定到本机指定端口(一般用于服务器) |
关于这两个方法也是好理解的, 一般服务器的端口号需要自己指定, 如果随机分配的话, 你的客户端要怎么访问你的服务器呢?毕竟客户端才是主动的一方, 知道服务器在哪里才能找到它进行通信吧.
关键方法 | 作用 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据 (如果没有接收到数据报, 进行阻塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据包 (不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
这里receive
方法参数传入的是一个空的对象, receive方法内部会对这个对象进行填充, 从而构造出结果数据, 这个参数也是一个输出型参数.
第二个是DatagramPacket
, 表示一个UDP数据报, 在UDP的服务器和客户端都需要使用到, 接收和发送的数据就是在传输DatagramPacket对象, 这就是体现了UDP面向数据报的特点.
构造方法 | 作用 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造一个DatagramPacket用来接收数据报,接收的数据保存在字节数组里, 接受指定长度 |
DatagramPacket(byte[] buf, int offset,int length, SocketAddress address) | 构造一个DatagramPacket用来发送数据报,发送的数据为字节数据,从0到指定长度,address用来指定目的主机的IP和端口号 |
关键方法 | 作用 |
---|---|
InetAddress getAddress() | 从接受的数据报中,获取发送端IP地址,或从发送的数据报中,获取接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号,或从发送的数据报中,获取接收端的端口号 |
SocketAddress getSocketAddress() | 从接收的数据报中,获取发送端主机SocketAddress,或从发送的数据报中,获取接收端的SocketAddress(IP地址+端口号) |
byte[] getData() | 获取数据报的数据 |
在网络编程中, 一定要注意区分清楚服务器与客户端使用之间使用的五元组, 具体如下:
- 源IP, 就是发送方IP.
- 源端口, 发送方端口号, 服务器需要手动指定, 客户端让系统随机分配即可.
- 目的IP, 接收方IP, 包含在拿到的数据报中, 服务器响应时的目的IP就在客户端发来的数据报中, 客户端发送请求时的目的IP就是服务器的IP.
- 目的端口, 接收方端口号包含在数据报中, 服务器响应时的目的端口就在客户端发来的数据报中, 客户端发送请求时的目的端口就是服务器的端口号.
- 协议类型, 如UDP/TCP.
2. UDP客户端回显服务器程序
正常来说, 客户端和服务器程序要实现的是, 客户端发送请求, 服务器接收请求后, 要根据请求计算响应(业务逻辑), 然后把响应返回给客户端.
而在这里只是要演示Socket api的用法, 就不涉及业务逻辑了, 我们让服务器收到什么就给客户端返回什么, 这样实现服务器就叫做回显服务器.
2.1 UDP回显服务器
UDP服务器设计步骤:
- 创建Socket实例对象(
DatagramSocket
对象), 需要指定服务器的端口号, 因为服务器是被动接收和处理请求的一端, 而客户端是主动发起请求的一端, 客户端必须得知道服务器在哪里才能发送请求, 也就是需要知道服务器的端口号. - 服务器启动, 读取客户端请求, 把得到的数据填充到
DatagramPacket
对象中, 这里得到的请求中是包含着有关客户端的地址信息的(IP+端口号), 可以通过getSocketAddress
方法获取到. - 处理客户端请求, 计算响应, 这里实现的是一个回显服务,直接根据请求返回相同的响应即可, 但在实际开发中, 这个处理请求的部分其实是最关键的.
- 将响应返回给客户端, 要将响应数据构造成
DatagramPacket
对象, 注意要给出客户端的地址信息.
代码如下:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;// UDP版本的回显服务器
public class UdpEchoServer {//准备好socket实例,准备传输private DatagramSocket socket = null;//构造时指定服务器的端口号public UdpEchoServer(int port) throws SocketException {this.socket = new DatagramSocket(port);}public void start() throws IOException {System.out.println("服务器启动!");//服务器要给多个客户端提供服务while (true) {//1. 读取客户端发过来的请求DatagramPacket requstPacket = new DatagramPacket(new byte[4096], 4096);//receive内部会对参数对象进行填充数据,填充的数据来源于网卡socket.receive(requstPacket);//解析收到的数据包,一般解析成字符串进行处理; 构造字符串的参数分别为数据数组,存入数据数组的起始下标,长度String requst = new String(requstPacket.getData(), 0, requstPacket.getLength());//2. 根据请求计算响应String response = process(requst);//3. 把响应写回到客户端中DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,requstPacket.getSocketAddress());socket.send(responsePacket);//输出一下发送日志System.out.printf("[%s:%d] req: %s, resp: %s \n", requstPacket.getAddress().toString(),requstPacket.getPort(), requst, response);}}public String process(String requst) {//...处理数据,这里是回显服务直返回原数据即可return requst;}public static void main(String[] args) throws IOException {// 1024 到 65535即可UdpEchoServer server = new UdpEchoServer(9090);server.start();}
}
上面代码中要注意
这里response.getBytes().length
要注意不能写成response.length()
, 因为DatagramPacket
不认字符只认字节, response.length()获取的是有效字符数, response.getBytes().length获取的是有效字节数.
代码中的循环是一个死循环, 这样设施也是没有问题的, 大部分服务器都是要7 * 24
小时运行的.
2.2 UDP客户端
UDP客户端设计步骤:
- 创建Socket实例对象(
DatagramSocket
对象), 可以指定端口号创建也可以让系统随机分配, 自己指定端口号容易与已经被使用的端口号冲突, 所以这里让所以系统随机分配更就行了, 不用担心端口号的冲突的问题. - 用户输入请求, 并使用
DatagramPacket
对象打包数据请求, 要注意给出服务器的地址(IP和端口号), 并将请求发送. - 读取服务器返回的响应并进行处理.
代码如下:
import java.io.IOException;
import java.net.*;
import java.util.Scanner;//UDP版本的回显客户端
public class UdpEchoCliet {private DatagramSocket socket = null;//需要指定服务器的ip和端口号private String serverIp = null;private int serverPort = 0;public UdpEchoCliet(String serverIp, int serverPort) throws SocketException {//让系统分配一个空闲的端口号即可this.socket = new DatagramSocket();this.serverIp = serverIp;this.serverPort = serverPort;}public void start() throws IOException {System.out.println("客户端启动!");Scanner scanner = new Scanner(System.in);while (true) {// 1. 从控制台读取要发送的数据System.out.print("> ");String request = scanner.next();if (request.equals("exit")) {System.out.println("客户端退出");break;}// 2. 构造UDP请求,并发送//上面的IP地址是一个字符串,需要使用InetAddress.getByName来转换成一个整数.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);String response = new String(responsePacket.getData(), 0, responsePacket.getLength());// 4. 把解析好的结果显示出来System.out.println(response);}}public static void main(String[] args) throws IOException {//在本机上测试,127.0.0.1是IP,表示自己主机UdpEchoCliet cliet = new UdpEchoCliet("127.0.0.1", 9090);cliet.start();}
}
上面说到, 为了防止客户端端口号的冲突, 我们让系统为客户端随机分配了端口, 那么客户端为什么就不怕端口号冲突呢?
其实也好理解, 服务器肯定是程序员自己手里的机器, 上面运行啥, 程序员就可以安排哪个程序使用哪个端口, 也就是说, 服务器上面的程序是可控的; 而客户端是运行在用户电脑上的, 环境复杂, 不可控性高.
对于客户端服务器程序来说一个服务器是要给许多客户端提供服务的, 但是IDEA
默认只能启动一个客户端, 要想测试多个客户端, 需要我们手动设置一下IDEA.
第一步, 右键代码编辑处, 按下图进行操作.
第二步, 找到Modify options
并点击.
第三步, 勾选上Allow multiple instances
后, 点击OK即可.
测试结果:
首先一定要先启动服务, 然后再启动客户端进行测试.
2.3 UDP实现查词典的服务器
上面实现的回显服务器缺乏业务逻辑, 这里在上面的代码的基础上稍作调整, 实现一个 “查词典” 的服务器(将英文单词翻译成中文解释), 这里其实就很简单了, 对于客户端的代码还可以继续使用, 服务器只需把处理请求部分的代码修改即可, 我们可以继承上面的回显服务器, 重写请求部分的代码, 英语单词和汉语解释可以由一个哈希表实现映射关系, 构成词库, 然后根据请求来获取哈希表中对应的汉语解释即可.
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;public class UdpDictServer extends UdpEchoServer{private Map<String, String> dict = new HashMap<>();public UdpDictServer(int port) throws SocketException {super(port);//词库dict.put("cat", "小猫");dict.put("dog", "小狗");dict.put("bird", "小鸟");dict.put("apple", "苹果");dict.put("banana", "香蕉");dict.put("strawberry", "草莓");dict.put("watermelon", "西瓜");//...}@Overridepublic String process (String request) {//查词典return dict.getOrDefault(request, "当前单词没有查到结果!");}public static void main(String[] args) throws IOException {UdpDictServer server = new UdpDictServer(9090);server.start();}
}
关于网络编程这里涉及一个重要的异常, 如下图:
这是一个表示端口冲突的异常, 一个端口只能被一个进程使用, 如果有多个进程使用同一个端口, 就会出现如上图的异常.
三. TCP网络编程
1. TCP套接字
TCP相比于UDP有很大的不同, TCP
的话首先需要通信双方成功建立连接然后才可以进行通信, TCP进行网络编程的方式和文件读写中的字节流类似, 是字节为单位的流式传输, 如果对下面涉及的IO流操作不熟悉的话, 可以看一看我前面的一篇博客 Java文件IO操作及案例 .
对于TCP的套接字, Java提供了两个类来进行数据的传输, 一个是ServerSocket
, 是专门给服务器使用的Socket对象, 用来让服务器接收客户端的连接;
构造方法 | 解释 |
---|---|
ServerSocket(int port) | 创建一个服务器套接字Socket,并指定端口号 |
关键方法 | 解释 |
---|---|
Socket accept() | 开始监听指定端口,有客户端连接时,返回一个服务端Socket对象,并基于该Socket对象与客户端建立连接,否则阻塞等待 |
void close() | 关闭此套接字 |
第二个是Socket
, 这个类在客户端和服务器都会使用, 进行服务器与客户端之间的数据传输通信, TCP的传输可以类比打电话的场景, 客户端发送请求后, 服务器调用ServerSocket
类的accept
方法来 “建立连接” (接通电话), 建立连接后两端就可以进行通信了, Socket可以获取到文件(网卡)的输入输出流对象, 然后就可以流对象进行文件(网卡)读写了, 体现了TCP面向字节流, 全双工的特点.
构造方法 | 解释 |
---|---|
Socket(String host,int port) | 创建一个客户端Socket对象,并与对应IP主机,对应端口的进程进行连接 |
关键方法 | 解释 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回套接字的输入流 |
OutputStream getOutputStream() | 返回套接字的输出流 |
TCP的通信需要建立连接, 这里涉及长短连接的问题, 什么时候关闭连接就决定了是短连接还是长连接, 具体如下:
- 短连接: 每次接收到数据并返回响应后, 都关闭连接, 即是短连接; 也就是说, 短连接只能一次收发数据.
- 长连接: 不关闭连接, 一直保持连接状态, 双方不停的收发数据, 即是长连接; 也就是说, 长连接可以多次收发数据.
对比以上长短连接,两者区别如下:
- 建立连接, 关闭连接的耗时: 短连接每次请求, 响应都需要建立连接, 关闭连接; 而长连接只需要第一次建立连接, 之后的请求, 响应都可以直接传输; 相对来说建立连接, 关闭连接也是要耗时的, 长连接效率更高.
- 主动发送请求不同: 短连接一般是客户端主动向服务端发送请求, 而长连接可以是客户端主动发送请求, 也可以是服务端主动发.
- 两者的使用场景有不同: 短连接适用于客户端请求频率不高的场景, 如浏览网页等; 长连接适用于 客户端与服务端通信频繁的场景, 如聊天室, 实时游戏等.
2. TCP客户端回显服务器程序
2.1 TCP回显服务器
TCP服务器设计步骤:
- 创建
ServerSocket
实例对象, 需指定服务器端口号. - 启动服务器, 使用
accept
方法和客户端建立连接, 如果没有客户端来连接, 这里的accept方法会阻塞. - 接收客户端发来的请求(通过
Socket
获取到InputStream
流对象来读取请求). - 处理客户端请求, 计算响应.
- 将响应返回给客户端(通过
Socket
获取到OutputStream
流对象来发送响应).
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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 {//服务器专用的socket,用来和客户端建立连接private ServerSocket serverSocket = null;public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}//启动服务器public void start() throws IOException {System.out.println("启动服务器!");while (true) {//和客户端建立连接Socket clientSocket = serverSocket.accept();//和客户端进行交互,//这个写法只有一个线程,同一时间只能处理一个客户端proccessConnection(clientSocket);}}//处理一个客户端连接private void proccessConnection(Socket clientSocket) {System.out.printf("[%s:%d] 客户端端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());//基于clientSocket对象和客户端进行通信try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {//客户端可能有多个请求,所以使用循环来处理while (true) {// 1. 读取请求Scanner scanner = new Scanner(inputStream);if (!scanner.hasNext()) {//hasNext()方法判断输入(文件,字符串,键盘等输入流)是否还有下一个输入项,若有,返回true,反之false.//hasNext会等待客户端那边的输入,即会阻塞等待输入源的输入//当客户端那边关闭了连接,输入源也就结束了,没有了下一个数据,说明读完了,此时hasNext()就为false了System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());break;}//next 是一直读取到换行符/空格/其他空白符结束,但是最终返回结果里不包含空白符.String requst = scanner.next();// 2. 根据强求构造响应String response = process(requst);// 3. 返回响应的结果outputStream.write(response.getBytes(), 0, response.getBytes().length);byte[] blank= {'\n'};outputStream.write(blank);outputStream.flush();//或者使用println来写入,让结果中带有一个 \n 换行,方便对端来接收解析./*PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(response);//刷新缓冲区,保证当前写入的数据发送出去printWriter.flush();*/System.out.printf("[%s:%d] req: %s; resp: %s \n", clientSocket.getInetAddress().toString(),clientSocket.getPort(), requst, response);}} catch (IOException e) {e.printStackTrace();} finally {try {//释放资源,相当于挂断电话clientSocket.close();} catch (IOException e) {throw new RuntimeException(e);}}}public String process(String requst) {return requst;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();}
}
要注意理解这里的代码,
这里的 scanner.hasNext()
什么时候会使false
呢? 这是因为, 当客户端退出之后, 对应的流对象就读到了EOF
(文件结束标记), 那这里为啥会读到EOF, 这是因为客户端进程退出的时候, 就会触发socket.close()
, 也就触发FIN
(客户端端关闭连接的请求, 这个涉及到TCP协议连接管理的知识), 也就是操作系统内核收到客户端方发来的FIN数据报, 就会将输入源结束, 标记为EOF.
上面实现的TCP回显服务器的代码中有一个致命的缺陷就是, 这个代码同一时间只能连接一个客户端, 也就是只能处理一个客户端的请求, 下面先写客户端的代码, 然后再分析这里的问题.
2.2 TCP客户端
TCP客户端设计步骤:
- 创建
Socket
实例对象, 用于与服务器建立连接, 参数为服务器的IP地址和端口号, 在new Socket
实例对象的时候, 就会触发和TCP的连接过程. - 客户端启动, 用户输入请求, 构造构造请求并发送给服务器(使用
OutputStream/PrintWriter
), 要注意去刷新缓冲区保证数据成功写入网卡. - 读取服务器的响应并进行处理.
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;//TCP版本回显客户端
public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serverIp, int serverPort) throws IOException {// Socket构造方法,能够识别点分十进制格式的IP地址// new这个对象的同时,就会进行TCP连接操作socket = new Socket(serverIp, serverPort);}public void start() {System.out.println("客户端启动!");Scanner scanner = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {while (true) {// 1. 先从键盘上读取用户输入的内容System.out.printf("> ");String requst = scanner.next();if (requst.equals("exit")) {System.out.println("客户端退出");break;}// 2. 构造请求并发送给服务器outputStream.write(requst.getBytes());byte[] blank= {'\n'};outputStream.write(blank);outputStream.flush();/*PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(requst);printWriter.flush();*/// 3. 读取服务器的响应Scanner respScanner = new Scanner(inputStream);String response = respScanner.next();// 4. 把响应内容显示到屏幕上System.out.println(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();}
}
2.3 解决服务器无法同时出力多个客户端的问题
在2.1中的代码有一个很严重的缺陷, 服务器是要处理多个客户端的请求的, 但为什么说上面代码只能连接处理一个客户端呢?
这是因为在服务器代码中的processContain
方法中有一层循环, 这层循环需要与当前通信的客户端传输完成后才会退出, 也就是说只要现在连接的客户端没有退出, 这里的循环就不会退出, Socket
对象就无法释放去建立另一个连接, 简单说就是一个线程只能连接一个客户端, 那么这里可以用多线程/线程池来解决这里存在的问题, 让主线程主线程专门负责进行accept
和客户端建立连接, 每收到一个连接, 创建新的线程, 由新线程来负责处理这个新的客户端请求.
修改部分代码如下:
public void start() throws IOException {System.out.println("启动服务器!");// 此处使用 CachedThreadPool,线程数不太应该是固定的ExecutorService threadPool = Executors.newCachedThreadPool();while (true) {//和客户端建立连接Socket clientSocket = serverSocket.accept();//和客户端进行交互/*//这个写法只有一个线程,同一时间只能处理一个客户端proccessConnection(clientSocket);*//*//使用多线程Thread t = new Thread(() -> {proccessConnection(clientSocket);});t.start();*/// 使用线程池threadPool.submit(() ->{proccessConnection(clientSocket);});}}
使用线程池相较于多线程是更优化的方案, 使用多线程的话每连接到一个客户端就会就会创建一个线程, 如果同一时刻连接过多, 这里线程创建和销毁的开销就比较大了, 使用线程池就可以减少这里开销, 提高效率.
上述的方案是使用了线程池, 但如果服务器连接的客户端非常多, 而且都迟迟不断开,就会导致有很多的线程, 这对于机器来说就是一个很大的负担, 这里还是有更好的实现方案的.
这就是在解决单机支持更大量客户端的问题了, 也是经典的C10M
(单机处理1KW个客户端)问题.
这里并不是说单机真正能处理1KW
个客户端, 只是表达说客户端的量非常大(比C10K, 1W
还多), 处理这个问题主要是去实现让一个线程可以处理客户端连接, 这就是IO多路复用, IO多路转接技术, 会充分利用等待的时间去做其他的事情.
这个实现思路是基于一个事实, 虽然服务器可能连接的客户端有很多, 但是这里的连接并不是严格意义上的去同时处理请求, 会有等待请求的时间, 也就是说多个客户端发来的请求是有先后的, 我们就可以利用等待请求的时间去处理另一个连接, 实现起来其实就是给线程安排一个集合, 这个集合放了一堆连接, 我们线程负责监听集合, 哪个连接有数据来了, 就处理哪个连接, 而其他的连接还可能在等待请求.
在操作系统里, 提供了一些API, 比如select
, poll
, epoll
; epoll是三代目, 解决了select, poll中的一些问题, 是目前最好的一个, 在Java中, 也提供了一组NIO
这样的类, 封装了上述技术.
2.4 TCP实现查词典的服务器
和UDP实现过程一个, 继承+哈希表词库, 再重写一下处理请求部分的代码即可.
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class TcpDictServer extends TcpEchoServer{Map<String, String> dict = new HashMap<>();public TcpDictServer(int port) throws IOException {super(port);dict.put("cat", "小猫");dict.put("dog", "小狗");dict.put("bird", "小鸟");dict.put("apple", "苹果");dict.put("banana", "香蕉");dict.put("strawberry", "草莓");dict.put("watermelon", "西瓜");//...}@Overridepublic String process(String requst) {//查字典return dict.getOrDefault(requst, "当前单词没有查到结果");}public static void main(String[] args) throws IOException {TcpDictServer server = new TcpDictServer(9090);server.start();}
}
相关文章:

Java网络编程之UDP和TCP套接字
文章目录一. 网络编程概述二. UDP网络编程1. UDP套接字2. UDP客户端回显服务器程序2.1 UDP回显服务器2.2 UDP客户端2.3 UDP实现查词典的服务器三. TCP网络编程1. TCP套接字2. TCP客户端回显服务器程序2.1 TCP回显服务器2.2 TCP客户端2.3 解决服务器无法同时出力多个客户端的问题…...

Linux进程信号产生以及捕捉
一.什么是信号 生活中,有哪些信号相关的场景呢,比如:红绿灯,闹钟,转向灯等等 1.这里我们要知道,你为什么认识这些信号呢,记住了对应场景下的信号+后续是有”动作“要你执行的 2.我们…...
11. GLSL(OpenGL Shader Language)常用知识点汇总
1. 说明: 在使用OPenGL进行三维模型渲染时,需要使用到两个着色器对模型进行位置设置和颜色设置,分别为顶点着色器和片段着色器,这两个着色器是使用 GLSL 语法进行编写的。这篇文章总结了一些GLSL中的一些基本语法知识。 2. 基本…...

转发一张网络工程师考试的试卷2021.5.15
网络工程师考试 单选题 (30题,每题1分,共30分) 1. 你在一个网络中实现DHCP服务,配置一些计算机成为DHCP客户端,由于工作需要,一台系统为Windows 10 的客户端要把从DHCP服务器获得的地址释放&a…...

AMD发布23.2.1 新驱动 支持开年新作《魔咒之地》
如果说2023年有什么新作,《魔咒之地(Forspoken)》当属开年大作之一,1月25日才在steam平台发售。虽然开售后的表现似乎不如想象中优秀,加之价格相对昂贵,令不少玩家望而却步,但如果只是想尝鲜&am…...
开放平台如何做接口的签名和加解密?
目录安全性功能介绍实现流程开放平台依赖代码AES加解密工具类PlatformConfigRequestUtilsPlatformServiceCommonCodeZuulFilterHelperServerResponsePlatformContactRsaSignatureRsaUtilsStreamUtil开放平台-解密过滤器开放平台-加密过滤器调用方代码公共代码Get-DemoPost-Demo…...

Mr. Cappuccino的第40杯咖啡——Kubernetes之Pod生命周期
Kubernetes之Pod生命周期Pod生命周期官方文档Pod的状态初始化容器案例钩子函数Exec命令TCPSocketHTTPGet案例容器探测Exec命令TCPSocketHTTPGet探测时间重启策略Pod生命周期官方文档 Pod生命周期官方文档 Pod的状态 pending:挂起,apiserver创建了pod资…...

记一次OOM
1,问题描述: 新上了一版代码之后,上游服务请求我们服务失败,报错:“服务不可用”,发现注册中心上服务掉线,查询日志:发现oom:Java heap space,GC overhead limit exceeded。 容易…...

idea插件生成dao类service类controller类以及mapper.xml
idea插件生成dao类service类controller类以及mapper.xml 安装插件Easycode和MybatisX,不用自己写代码 1.Files——》Settings——》Plugins,分别搜索Easycode和MybatisX,点击下载。 2.新建一个springboot模板,选择的依赖如下 3.…...
DML 数据操作语言
概述 DML英文全称是Data Manipulation Language(数据操作语言),用来对数据库中表的数据记录进行增、删、改操作。 添加数据(INSERT)修改数据(UPDATE)删除数据(DELETE) 添加数据 1、给指定字段…...

PySpark实战一之入门
1、PySpark的编程模型 分三个模块: 数据输入:通过SparkContext对象,完成数据输入 数据处理计算:输入数据后得到RDD对象,对RDD对象的成员方法进行迭代计算 数据输出:最后通过RDD对象的成员方法࿰…...

【DockerCE】Docker-CE 23.0.1正式版发布
很意外啊!Docker社区版竟然直接从20.xx.xx版本,升级到23.xx.xx版本了。官网地址(For RHEL/CentOS 7.9):https://download.docker.com/linux/centos/7/x86_64/stable/Packages/23.0.1版本官方安装包如下:# l…...

vscode开发的Vue家用电器维修服务系统nodejs+mysql
主要功能包括管理员:首页、个人中心、用户管理、维修员管理、维修信息管理、维修申请管理、维修处理管理、家电类别管理、配件信息管理、配件领用管理、维修结果管理、家电维修知识管理、公告信息管理、留言板管理,用户:首页、个人中心、维修…...

PyQt5数据库开发1 4.2 配置SQL Server 2008 数据源(ODBC编程)
文章目录 配置SQL Server 2008 数据源(ODBC编程) 1. 了解要配置的数据源服务器名称,以及数据库和对应表 2. 打开控制面板,点击管理工具 3. 双击数据源 4. 选择“用户DSN”选项卡,点击“添加” 5. 选择SQL Serv…...

【JavaEE】多线程代码实例:单例模式与阻塞队列BlockingQueue
目录 单例模式: 什么是单例模式? 单例模式的实现方式: 饿汉模式: 懒汉模式: 基于并发编程对单例模式线程安全问题的讨论: 阻塞队列: 标准库中的阻塞队列: 自实现阻塞…...

算法思想 - 搜索算法
本文主要介绍算法中搜索算法的思想,主要包含BFS,DFS。搜索相关题目深度优先搜索和广度优先搜索广泛运用于树和图中,但是它们的应用远远不止如此。BFS广度优先搜索的搜索过程有点像一层一层地进行遍历,每层遍历都以上一层遍历的结果…...

C#底层库--日期扩展类(上周、本周、明年、前年等)
系列文章 C#底层库–记录日志帮助类 本文链接:https://blog.csdn.net/youcheng_ge/article/details/124187709 C#底层库–数据库访问帮助类(MySQL版) 本文链接:https://blog.csdn.net/youcheng_ge/article/details/126886379 …...

如何在 Webpack 中开启图片压缩
工具对比 npmtrends.com/image-minim… 这四个压缩工具,从下载量来看,image-webpack-loader 较多,image-minimizer-webpack-plugin、imagemin-webpack-plugin 次之,imagemin-webpack 已经不再维护,因此不考虑此工具。 …...
Web-Filter
## 今日内容 1. Filter:过滤器 2. Listener:监听器 # Filter:过滤器 1. 概念: * 生活中的过滤器:净水器,空气净化器,土匪、 * web中的过滤器:当访问服务器的资源时…...
测试写文章自动保存
近日恰逢双十一,瞅了瞅自己干瘪的钱包,没忍心入手期待已久的 macPro,只好在虚拟机里玩一下 mac好了,等以后钱包傲气的时候再来个真实的。 安装环境: windows10 VMWare14.2 2018-7-28 小嘚瑟补充:唧唧歪歪大半年,一夜回到解放前,终于剁手整了个真机,可以折腾一下了 ——…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...

Sklearn 机器学习 缺失值处理 获取填充失值的统计值
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...