JavaWebUDP/TCP 简单实现汉译英服务器与客户端

Posted Perceus

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaWebUDP/TCP 简单实现汉译英服务器与客户端相关的知识,希望对你有一定的参考价值。

@TOC


TCP 与 UDP 的 区别


有连接 与 无连接

可以怎么去理解?

  1. 有链接:像打电话

  1. 无连接:发微信

注:

  • TCP,就是要求双发先建立连接,连接好了,才能进行传数据。
  • UDP,直接传输数据,不需要双方建立连接

面向字节流 与 数据报

  1. 面向字节流数据是以字节为单位,进行传输
  1. 面向数据报以数据报为单位,进行传输

在代码中,这两者的区别是非常明显的!


通信五元组

  1. 源IP
  2. 源端口
  3. 目的IP
  4. 目的端口
  5. 协议类型

UDP Socket编程

TCP 和 UDP 协议中,只有 UDP 是面向数据报的。
那么 DatagramScoket 和 DatagramPacket 这两类,从名字就能看出来(Datagram-数据报),是关于UDP协议的类

UDP 主要接口

使用UDP实现汉译英 C/S

服务端

// 实现翻译回显C/S
public class MyUDPServer 

    // 对于一个服务器程序来说, 核心流程也是要分成两步.
    // 1. 进行初始化操作 (实例化 Socket 对象)
    // 2. 进入主循环, 接受并处理请求. (主循环就是一个 "死循环")
    //   a) 读取数据并解析
    //   b) 根据请求计算响应
    //   c) 把响应结果写回到客户端.

    private DatagramSocket socket =null;

    //map去存储我们的汉译英数据
    private Map<String, String> map = new HashMap<>();

    public  MyUDPServer(int port) throws SocketException 
        //添加数据
        initDates();
        //服务器new socket对象的时候需要和一个ip地址和端口号绑定起来
        //如果没有写ip 则默认时0.0.0.0 (一个特殊的ip会关联到这个主机的国有网卡的ip)
        //socket对象本省就是一个文件
        socket = new DatagramSocket(port);
    

    // map初始化
    private void initDates() 
        map.put("猫", "cat");
        map.put("猪", "pig");
        map.put("狗", "dog");
        map.put("人", "people");
        map.put("笔", "pen");
        map.put("坐", "sit");
        map.put("手", "hand");
        map.put("腿", "leg");
    

    //启动服务器
    public void start() throws IOException 

        System.out.println("服务器启动");
        // UDP 不用建立连接, 接受盛怒据即可
        while(true)
            //1. 接受客户端的请求
            //2. 根据请求计算相应
            //3. 把响应写回客户端
            //这是一个接受数据的缓冲区 地址是接受数据的时候有内存填充
            DatagramPacket datagramPacket= new DatagramPacket(new byte[4096],4096);
            //程序启动会很快到达receive操作 如果客户端没有发送任何数据 此时receive操作会阻塞直到有客户端发送数据过来
            //1.当整的有哭换端发送过来数据时 receive就会将数据保证到DategramPAcket对象的缓冲区里
            socket.receive(datagramPacket);
            //原本请求的数据时byte[]需要将其转换成String 并且如果发来的数据小于我们缓冲区的大小就会默认添加空格 我们得去掉无用空格
            String request = new String(datagramPacket.getData(), 0, datagramPacket.getLength(),"UTF-8").trim();
            //2.请求计算相应
            String respond = process(request);

            //把响应写回给客户端, 响应数据就是 response, 需要包装成一个 DatagramPacket 对象
            //此时这个用于send 不仅需要指定缓冲区还不要忘记在Packet对象的最后加上请求数据包里的Socket地址
            //填写ip和port还可以自己手动设置将ip和port分开写(如下面案例) 还可以直接定义InetAddress对象(里面包含ip和port)
            DatagramPacket respondPacket = new DatagramPacket(respond.getBytes(),
                    respond.getBytes().length, datagramPacket.getSocketAddress());

            // 3。发送数据
            socket.send(respondPacket);

            //打印请求访问日志
            System.out.println(respondPacket.getAddress().toString() + "  " + respondPacket.getPort() + "  request: "
                    + request + "  respond: " + respond);

        

    

    private String process(String request) 
        return map.getOrDefault(request, "未学习");
    

    //一个主函数去设置该服务器的端口 并让其开始执行
    public static void main(String[] args) 
        try 
            MyUDPServer myUDPServer = new MyUDPServer(9090);
            try 
                myUDPServer.start();
             catch (IOException e) 
                e.printStackTrace();
            
         catch (SocketException e) 
            e.printStackTrace();
        
    


