实现将英文转化称为中文
想在服务器和客户端中实现这一功能,在响应阶段做出让传进来的英文返回中文。需要定义一个HashMap类型来存储其可能输入进来的英文和将要返回的中文。
UDP中建立
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 {//在构造方法中填入可以保证当一运行这个服务器里面将会自动存储进这些翻译所需要的dict
super(port);
dict.put("dog", "小狗");
dict.put("cat", "小猫");
dict.put("pig", "小猪");
}
@Override
public String process(String request) {//重写其继承的回显服务器中的process
return dict.getOrDefault(request,"该词在字典中不存在");
}
public static void main(String[] args) throws IOException {
UdpDictServer udpDictServer = new UdpDictServer(9090);
udpDictServer.start();
}
}
TCP中建立
TCP中的Socket API 和UDP中的Socket API有比较大的差异,但用有一些相同。
TCP中ServerSocket(给服务器使用的类,通过这个类来绑定端口号)
Socket(既会给服务器使用,又会给客户端使用)
这两个类都是用来表示socket文件(抽象了网卡这样的硬件设备)
注因为TCP是字节流传输的基本单位是byte,不像TCP那样所以不用建立一个专门用来模拟数据报。
TCP中TCP是有连接的.UDP无连接所以TCP不用像UDP那样保存对应端口号的信息。
.
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 socket =serverSocket.accept();//通过这个accept方法来接受客户端的信息,这个方法是自动完成的由操作系统内核(???)
}
}
TCP的基本思路与UDP一样在创建之初都需要创建一个对象来与客户端进行连接并进行保存,只不过TCP中不是直接用这个保存的对象来进行编写,而是又通过一个新的类来接受其中的信息,后续通过这个新的类创建的对象来进行交互。而UDP中是一个类来进行创建新的对象来进行接受信息和返回信息。
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
Socket socket =serverSocket.accept();//通过这个来接受客户端的信息
processConnection(socket);//与UDP不同之处在于其是建立一个连接过程方法在里面进行处理各种信息
}
}
public void processConnection(Socket clientSocket){
System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());//打印日志证明连接上了
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {//在try-with-resources语句的括号中声明的资源对象必须是实现了‘AutoCloseable`或`c1oseable`接口的对象,这些接口都定义了`c1ose()`方法。
//Java编译器会在try-with-resources语句中自动生成对资源对象`close()'方法的调用,这些调用会被插入到finally块中,确保在try块执行结束后资源会被正确关闭。在try块执行过程中,如果发生异常,Java虚拟机会首先执行finally块中的代码,然后再向上抛出异常。这样就保证了资源的关闭操作一定会在异常被抛出之前执行,从而确保资源被正确关闭。
while(true){
//下面代码规定了从inputStream这个字节流中读入数据千万不要写成System.in这是从键盘中读
//Scanner scanner = new Scanner(System.in);//创建这个就是为了来接收从客户端传过来的字节流
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()){//使用hashNext来判断是否还有信息传过来如果没有那就是客户端关闭了
System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
break;;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
在TCP中在客户端运行之后又不同的点在于因为TCP是直接与客户端通过字节流的方式进行连接的。所以需要对这字节流来进行处理,所以又建立了一个方法来进行控制,在使用字节流时又因为害怕流对象忘记关闭会导致资源泄露(???),使用Try来进行自动关闭,在使用try进行自动关闭时,try这一块的代码在运行完毕时,会自动生成 close方法,如然后这个方法会被插入到finall块中确保在try执行完之后会被关闭资源,并且如果发生异常的话,也会先执行finally块,然后在抛出异常的。
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
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){
Socket socket =serverSocket.accept();//通过这个来接受客户端的信息
processConnection(socket);//与UDP不同之处在于其是建立一个连接过程方法在里面进行处理各种信息
}
}
public void processConnection(Socket clientSocket){
System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());//打印日志证明连接上了
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {//在try-with-resources语句的括号中声明的资源对象必须是实现了‘AutoCloseable`或`c1oseable`接口的对象,这些接口都定义了`c1ose()`方法。
//Java编译器会在try-with-resources语句中自动生成对资源对象`close()'方法的调用,这些调用会被插入到finally块中,确保在try块执行结束后资源会被正确关闭。在try块执行过程中,如果发生异常,Java虚拟机会首先执行finally块中的代码,然后再向上抛出异常。这样就保证了资源的关闭操作一定会在异常被抛出之前执行,从而确保资源被正确关闭。
while(true){
Scanner scanner = new Scanner(inputStream);//创建这个就是为了来接收从客户端传过来的字节流
if(!scanner.hasNext()){//使用hashNext来判断是否还有信息传过来如果没有那就是客户端关闭了
System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
break;
}
String request = scanner.next();//将在字节流中读取到的信息传入到request中,并且使用next读的目的是因为next读时会因为读到空白符就会返回。空白符是一种特殊的字符:换行、回车符、空格指标符等在这里就规定当读到客户端的换行时为结束标志
String response =process(request);//处理客户端传进来的数据返回一个响应
PrintWriter printWriter = new PrintWriter(outputStream);//将会以文本的形式写入到outputStream
printWriter.println(response);//使用PrintWriter将响应产生的字符串以文本形式写入outputStream中将其写进字节流中,并且这里使用printfln也是使客户端那里有回车,使客户端和服务器端有共同的逻辑来读数据
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);
}
}
public String process(String request) {
// 此处也是写的回显服务器. 响应和请求是一样的.
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
}
}
PrintWriter printWriter = new PrintWriter(outputStream)其可以将下文中使用的printWriter.println(response)中的response读入到outputStream以文本形式进行传输。
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
Socket socket =serverSocket.accept();//通过这个来接受客户端的信息,sokcet这个对象循环每次有一个新的客户端来建立连接,都会建立相同的socket对像
processConnection(socket);//与UDP不同之处在于其是建立一个连接过程方法在里面进行处理各种信息
}
}
在客户端不断的进行请求时会不断的创建新的socket对象,但是这个socket对象并没有被关闭,就会导致socket对象占据着文件描述符的位置。并且随着访问量的增多可能导致这个文件描述符慢导致导致文件泄露。
所以在最后我们自己进行关闭在原先加入下面代码进行手动关闭。
finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
TCP的客户端
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 sereveIp,int serevePort) throws IOException {
socket =new Socket(sereveIp,serevePort);//连接时只需要创建socket对象并向这个对象里面传入服务器的值即可连接细节是内核来完成
}
public void start(){
Scanner scanner = new Scanner(System.in);//为了下面中读想要输入的内容
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream() ) {
PrintWriter writer = new PrintWriter(outputStream);//为了下面将需求以文本类型输入outputStream
Scanner scannerNetwork = new Scanner(inputStream);//为了下面读取服务器中的回复的响应
while (true){
System.out.println("->");
String request = scanner.next();
writer.println(request);//将用户的内容输入到outputStream字节流当中
writer.flush();//将outputStream字节流中的东西冲刷进去服务器中
String response = scannerNetwork.next();
System.out.println(response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);
tcpEchoClient.start();
}
}
tcp 的客户端行为和 udp 的客户端差不多.
从控制台读取用户输入的内容
把字符串作为请求, 发送给服务器
从服务器读取响应.
把响应显示到界面上.
但是现在还有代码问题不能建立一个多个客户端同时与服务器进行访问
如图所示如果有两个个进程的话第一个进行执行的时候代码会一直在processConnection方法中运行如果此时线程不输入请求,那么会使自己卡在scanner.hasNext(),并且processConnection这个方法不出去无法进行start方法中的true循环使,新的线程无法通过accept来建立于客户端的连接
导致其第二个线程无法运行。
解决这个问题可以加线程
线程是并发执行的互不影响
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
//并且先输出2证明一直卡在accept的客户端信息,并且每次与一个客户端进行连接accept执行一次,其下面的进程每次都会在创建一个分支来运行processConnection等待这个分支的这个processConnection方法执行完了会使这个分支自己完
System.out.println("1");//最开始服务器开始运行时启动执行1每当一个客户端运行时输出1那就说明这并不是在无线的输出1
Socket socket =serverSocket.accept();//通过这个来接受客户端的信息,sokcet这个对象循环每次有一个新的客户端来建立连接,都会建立相同的socket对
System.out.println("2");
Thread t = new Thread(()->{//不用管其方法是否返回,每当一个请求过来accept都接受信息
processConnection(socket);//与UDP不同之处在于其是建立一个连接过程方法在里面进行处理各种信息
});
t.start();
}
}
通过建立线程使这个循环可以不用管 processConnection返回的值了其每次建立一个对象时,循环自己都会再次到accepy等待下一个客户端对象。
还有一个方法使用线程池
ublic void start() throws IOException {
System.out.println("服务器启动");
ExecutorService service = Executors.newCachedThreadPool();
while (true){
//并且先输出2证明一直卡在accept的客户端信息,并且每次与一个客户端进行连接accept执行一次,其下面的进程每次都会在创建一个分支来运行processConnection等待这个分支的这个processConnection方法执行完了会使这个分支自己完
System.out.println("1");//最开始服务器开始运行时启动执行1每当一个客户端运行时输出1那就说明这并不是在无线的输出1
Socket socket =serverSocket.accept();//通过这个来接受客户端的信息,sokcet这个对象循环每次有一个新的客户端来建立连接,都会建立相同的socket对
System.out.println("2");
// Thread t = new Thread(()->{//不用管其方法是否返回,每当一个请求过来accept都接受信息
// processConnection(socket);//与UDP不同之处在于其是建立一个连接过程方法在里面进行处理各种信息
// });
// t.start();
service.submit(new Runnable() {
@Override
public void run() {
processConnection(socket);
}
});
}
}
线程池的方式是可以降低"频繁创建销毁线程的开销"但是,如果同一时刻有大量的客户端来连接,就会使系统上出现大量的线程如果一个机器有个几百个线程,还能勉强跑一跑.如果是上万个线程甚至更多,这个机器是非挂不可