【JavaEE】基于TCP的客户端服务器程序

简单的英译汉翻译程序

接着上篇博客,我们继续来学习 Socket 套接字的相关知识点,首先我们写一个 英译汉翻译程序

即客户端不变,把服务器代码进行调整,关键的逻辑就是把响应写回给客户端。

package network;

import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;


//使用继承,是为了复用之前的代码 省下我们重复写代码的时间
public class UdpDictServer extends UdpEchoServer{
    Map<String, String> dict = new HashMap<>();
    public UdpDictServer(int port) throws SocketException {
        super(port);

        dict.put("pig","猪");
        dict.put("cat","猫");
        dict.put("dog","狗");
        dict.put("owl","猫头鹰");
        dict.put("giant panda","大熊猫");
    }

    @Override
    public String process(String request){
        return dict.getOrDefault(request,"该单词没有查到");
    }

    public static void main(String[] args) throws IOException {
        UdpDictServer udpDictServer = new UdpDictServer(9090);
        udpDictServer.start();
    }
}

那么我们首先启动服务器。

启动服务器

然后启动我们的客户端,输入相应的英文单词,观察服务器的响应。

查看 hero

客户端这边没有问题,我们再点击到服务器这里观察一下。

服务器

到这里 UDP 版本的 客户端服务器代码 的全部内容了就已经讲完了,今天我们来学一下 TCP版本的客户端服务器代码。

TCP API

TcpEchoServer

SeverSocket(给 TCP服务器 用的)
Socket(既要给服务器用,又要给客户端用)。

我们先写 TCP版本 的 TcpEchoServer 的代码:

public class TcpEchoServer {
    //serverSocket 只有一个,clientSocket 会给每个客户端都分配一个
    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();
            processConnection(clientSocket);
        }
    }

前面基本都和 UDP 的差不多,区别就是这里的 start 方法,由于 TCP 是有连接的,不是一上来就能读数据,需要先建立连接;accept 返回了一个socket对象,如果此时没有客户,那么这里 accept 就会阻塞。

接下来我们来写processConnection()方法的代码。

这里也分为三步:

step1: 循环处理每个请求,分别返回响应。

step2: 根据请求,计算响应。

step3:把这个响应返回给客户端。

private 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 scanner = new Scanner(inputStream);//相当于字符流
            PrintWriter printWriter = new PrintWriter(outputStream);//OutputStream字节流 PrintWriter字符流
            while(true){
                //1.读取请求
                if(!scanner.hasNext()){
                    //读取的流到了结尾了(对端关闭了)
                    System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress().toString(),
                            clientSocket.getPort());
                    break;
                }
                //直接使用 scanner 读取一段字符串
                String request = scanner.next();
                //2.根据请求计算响应
                String response = process(request);
                //3.把响应写回给客户端 响应里也是要带上换行的
                printWriter.println(response);
                printWriter.flush();
                System.out.printf("[%s:%d] req: %s;resp: %s\n",clientSocket.getInetAddress(),
                        clientSocket.getPort(),request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            clientSocket.close();
        }

    }

    private String process(String request) {
        return request;
    }

观察上述代码,我们可以发现 clientSocket 执行了关闭操作,那么关闭是在干什么?释放资源。

释放资源的前提是这个资源不再使用了。对于UDP的程序 ServerSocket 来说,这些 socket 都是贯穿始终的,但是对于TCP来说,clientSocket 有很多,断开就不再需要了,每次都得保证处理完的连接都进行释放。

TcpEchoServer完整代码:

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;

public class TcpEchoServer {
    //serverSocket 只有一个,clientSocket 会给每个客户端都分配一个
    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();
            //clientSocket是服务器的Socket 通过这个Socket和客户端进行通信
            processConnection(clientSocket);
        }
    }

    //通过这个方法来处理一个连接
    //读取请求
    //根据请求计算响应
    //把响应返回给客户端
    private 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 scanner = new Scanner(inputStream);//相当于字符流
            PrintWriter printWriter = new PrintWriter(outputStream);//OutputStream字节流 PrintWriter字符流
            while(true){
                //1.读取请求
                if(!scanner.hasNext()){
                    //读取的流到了结尾了(对端关闭了)
                    System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress().toString(),
                            clientSocket.getPort());
                    break;
                }
                //直接使用 scanner 读取一段字符串
                String request = scanner.next();
                //2.根据请求计算响应
                String response = process(request);
                //3.把响应写回给客户端 响应里也是要带上换行的
                printWriter.println(response);
                printWriter.flush();
                System.out.printf("[%s:%d] req: %s;resp: %s\n",clientSocket.getInetAddress(),
                        clientSocket.getPort(),request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            clientSocket.close();
        }

    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
        tcpEchoServer.start();
    }
}