客户端

//客户端程序
public class MyUDPClient 
    //核心操作有俩步
    //启动客户端的时候需要指定连接那台服务器
    //执行任务主要流程分4步
    // 1. 从用户这里读取输入的数据.
    // 2. 构造请求发送给服务器
    // 3. 从服务器读取响应
    // 4. 把响应写回给客户端.

    //需要客户端知道要发往哪台服务器的ip 和端口 还需要一个udp的连接对象
    private String severIP = "127.0.0.1";
    private int severPort = 9090;
    private DatagramSocket socket = null;

    //需要在启动客户端的时候来指定需要连接哪个服务器
    public MyUDPClient(String severIP, int severPort) throws SocketException 
        this.severIP = severIP;
        this.severPort = severPort;
        //客户端在创建socket的时候不需要绑定端口号 但是服务器必须绑定端口号
        //因为服务器绑定了端口号 客户端才能找到去访问它
        //客户端不绑定是为了可以在一台主机上启动多个客户端
        this.socket = new DatagramSocket();
    

    public void start() throws IOException 
        Scanner scanner = new Scanner(System.in);
        while (true) 
            //读取用户输入的消息
            System.out.print("输入字符串->");
            String request = scanner.nextLine();
            if ("exit".equals(request)) 
                break;
            

            //发送请求
            //注意ip和port要分开写并且前后位置要注意
            DatagramPacket requstPacket = new DatagramPacket(request.getBytes(),
                    request.getBytes().length, InetAddress.getByName(this.severIP), this.severPort);
            socket.send(requstPacket);

            //接收服务器的响应
            DatagramPacket respondPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(respondPacket);
            String respond = new String(respondPacket.getData(), 0, respondPacket.getLength()).trim();
            //显示响应
            System.out.println(respond);
        
    

    public static void main(String[] args) 
        try 
            //此时我们用于自己主机实验 127.0.0.1是一个特殊的ip(环回ip) 自己访问自己
            //如果服务器和客户端在同一台主机上旧使用环回ip 如果不在同一台主机上就必须填写服务器的ip
            //端口号必须与服务器的端口号一致
            MyUDPClient client = new MyUDPClient("127.0.0.1", 9090);
            try 
                client.start();
             catch (IOException e) 
                e.printStackTrace();
            
         catch (SocketException e) 
            e.printStackTrace();
        
    

效果:


TCP socket编程

ServerSocket 与 Socket

ServerSocket类

  • 重要方法 accept()
  1. 三次握手完成后, 服务器调用accept()接受连接;
  2. 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  3. Socket 是其返回值,代表网络的套接字

Socket类

简单的TCP服务器与客户端

服务端


public class TcpEchoServer 

    public ServerSocket serverSocket =null;

    public TcpEchoServer (int port) throws IOException 
        serverSocket =new ServerSocket(port);
    

    public void start() throws IOException 

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

        while (true) 
            // 由于 TCP 是有连接的,不能一上来就读数据, 要先建立连接 (接电话)
            // accept 就是在”接电话“,接电话的前提是,有人给你打~~, 如果没有客户端尝试建立连接, accept 就会阻塞
            // accept 返回一个 socket 对象, 称为 clientSocket , 后续和客户端之间的沟通, 都是通过 clientSocket 完成的
            Socket cilentSocket = serverSocket.accept();

            processConnection(cilentSocket);

        

    

    private void processConnection(Socket cilentSocket) throws IOException 
        //打印客户端信息
        System.out.printf("[%s:%d] 客户端建立连接!!\\n",cilentSocket.getInetAddress().toString(),cilentSocket.getPort());

        //处理请求和响应 全双工
        try( InputStream inputStream = cilentSocket.getInputStream()) 
            try(OutputStream outputStream = cilentSocket.getOutputStream())

                //循环处理每个请求,返回响应
                Scanner scanner =new Scanner(inputStream);
                while(true) 
                    //读取请求
                    if(!scanner.hasNext())
                        System.out.printf("[%s:%d] 客户端断开链接!!",cilentSocket.getInetAddress().toString(),cilentSocket.getPort());
                        break;
                    
                    // 此处用Scanner 更方便
                    String request = scanner.next();
                    //根据请求计算响应
                    String response=process(request);
                    //为了方便使用 用 PrintWrite 把 OutputStream 包裹
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(response);
                    //刷新缓冲区,没有可能不能第一时间看见响应结果
                    printWriter.flush();

                    System.out.printf("[%s,%d] req:%s , resp:%s\\n",cilentSocket.getInetAddress().toString(),
                            cilentSocket.getPort(),request,response);
                
            
         catch (IOException e) 
            e.printStackTrace();
        

     

    private String process(String request) 
        return request;
    

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

        TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);

        tcpEchoServer.start();

    


