自学内容网 自学内容网

网络编程套接字2

之前我们已经介绍了UDP套接字流程,接下来我们介绍TCP流套接字编程,TCP的一个核心特点,面向字节流,读写数据的基本单位就是字节。

1.API介绍

1.1ServerSocket:是创建TCP服务器Socket的API(专门给服务器用);

方法:

(服务器启动需要先绑定端口号)

TCP是“有连接”,这里的accept是联通连接的关键操作。

2.Socket

Socket是客户端Socket,或服务端中接收到客⼾端建⽴连接(accept⽅法)的请求后,返回的服 务端Socket(服务器和客户端都可以用)。

方法:

这两个参数是服务器的IP和服务器的端口。

代码实例:

TcpEchoServer

public class TcpEchoServer {
    private ServerSocket serverSocket = null;

    // 这里和 UDP 服务器类似, 也是在构造对象的时候, 绑定端口号.
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("启动服务器");

        // 这种情况一般不会使用 fixedThreadPool, 意味着同时处理的客户端连接数目就固定了.
        ExecutorService executorService = Executors.newCachedThreadPool();

        while (true) {
            // tcp 来说, 需要先处理客户端发来的连接.
            // 通过读写 clientSocket, 和客户端进行通信.
            // 如果没有客户端发起连接, 此时 accept 就会阻塞.

            // 主线程负责进行 accept, 每次 accept 到一个客户端, 就创建一个线程, 由新线程负责处理客户端的请求.
            Socket clientSocket = serverSocket.accept();

            // 使用多线程的方式来调整
//            Thread t = new Thread(() -> {
//                processConnection(clientSocket);
//            });
//            t.start();

            // 使用线程池来调整
            executorService.submit(() -> {
                processConnection(clientSocket);
            });
        }
    }

    // 处理一个客户端的连接.
    // 可能要涉及到多个客户端的请求和响应.
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            // 针对 InputStream 套了一层
            Scanner scanner = new Scanner(inputStream);
            // 针对 OutputStream 套了一层
            PrintWriter writer = new PrintWriter(outputStream);
            // 分成三个步骤
            while (true) {
                // 1. 读取请求并解析. 可以直接 read, 也可以借助 Scanner 来辅助完成.
                if (!scanner.hasNext()) {
                    // 连接断开了
                    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
                    break;
                }
                String request = scanner.next();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 返回响应到客户端
                // outputStream.write(response.getBytes());
                writer.println(response);
                writer.flush();

                // 打印日志
                System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress(), clientSocket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

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

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

服务器引⼊多线程, 如果只是单个线程,⽆法同时响应多个客⼾端,此处给每个客⼾端都分配⼀个线程.

// 启动服务器 
public void start() throws IOException {
 System.out.println("服务器启动!");
 while (true) {
 Socket clientSocket = serverSocket.accept();
 Thread t = new Thread(() -> {
 processConnection(clientSocket);
 });
 t.start();
 }
}

 服务器引⼊线程池 为了避免频繁创建销毁线程,也可以引⼊线程池.

// 启动服务器 
public void start() throws IOException {
 System.out.println("服务器启动!");
 ExecutorService service = Executors.newCachedThreadPool();
 while (true) {
 Socket clientSocket = serverSocket.accept();
 // 使⽤线程池, 来解决上述问题 
 service.submit(new Runnable() {
 @Override
 public void run() {
 processConnection(clientSocket);
 }
 });
 }
}

TCPEchoClient

public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        // 直接把字符串的 IP 地址, 设置进来.
        // 127.0.0.1 这种字符串
        socket = new Socket(serverIp, serverPort);
    }

    public void start() {
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            // 为了使用方便, 套壳操作
            Scanner scannerNet = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);

            // 从控制台读取请求, 发送给服务器.
            while (true) {
                // 1. 从控制台读取用户输入
                String request = scanner.next();
                // 2. 发送给服务器
                writer.println(request);
                //    加上刷新缓冲区操作, 才是真正发送数据
                writer.flush();
                // 3. 读取服务器返回的响应.
                String response = scannerNet.next();
                // 4. 打印到控制台
                System.out.println(response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

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

1. 创建socket对象就会在底层和对端建立TCP连接,及记录了对端的信息(服务器的IP和端口),不需要自己在创建变量保存了,直接TCP内部就保存了。

 2.

这两个socket对象不是同一个对象(它们在不同的进程中,甚至在不同的主机上)。

3.行为是自动加上\n

这个操作只是把数据放到“发送缓冲区”中,还没有真正写入到网卡中;如果这里使用print,则数据发过去,服务器收到了,但没有真正处理。

暗含一个约定,一个请求/响应,使用\n作为结束标记,对端读的时候,也是读到\n就结束(认为读到一个完整的请求)。

4.flush方法来“冲刷缓冲区”,才算真正的发送数据。

5.

判断收到的数据中是否包含“空白符”,遇到空白符(比如换行、回车、空格、制表符、翻页符...),认为一个“完整的next”,遇到之前都会堵塞。

6.

在这里clientSocket的生命周期是伴随一次连接的,每个客户端连接,都会创建一个新的;每个客户端断开连接,这个对象也就可以不要了,这时就需要加一个close(),防止文件的泄露。

TCP发送数据时,需要先建⽴连接,什么时候关闭连接就决定是短连接还是⻓连接:

  • 短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能⼀次收发 数据。
  • ⻓连接:不关闭连接,⼀直保持连接状态,双⽅不停的收发数据,即是⻓连接。也就是说,⻓连接可 以多次收发数据。

对⽐以上⻓短连接,两者区别如下:

  •  建⽴连接、关闭连接的耗时:短连接每次请求、响应都需要建⽴连接,关闭连接;⽽⻓连接只需要 第⼀次建⽴连接,之后的请求、响应都可以直接传输。相对来说建⽴连接,关闭连接也是要耗时 的,⻓连接效率更⾼。
  •  主动发送请求不同:短连接⼀般是客⼾端主动向服务端发送请求;⽽⻓连接可以是客⼾端主动发送 请求,也可以是服务端主动发。
  • 两者的使⽤场景有不同:短连接适⽤于客⼾端请求频率不⾼的场景,如浏览⽹⻚等。⻓连接适⽤于 客⼾端与服务端通信频繁的场景,如聊天室,实时游戏等。


原文地址:https://blog.csdn.net/2301_80501383/article/details/143765944

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!