网络编程TCP

🙉专栏推荐:Java入门知识🙉

🙉 内容推荐:Java网络编程(下)🙉

🐹今日诗词: 壮士当唱大风哥, 宵小之徒能几何?🐹


⛳️点赞 ☀️收藏⭐️关注💬卑微小博主🙏

⛳️点赞 ☀️收藏⭐️关注💬卑微小博主🙏


目录

TCP字节流套接字编程

回显服务器

服务端(有点问题的)

空白符问题

客户端

代码问题

原因

解决办法一

服务器(没问题的代码)

解决方法二

线程池的服务端代码(没问题的代码)

美图分享


TCP字节流套接字编程

TCP也有两个类用于网络编程

SeverSocket: 用于TCP字节数据进行网络通信, 创建了TCP服务端API, 专门用于服务器的

Socket: 客户端和服务器都能够使用, 客户端使用可以建立和服务器的通信

服务器可使用可以监听客户端的连接请求

值得注意的是: TCP是字节流传输, 传输单位是字节(byte), 不需要向UDP那样专门搞出一个类来用于传输数据报

SeverSocket类

构造方法: SeverSocket(int Port)

SeverSocket函数方法

函数方法

Socket类

构造方法: Socket(String host, int port)

Socket函数方法

回显服务器

服务端(有点问题的)

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TcpSever {
    private ServerSocket serverSocket = null;
    public TcpSever(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器!启动");
        while (true) {

            Socket clientsocket = serverSocket.accept();
            //这里和UDP不一样,UDP是直接接收请求, 而TCP需要先和客户端建立连接, 才能进一步处理请求
            processmethod(clientsocket);
            //客户端请求结束时应该调用close方法,释放资源, 所以在processmethod方法里调用close方法关闭

        }
    }

    private void processmethod(Socket clientsocket) throws IOException {
        System.out.printf("[%s:%d]客户端成功连接服务器\n", clientsocket.getInetAddress(), clientsocket.getPort());
        //打印日志, 接下来就是响应请求了

        try (OutputStream outputStream = clientsocket.getOutputStream();
             InputStream inputStream = clientsocket.getInputStream()){
            //从clientsocket中获取流对象, 对数据进一步处理, 把他们放到try()里,结束会自动调用close方法

            Scanner scanner = new Scanner(inputStream);

            while (true) {
                //客户端建立连接后, 可能会发送很多请求, 需要循环处理
                //inputStream.read();//读取请求,这样就得到了字符数组,接下来转成字符串, 这样写很麻烦我们可以使用Scanner一步到位

                if (!scanner.hasNext()) {
                    //读取前判断有没有数据, 没有数据说明连接断开了
                    System.out.printf("[%s:%d]客户端断开连接\n", clientsocket.getInetAddress(), clientsocket.getPort());
                    break;
                }

                String request = scanner.next();
                //读取请求, next()方法读取数据是读到空白符

                String response = process(request);
                //根据请求计算响应

                System.out.printf("[%s:%d] request = %s response = %s",
                        clientsocket.getInetAddress(), clientsocket.getPort(), request, response);
                //打印日志

                outputStream.write(response.getBytes());
                //将响应数据写回客户端

            }

        } finally {
            clientsocket.close();
            //客户端断开连接调用close方法释放资源
        }

    }

    private String process(String request) {
        //回显服务器,前面next()方法读取数据读到空白符停止,当我们把数据写回客户端是要把空白符加上
        return request + "\n";
    }

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

空白符问题

空白符是一类分隔符的统称, 不是一个空格, 常见空白符有很多

TCP字节流传输常见的问题就是空白符问题

发送请求和读取请求时都需要考虑分隔符

因为发送的数据带有空白符,当使用next方法读取数据时, 会忽略空白符, 因此当我们向客户端写回请求时需要把空白符加回去, 保持格式的统一性

客户端

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class TcpClient {
    private Socket socket = null;
    public TcpClient(String SeverIP, int SeverPort) throws IOException {
        socket = new Socket(SeverIP, SeverPort);
    }

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

        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            //获取流对象, 把数据传入流对象之后, 等待服务器响应即可, 这里和UDP不同, UDP是把数据打包发送到服务器
            //而TCP客户端已经和服务器建立连接了, 中间通道已经打通, 将数据传入流对象等待响应即可
            //流对象扮演的角色就像是中间商, 服务器和客户端的操作都要经过流对象

            Scanner scannerinpuStream = new Scanner(inputStream);
            while (true) {
                System.out.print("请输入你要发送的请求: ");
                String request = scanner.next();
                request += "\n";
                //next方法会忽略\n, 为了保持服务端next方法一致, 手动加上\n

                outputStream.write(request.getBytes());
                //将请求写到流对象中

                if(!scannerinpuStream.hasNext()) {
                    //没有数据说明读取完毕了
                    break;
                }
                String response = scannerinpuStream.next();
                //从服务器获取响应

                System.out.println(response);
                //将数据打印出来

            }

        }

    }

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

