当前位置: 首页 > article >正文

网络编程套接字之TCP

目录

一、TCP流套接字编程

ServerSocket

Socket

TCP长短连接

二、TCP回显服务器客户端

服务器

客户端


一、TCP流套接字编程

我们来一起学习一下TCP socket api的使用,这个api与我们之前学习的IO流操作紧密相关,如果对IO流还不太熟悉的,可以看看这篇IO流操作

ServerSocket

顾名思义,ServerSocket是创建TCP服务器的Socket对象

构造方法作用
ServerSocket(int port)创建一个服务器套接字Socket,并指定端口号
方法作用
Socket accept()开始监听指定端口,有客户端连接时,返回一个服务端Socket对象,并基于该Socket对象与客户端建立连接,否则阻塞等待
void close()关闭此套接字

Socket

我们这里的Socket既是客户端的Socket,也可能是服务器接收到客户端连接后,返回的服务器Socket,不论是那个Socket,都是双方建立连接后,保存对方信息,进行收发数据的。

构造方法作用
Socket(String host,int port)创建一个客户端Socket对象,并与对应IP主机,对应端口的进程进行连接
方法作用
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回套接字的输入流
OutputStream getOutputStream()返回套接字的输出流

TCP长短连接

顾名思义,我们的TCP的长短连接,就表示我们TCP建立连接后,什么时候关闭连接就决定了是长连接还是短链接。
短连接: 在每次接收到数据并返回响应后,关闭连接。也就是说短连接只能收发一次数据。
长连接: 一直保持连接的状态,不关闭连接,双方可以不停的收发数据。

二、TCP回显服务器客户端

服务器

public class TCPEchoServer {
    protected ServerSocket socket;

    public TCPEchoServer(int port) throws IOException {
        if(port<1025||port>65535){
            throw new BindException("端口号必须在1025-65535之间");
        }

        this.socket=new ServerSocket(port);
    }


    //开始处理客服端的连接请求
    public void start() throws IOException {
        System.out.println("服务器已启动等待客户端连接");

        while (true) {
            //接收客户端请求
            Socket clientSocket = socket.accept();
            //接收到客户端连接之后交给专门的方法进行数据处理
            processConnection(clientSocket);
        }
    }

    protected void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线了.\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        //所有的通讯数据都封装在Socket对象中,对应的就是入参clientSocket.TCP传输形式是字节流
        //1.通过输入输出流获取发送数据
        try(InputStream inputStream=clientSocket.getInputStream();
            OutputStream outputStream=clientSocket.getOutputStream()){
            //2.循环读取客户端发来的数据
            while (true) {
                //通过Scanner简化数据的读取
                Scanner scanner=new Scanner(inputStream);
                if(!scanner.hasNextLine()){
                    System.out.printf("[%s:%d] 客户端下线了.\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;
                }
                //获取用户发来的具体内容
                String request=scanner.nextLine();
                //根据请求计算响应
                String response=process1(request);
                //把响应写入输出流
                PrintWriter writer=new PrintWriter(outputStream);
                //一次读一行,不能用write
                writer.println(response);
                //刷新缓冲区相当于系统把数据交给了网卡
                writer.flush();
                //打印日志
                System.out.printf("[%s:%d] request = %s, response = %s\n", clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(), request, response);



            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    protected String process1(String request) {
        return request;
    }

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

}

客户端

public class TCPchoClient {
    //声明Socket对象
    private Socket socket;

    public TCPchoClient(String serverIp,int serverPort) throws IOException {
        //校验
        if (serverIp == null || serverIp.isEmpty()) {
            throw new RuntimeException("Ip地址不能为空");
        }
        if (serverPort < 1025 || serverPort > 65535) {
            throw new RuntimeException("端口号必须在1025-65535之间");
        }
        this.socket = new Socket(serverIp,serverPort);
    }

    public void start(){
        System.out.println("客户端已启动");
        //从Socket对象获取输入和输出流对象
        try(InputStream inputStream=this.socket.getInputStream();
            OutputStream outputStream=this.socket.getOutputStream()) {
            //循环接受用户的输入
            while (true) {
                System.out.println("->");
                Scanner scanner=new Scanner(System.in);
                String request=scanner.nextLine();
                //非空校验

                if (request == null || request.isEmpty()) {
                    System.out.println("输入内容不能为空");
                    continue;
                }
                //2.把用户的输入封装到输入流,可以用PrintWriter简化
                PrintWriter writer=new PrintWriter(outputStream);
                //3.把数据写入到输出流
                writer.println(request);
                //强制刷新缓冲区
                writer.flush();
                //接受返回数据
                Scanner responseScanner=new Scanner(inputStream);
                //解析响应数据
                String response = responseScanner.nextLine();
                //打印日志
                System.out.printf("request=%s,response=%s\n",request,response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


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

当我们客户端连接上这个服务器的时候,就执行到processConnection方法的while循环中,只要该方法不结束,我们的accpet就无法获取到第二个客户端socket对象。
那么我们如何解决这个问题呢?
使用多线程,我们的主线程专门负责进行accept,每收到一个连接,创建新线程,由新线程来负责处理这个新的客户端。

public class TCPThreadServer extends TCPEchoServer{

    public TCPThreadServer(int port) throws IOException {
        super(port);
    }

    @Override
    public void start() throws IOException {
        System.out.println("服务器已经启动,等待客户端连接");
        //循环接收客户端的连接请求
        while (true) {
            Socket clientSocket = socket.accept();
            //为每一个客户端连接创建一个线程
            Thread thread = new Thread(() -> {
                //子线程处理连接
                processConnection(clientSocket);
            });
            thread.start();
        }
    }

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

http://www.kler.cn/a/552763.html

相关文章:

  • ES8字符串填充用法总结:padStart(),padEnd(),rest剩余参数的用法{name,...obj},扩展运算符的用法,正则表达式命名捕获组
  • LabVIEW利用CANopen的Batch SDO写入
  • DEX-EE三指灵巧手:扩展AI与机器人研究的边界
  • win10系统上的虚拟机安装麒麟V10系统提示找不到操作系统
  • 赛前启航 | Azure 应用开发实战指南:开启创意的无限可能
  • RadASM环境,win32汇编入门教程之六
  • MySQL 索引失效处理:原因分析与优化实战
  • vue3项目,旅游景点页面
  • 高电服务器托管:企业IT基础设施的可靠之选
  • MySQL + Python 开发之旅:初识数据库与连接搭建
  • Docker 私有仓库 Harbor 详解
  • Flask的知识点总结
  • VScode运行C语言提示“#Include错误,无法打开源文件stdio.h”
  • Vue中事件名的命名规范
  • 打开多个chrome历史记录
  • IDEA中集成Maven
  • 一问读懂AI工具—DeepSeek、Kimi、豆包、文心一言、通义千问
  • 【C++】结构体排序+sort(),cmp()参数写法口诀
  • 社群共建与共享:以十点读书会为例探讨开源AI智能名片2+1链动模式S2B2C商城小程序的应用
  • 从C语言源码到可执行文件的生成过程通常包括