TcpEchoClinet

OK,接下来我们写 TcpEchoClinet 的代码。

public class TcpEchoClient {
    private Socket socket = null;
    public TcpEchoClient(String serverIp,int port) throws IOException {
        //这个操作相当于让客户端和服务器建立 tcp 连接
        //这里的连接连上了,服务器的 accept 就会返回
        socket = new Socket(serverIp,port);
    }

注意,这里传入的IP和端口号的含义表示和这个ip、端口号建立连接。

start()方法:

public void start(){
        Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){
            PrintWriter printWriter = new PrintWriter(outputStream);
            Scanner scannerFromSocket = new Scanner(inputStream);
            while(true){
                //1.从键盘上读取用户输入的内容
                System.out.println("和服务器连接成功");
                System.out.println("->");
                String request = scanner.next();
                //2.把读取的内容构造成请求,发送给服务器
                //这里发送 是带有换行的
                printWriter.println(request);
                //把数据写入内存的缓冲区 等缓冲区满了 才会真正写网卡
                //可以手动刷新缓冲区,让数据立即被写入网卡
                printWriter.flush();
                //3.从服务器读取响应内容
                String response = scannerFromSocket.next();
                //4.把响应结果显示到控制台上
                System.out.printf("req: %s;resp: %s\n",request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

main函数

public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
        client.start();

    }

接下来先启动服务器
启动服务器

服务器启动成功,启动客户端,可以发现和服务器连接成功!
启动客户端

然后我们点到服务器这边观察一下,可以发现服务器已经和客户端建立连接,系统已经分配了端口号。

已经建立连接

我们进行输入数据。

看服务器这边的响应。

那么我们结束客户端,看服务器这边的响应。

解决只能处理一个客户端的问题

虽然上述代码已经运行起来了,但是存在一个问题,就是当前的服务器同一时间只能处理一个连接,我们看如何验证?

如何在 idea 中启动多个客户端,此时我们需要配置一下,默认一个程序只能启动一个客户端


此时选中 Allow multiple instances 之后,就可以开启多个客户端。

我们启动两个客户端,看服务器这边有没有响应。

启动两个客户端后我们来进行观察

第一个客户端

第二个客户端

我们在第二个客户端进行输入,发现没有任何响应
第二个客户端
此时观察服务器,发现只有一个客户端上线

我们发现服务器这边没有再次出现第二个客户端建立连接的结果,只有第一次的客户端程序可以得到响应。

那么这是为什么呢?

观察代码我们就会发现有一个客户端,就需要 accept 一次,有多个客户端,就需要 accept 多次。执行 accept 之后,就会进入 processConnection 。
accept

processConnection 中也有一个循环,这个循环只有当前客户端退出了,循环才会结束。

循环结束,processConnection 方法才会结束,方法结束才会执行到第二次 accept。

processConnection

那此时我们把第一个客户端关闭查看第二个客户端,我们可以看到第二个客户端立即连接成功
客户端

此时服务器已经和该客户端连接成功。
服务器

当然,我们的目的是希望同时能够给 客户端1 提供循环服务,又能够 循环的调用 accept。那我们如何解决呢?这里就得用到我们之前学的多线程啦!

解决方案:

让主线程循环调用accept,当有客户端连接上来的时候就让主线程创建一个新线程,由新线程负责客户端的若干个请求,这个时候多个线程看上去是同时执行的。

这里我们修改TcpEchoServer,在原有的TcpEchoServer基础上修改以下部分代码即可。

public void start() throws IOException {
        System.out.println("服务器启动");
        while(true){
            Socket clientSocket = serverSocket.accept();
            //如果直接调用,该方法会影响这个循环的二次执行,导致 accept 不及时了
            //创建新线程,用新线程来调用 processConnection
            //每次来一个新的客户端都创建一个新的线程
            //clientSocket是服务器的Socket 通过这个Socket和客户端进行通信
            Thread t = new Thread(()->{
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            t.start();

        }
    }

我们再次启动多线程版本的服务器代码,启动两个客户端,发现没有问题。
第一个客户端
第二个客户端
服务器
客户端关闭

那么讲到这里我们的网络编程 Socket 就结束了,感谢小伙伴的支持!!
拜拜

  • 17
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值