客户端


public class TcpEchoClient 
    // 用普通的 Socket 即可,不用 ServerSocket 了
    private Socket socket = null;

    //此处也不用手动给客户端指定端口号,由系统自动分配(隐式)
    public TcpEchoClient(String serverIP,int serverPort) throws IOException 
        // 其实这里是可以给定端口号的,但是这里给了之后,含义是不同的。
        // 这里传入的 IP 与 端口号 的 含义: 表示的不是自己绑定,而是表示 和 这个IP 端口 建立连接
        socket = new Socket(serverIP,serverPort);// 这里表示 与 IP 为serverIP的主机上的 端口为9090的程序,建立连接。
    
    public void start()
        System.out.println("和进服务器连接成功!");
        Scanner sc = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream())
            try (OutputStream outputStream = socket.getOutputStream())
                while(true)
                    //1、从控制台读取字符串
                    System.out.println("->");
                    String request = sc.next();
                    //2、根据读取的自妇产,构造请求,把请求发送给服务器
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(request);// 看似是一个输出语句,其实已经将数据写到服务器里面去了
                    printWriter.flush();// 记得 立即刷新缓冲区,确保 服务器 第一时间 感知到 请求。
                    //3、从服务器读取响应,并解析
                    Scanner scanner = new Scanner(inputStream);
                    String response = scanner.next();
                    //4、把结果显示到控制台上。
                    System.out.printf("request:%s,response:%s\\n ",request,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();
    

效果


拓展:

多个客户端 与 服务器建立连接

虽然此时的 TCP代码已将跑起来了还是此处还存在一个很严重的问题!!!!


程池版本——TCP服务器

  • 观察运行上述代码我们发现再启动一个客户端, 尝试连接服务器, 发现第二个客户端, 不能正确的和服务器进行通信.分析原因, 是因为我们accecpt了一个请求之后, 就在一直while循环尝试read, 没有继续调用到accecpt, 导致不能接受新的请求.我们当前的这个TCP, 只能处理一个连接, 这是不科学的
  • 所以我们通过每个请求, 创建子进程的方式来支持多连接
  • 但是还有问题当我们有很多连接的时候 线程就会疯狂的创建和销毁 所以结合前面所学我们可以使用线程池进行优化
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TCPThreadPoolServer 
    //去内核找连接
    //处理这个连接对象
    //获得socket的输入流
    //处理输入流请求
    //将响应写回到socket输出流
    private ServerSocket serverSocket = null;

    //构造函数需要给服务器绑定一个端口

    public TCPThreadPoolServer(int port) throws IOException 
        this.serverSocket = new ServerSocket(port);
    

    public void start() throws IOException 
        System.out.println("服务器启动");
        //用一个线程池去解决多个多连接问题
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true) 
            Socket socket = serverSocket.accept();
            executorService.execute(new Runnable() 
                @Override
                public void run() 
                    processSocket(socket);
                

                private void processSocket(Socket socket) 
                    System.out.printf("[%s : %d] 已上线\\n", socket.getInetAddress().toString(), socket.getPort());

                    try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                         BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())))

                        while (true) 
                            String get = bufferedReader.readLine();
                            String put = process(get);
                            bufferedWriter.write(put + "\\n");
                            //因为使用的是带缓冲区的buffer 所以一开始write是写入了缓冲区里 调用flush才可以将数据真正写到socket文件中
                            bufferedWriter.flush();

                            System.out.printf("[%s:%d] req: %s; resp: %s\\n", socket.getInetAddress().toString(),
                                    socket.getPort(), get, put);
                        

                     catch (Exception e) 
                        System.out.printf("[%s : %d] 已下线\\n", socket.getInetAddress().toString(), socket.getPort());
                    
                

                private String process(String get) 
                    return get.toUpperCase();
                
            );

        
    

    public static void main(String[] args) 
        try 
            TCPThreadPoolServer tcpThreadPoolServer = new TCPThreadPoolServer(9090);
            tcpThreadPoolServer.start();
         catch (IOException e) 
            e.printStackTrace();
        
    

以上是关于JavaWebUDP/TCP 简单实现汉译英服务器与客户端的主要内容,如果未能解决你的问题,请参考以下文章

在线汉译英翻译

请英语高手帮忙 汉译英句子

汉译英--论文的英文摘要请帮忙

(急)高分请人帮汉译英(下面是原文)好的一定追加50分

如何调用有道翻译api进行汉译英

汉译英基础