运行效果

代码问题

这种写法只能对一个客户端提供服务,  当我们启动多个客户端时

原因

解决办法一

把processmethod方法放到多线程的环境下运行


服务器(没问题的代码)

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TcpSever {
    private ServerSocket serverSocket = null;
    public TcpSever(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器!启动");
        while (true) {
            Socket clientsocket = serverSocket.accept();
            //这里和UDP不一样,UDP是直接接收请求, 而TCP需要先和客户端建立连接, 才能进一步处理请求

            Thread thread = new Thread(() -> {
                try {
                    processmethod(clientsocket);
                    //客户端请求结束时应该调用close方法,释放资源, 所以在processmethod方法里调用close方法关闭
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            thread.start();
        }
    }

    private void processmethod(Socket clientsocket) throws IOException {
        System.out.printf("[%s:%d]客户端成功连接服务器\n", clientsocket.getInetAddress(), clientsocket.getPort());
        //打印日志, 接下来就是响应请求了

        try (OutputStream outputStream = clientsocket.getOutputStream();
             InputStream inputStream = clientsocket.getInputStream()){
            //从clientsocket中获取流对象, 对数据进一步处理, 把他们放到try()里,结束会自动调用close方法

            Scanner scanner = new Scanner(inputStream);

            while (true) {
                //客户端建立连接后, 可能会发送很多请求, 需要循环处理
                //inputStream.read();//读取请求,这样就得到了字符数组,接下来转成字符串, 这样写很麻烦我们可以使用Scanner一步到位

                if (!scanner.hasNext()) {
                    //读取前判断有没有数据, 没有数据说明连接断开了
                    System.out.printf("[%s:%d]客户端断开连接\n", clientsocket.getInetAddress(), clientsocket.getPort());
                    break;
                }

                String request = scanner.next();
                //读取请求, next()方法读取数据是读到空白符

                String response = process(request);
                //根据请求计算响应

                System.out.printf("[%s:%d] request = %s response = %s",
                        clientsocket.getInetAddress(), clientsocket.getPort(), request, response);
                //打印日志

                outputStream.write(response.getBytes());
                //将响应数据写回客户端

            }

        } finally {
            clientsocket.close();
            //客户端断开连接调用close方法释放资源
        }

    }

    private String process(String request) {
        //回显服务器,前面next()方法读取数据读到空白符停止,当我们把数据写回客户端是要把空白符加上
        return request + "\n";
    }

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

解决方法二

把代码放到线程池中, 这种方法更合适, 不仅解决了只能连接一个客户端, 又能减少线程创建销毁的开销

线程池的服务端代码(没问题的代码)

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class TcpSever {
    private ServerSocket serverSocket = null;
    public TcpSever(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器!启动");

        ExecutorService pool = Executors.newCachedThreadPool();
        //这个线程池可以自动扩容
        while (true) {

            Socket clientsocket = serverSocket.accept();
            //这里和UDP不一样,UDP是直接接收请求, 而TCP需要先和客户端建立连接, 才能进一步处理请求
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processmethod(clientsocket);
                        //客户端请求结束时应该调用close方法,释放资源, 所以在processmethod方法里调用close方法关闭
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }

    private void processmethod(Socket clientsocket) throws IOException {
        System.out.printf("[%s:%d]客户端成功连接服务器\n", clientsocket.getInetAddress(), clientsocket.getPort());
        //打印日志, 接下来就是响应请求了

        try (OutputStream outputStream = clientsocket.getOutputStream();
             InputStream inputStream = clientsocket.getInputStream()){
            //从clientsocket中获取流对象, 对数据进一步处理, 把他们放到try()里,结束会自动调用close方法

            Scanner scanner = new Scanner(inputStream);

            while (true) {
                //客户端建立连接后, 可能会发送很多请求, 需要循环处理
                //inputStream.read();//读取请求,这样就得到了字符数组,接下来转成字符串, 这样写很麻烦我们可以使用Scanner一步到位

                if (!scanner.hasNext()) {
                    //读取前判断有没有数据, 没有数据说明连接断开了
                    System.out.printf("[%s:%d]客户端断开连接\n", clientsocket.getInetAddress(), clientsocket.getPort());
                    break;
                }

                String request = scanner.next();
                //读取请求, next()方法读取数据是读到空白符

                String response = process(request);
                //根据请求计算响应

                System.out.printf("[%s:%d] request = %s response = %s",
                        clientsocket.getInetAddress(), clientsocket.getPort(), request, response);
                //打印日志

                outputStream.write(response.getBytes());
                //将响应数据写回客户端

            }

        } finally {
            clientsocket.close();
            //客户端断开连接调用close方法释放资源
        }

    }

    private String process(String request) {
        //回显服务器,前面next()方法读取数据读到空白符停止,当我们把数据写回客户端是要把空白符加上
        return request + "\n";
    }

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


美图分享

✨🎆谢谢你的阅读和耐心!祝愿你在编程的道路上取得更多的成功与喜悦!"🎆✨🎄

⭐️点赞收藏加关注,学习知识不迷路⭐️

🎉✔️💪🎉✔️💪🎉✔️💪🎉✔️💪🎉

👍😏⛳️点赞☀️收藏⭐️关注😏👍

👍😏⛳️点赞☀️收藏⭐️关注😏👍

👍😏⛳️点赞☀️收藏⭐️关注😏👍

🙆‍♂️🙆‍♂️🙆‍♂️🙆‍♂️🙆‍♂️🙆‍♂️🙆‍♂️🙆‍♂️🙆‍♂️🙆‍♂️🙆‍♂️🙆‍♂️🙆‍♂️

相关推荐

  1. 网络编程练习题(TCP)

    2024-06-06 23:04:02       26 阅读
  2. tcp网络编程(基础)

    2024-06-06 23:04:02       15 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-06 23:04:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-06 23:04:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-06 23:04:02       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-06 23:04:02       20 阅读

热门阅读

  1. 赶紧收藏!2024 年最常见 20道 Kafka面试题(九)

    2024-06-06 23:04:02       10 阅读
  2. 【2024.06.06 晴-周四】

    2024-06-06 23:04:02       8 阅读
  3. IDEA 2022

    IDEA 2022

    2024-06-06 23:04:02      8 阅读
  4. 力扣linkedlist

    2024-06-06 23:04:02       6 阅读
  5. 【高频】如何保证缓存和数据库一致

    2024-06-06 23:04:02       8 阅读
  6. git本地仓库与远程仓库关联

    2024-06-06 23:04:02       11 阅读
  7. 如何重新设置路由器密码

    2024-06-06 23:04:02       9 阅读
  8. 华为防火墙基础配置

    2024-06-06 23:04:02       11 阅读
  9. 整理好了!2024年最常见 20 道 Kafka面试题(九)

    2024-06-06 23:04:02       12 阅读