【JavaEE】网络(3)


一、TCP流套接字编程

API介绍:

1.1 ServerSocket

ServerSocket 是创建TCP服务端Socket的API

构造方法:

构造方法描述
ServerSocket ( int  port )创建一个服务端流套接字Socket,并绑定指定端口

方法:

方法描述
Socket  accept ( )
void close ( )关闭此套接字

1.2 Socket

Socket 是客户端Socket,或服务端中接收到客户端简历连接(accept方法)的请求后,返回服务端Socket

不管是客户端还是服务端Socket,都是双方简历连接以后,保存的对端信息,及用来与对方收发数据的

构造方法:

构造方法

描述

Socket ( String host,  int port )创建一个客户端流套接字,并与对应IP主机上对应端口的进程建立连接

方法:

方法描述
InetAddress  getInetAddress( )

返回套接字所连接的地址

InputStream getInputStream( )返回此套接字的输入流
OutputStream getOutputStream( )返回此套接字的输出流

二、模拟回显服务器

public class TCPEchoServer {
    private ServerSocket serverSocket = null;

    public TCPEchoServer (int port) throws IOException {
        serverSocket = new ServerSocket(port); // 创建TCP 服务端Socket并绑定端口号
    }


}

TCP是面向有连接型的,客户端要先和服务器成功建立连接才能发送数据

public class TCPEchoServer {
    private ServerSocket serverSocket = null;

    public TCPEchoServer (int port) throws IOException {
        serverSocket = new ServerSocket(port); // 创建 TCP 服务端Socket并绑定端口号
    }


    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket); // 通过这个方法处理连接
        }
    }
}

服务器启动后就要代码就会走到accept方法,如果没有客户端连接上来,accept就会阻塞,直到有客户端连接上来;客户端连接上来后,通过processConnection方法处理这个连接

private void processConnection (Socket clientSocket) throws IOException {
    //1. 打印一个日志,告知说当前有客户端连上了
    System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());

    //2. 从 clientsocket 中获取流对象,来进一步的进行 后续操作
    try (InputStream inputStream = clientSocket.getInputStream(); 
             OutputStream outputStream = clientSocket.getOutputStream()) {


    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        clientSocket.close();
    }

获取到流对象后,就要从客户端的输入流中读取请求,我们可以采用inputStream.read(byte[ ] buf)的方式读取,但是读取的结果是一个byte[ ],在处理请求时还要转化为一个字符串,而且一次连接不止一个请求,一个byte[ ]里面可能有多给请求,转化为字符串后不易将不同的请求区分;所以这里采用Scanner

Scanner scanner = new Scanner(inputStream); //使用Scanner读取客户端的输入流中的数据
while (true) { //设置循环,循环读取多个请求
    //3.1 读取请求并解析
    // scanner.hasNext收到了请求且有明确的分隔符则返回true
    if (!scanner.hasNext()) {  //若tcp断开则返回false
        System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort()); //souf
        break;
    }
    String request = scanner.next(); //读取输入流中的请求

}

在客户端发送的每一个请求后面都加上一个'\n',而scanner.hasNext( )就是在扫描输入流中是否还有"标记",这个标记就是指空白符(空格、制表符、换行符等),而我们每一个请求后面都会加上换行符,所以只要输入流中还有请求,循环就会继续

//3.2 根据请求计算响应
String response = process(request);
//3.3 把响应写回给客户端, 写到客户端Socket的输出流中
outputStream.write(response.getBytes(), 0, response.getBytes().length);
//3.4 服务器打印日志
System.out.printf("[%s:%d] req=%s, resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);

返回给客户端的响应有很多,所以每个响应的后面都要加上换行符

private String process (String request) {
    return request + "\n";
}

服务器完整代码:

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 clientSocket = serverSocket.accept(); //客户端没有连接上来,这里就会进入阻塞
            processConnection(clientSocket);
        }
    }

    private void processConnection (Socket clientSocket) throws IOException {
        //1. 打印一个日志,告知说当前有客户端连上了
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());

        //2. 从socket 中获取流对象,来进一步的进行 后续操作
        try (InputStream inputStream = clientSocket.getInputStream(); //!!!!从客户端的socket连接获取输入流和输出流
             OutputStream outputStream = clientSocket.getOutputStream()) {

            //使用Scanner读取客户端的输入流
            Scanner scanner = new Scanner(inputStream);

            //3. 也搞一个循环,针对每个连接,客户端都可能会发来多个请求,服务器也就需要返回多个响应了
            while (true) {
                //3.1 读取请求并解析
                //scanner.hasNext收到了请求且有明确的分隔符则返回true
                if (!scanner.hasNext()) {   //若tcp断开则返回false
                    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort()); //souf
                    break;
                }
                String request = scanner.next(); //读取输入流中的请求
                //3.2 根据请求计算响应
                String response = process(request);
                //3.3 把响应写回给客户端, 写到客户端Socket的输出流中
                outputStream.write(response.getBytes(), 0, response.getBytes().length);
                //3.5 服务器打印日志
                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 + "\n"; //每个响应结尾加上换行符
    }

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

}

接下来编写客户端代码:

public class TCPEchoClient {
    private Socket socket = null;

    public TCPEchoClient(String serverIp, int serverPort) throws IOException {
        socket = new Socket(serverIp, serverPort);
    }

上述构造方法本身就能和指定的服务器申请连接,然后服务器那边调用accept方法后就能成功建立连接,建立连接的过程都是操作系统内核完成的

public void start() {
        System.out.println("客户端启动!");
        Scanner scanner = new Scanner(System.in);

        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            Scanner scannerNetwork = new Scanner(inputStream); //读取输入流中服务器返回的响应

            while (true) {


            }

        } catch (IOException e) {
            e.printStackTrace();
        }
}

接下来在循环中,客户端可以不断发送请求并返回响应

//1. 从控制台读取数据
System.out.print("请输入要发送的数据: ");
String request = scanner.next(); //这个scanner只能读取控制台的数据,和输入流无关

之前约定每个请求后面都要加上换行符,这里request后面没有换行符,所以要手动加上后再写到输入流中,写入输入流就相当于将请求发送给了服务器

//2. 把请求发送给服务器
request += "\n";
outputStream.write(request.getBytes());

【疑问】为什么说这样就是将请求发送给了服务器?

【解答】在客户端程序中通过Socket的构造方法申请与服务器端建立连接,然后在服务器端程序中,通过accept方法接收了该连接,此时客户端和服务器端的两个Socket对象:socket和clientSocket,它们就是彼此关联的,通过他们的流对象就可以互相进行读写操作,比如: clientSocket的输出流对象outputStream通过write方法写入响应,socket可以通过Scanner(inputStream)读取响应,反过来也可以

//3. 从服务器读取到响应
if (!scannerNetwork.hasNext()) {
    break;
}
String response = scannerNetwork.next(); //scannerNetwork是扫描输入流的,和控制台无关
//4. 把响应显示到控制台上
System.out.println(response);

上述代码,服务器将相应写回到输出流中,客户端通过Scanner读取响应

完整客户端代码如下:

public class TCPEchoClient {
    private Socket socket = null;

    public TCPEchoClient(String serverIp, int serverPort) throws IOException {
        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()) {
            Scanner scannerNetwork = new Scanner(inputStream);

            while (true) {
                //1. 从控制台读取数据
                System.out.print("请输入要发送的数据: ");
                String request = scanner.next();
                //2. 把请求发送给服务器
                request += "\n";
                outputStream.write(request.getBytes());

                //3. 从服务器读取到响应
                if (!scannerNetwork.hasNext()) {
                    break;
                }
                String response = scannerNetwork.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();
    }


}

接下来同时启动服务器程序和客户端程序:

通过控制台输入请求:


上述服务器程序中还有个问题:因为只有一个processConnection方法,所以它一次只能处理一个客户端连接,当有第二个客户端连接时,必须等到第一个客户端断开连接才能建立第二个连接,这是不合理的,所以可以引入多线程,这样可以处理多个客户端连接

Thread t = new Thread (() -> {
    try {
        processConnection(clientSocket);
    } catch (IOException e) {
        e.printStackTrace();
    }
});

t.start();

🙉本篇文章到此结束

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值