TCP流套接字编程(模拟多个客户端与服务器交互)

news2024/11/14 18:26:21

目录

一、ServerSocket API

1.1、ServerSocket构造方法

1.2、ServerSocket方法

二、Socket API 

2.1、socket构造方法 

2.2、socket方法

三、TCP 中的长短连接

四、示例 

实现聊天室功能

五、存在的问题 


一、ServerSocket API

ServerSocket 是创建TCP服务端Socket的API。

1.1、ServerSocket构造方法

方法签名方法说明
ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口

1.2、ServerSocket方法

方法签名方法说明
Socket accept()开始监听指定端口(创建时绑定的端口),有客户连接之后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待
void close()关闭此套接字

二、Socket API 

Socket是客户端Socket,或服务端中接收到客户端建立连接(accpet方法)的请求后,返回的服务端Socket。

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

2.1、socket构造方法 

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

2.2、socket方法

方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

三、TCP 中的长短连接

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

  1. 短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说短连接只能一次收发数据
  2. 长连接:不关闭连接,一致保持连接状态,双方不停的收发数据,即是长连接。也就是说长连接能够多次收发数据。 

对比以上长短连接,两者区别如下:

  • 建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要建立一次连接即可,之后的请求、响应都可以直接传输。相对于说建立连接、关闭连接也是需要耗时的,长连接效率比较高。
  • 主动发送请求不同:短连接一般是客户端主动向服务端发起请求;而长连接不论是客户端还是服务端都可以向对方发起请求。
  • 两者使用的场景不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接使用于客户端与服务端通信频繁的场景,如聊天室等。

四、示例 

实现聊天室功能

  1. 客户端先接收键盘输入,循环接收客户端输入内容,发起聊天。
  2. 发送请求:用户输入的数据封装为输出流,发送到服务端 。
  3. 服务端接收并处理请求:接收请求数据,根据该请求数据来计算响应。
  4. 服务端返回响应:将响应数据封装为输出流,返回给客户端。
  5. 客户端接收响应:简单的打印输出所有的响应内容。

1、基于TCP的客户端


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

/**
 * 基于TCP的客户端
 */
public class TCPEchClient {
    //生命Socket对象
    public Socket socket;
    //通过构造方法来初始化Socket,指定IP地址,端口
    public TCPEchClient(String ServerIp, int port) throws IOException {
        //根据服务器的IP与端口号创建Socket对象
        this.socket=new Socket(ServerIp,port);
    }
    public void start(){
        System.out.println("客户端已启动...");
        try {
            InputStream inputStream=socket.getInputStream();
            OutputStream outputStream=socket.getOutputStream();
            //循环接收用户的输入
            while(true){
                System.out.print("->");
                Scanner sc=new Scanner(System.in);
                String request=sc.nextLine();
                if(request==null||request.equals("")){
                    System.out.println("输入内容不能为空!!!");
                    continue;
                }
                //2、把用户的输入封装到输出流中,可以用PrintWriter简化操作
                PrintWriter writer=new PrintWriter(outputStream);
                //3、把数据发送出去(写数据)
                writer.println(request);
                //强制刷新缓冲区
                writer.flush();
                //4、接收服务器的响应
                Scanner responseSc=new Scanner(inputStream);
                //5、解析相应的数据
                String response=responseSc.nextLine();
                //6、打印日志
                System.out.printf("request=%s,response=%s\n",request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //初始化客户端
    public static void main(String[] args) throws IOException {
        TCPEchClient tcpEchClient=new TCPEchClient("127.0.0.1",9090);
        tcpEchClient.start();
    }
}

2、基于TCP的服务端


import tcp.TCPEchoSever;

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;

/**
 * 基于TCP的服务端
 */
public class TCPEchServer {
    //声明一个服务端的类
    public ServerSocket socket;
    //构造方法,初始化对象
    public TCPEchServer(int port) throws IOException {
        //校验端口
        if(port<1||port>65535){
            throw new RuntimeException("端口号建议在1025~65535之间");
        }
        //根据指定的端口号初始化Socket服务
        this.socket=new ServerSocket();
    }
    public void start() throws IOException {
        System.out.println("服务已启动等待客户端连接...");
        //循环接收服务器连接
        while(true){
            //接收客户端请求
            Socket clientSocket=socket.accept();
            //接收到客户端连接之后,交给专门处理连接的方法。
            processConnection(clientSocket);
        }
    }

    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s %d]客户端已上线.\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        //所有的通信数据都在Socket对象中clientSocket,Tcp传输的数据是字节流的形式
        //1、获取输入输出流
        try {
            InputStream inputStream=clientSocket.getInputStream();
            OutputStream outputStream=clientSocket.getOutputStream();
            //循环处理客户端发来的请求
            while(true){
                //Scanner简化字符串的获取
                Scanner sc=new Scanner(inputStream);
                //判断是否还有数据
                if(!sc.hasNextLine()){
                    System.out.printf("[%s:%d] 客户端已下线.\n",
                            clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;
                }
                //获取用户发来的请求
                String request=sc.nextLine();
                String response=process(request);
                //把响应返回给客户端
                PrintWriter writer=new PrintWriter(outputStream);
                //强制刷新缓冲区
                writer.flush();
                System.out.printf("[%s:%d]request=%s,response=%d\n",clientSocket.getInetAddress().toString()
                        ,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 {
        TCPEchoSever tcpEchoSever=new TCPEchoSever(9090);
        tcpEchoSever.start();
    }
}

3、封装响应数据

import java.io.IOException;
import java.util.Scanner;

public class TCPMsgServer extends TCPEchoSever {

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

    //实现一个聊天的功能
    @Override
    protected String process(String request) {
        //1.打印发送方发来的消息
        System.out.println("->"+request.toString());
        //2.回复对方的消息
        Scanner sc=new Scanner(System.in);
        String response=sc.nextLine();
        //3.返回消息
        return response;
    }

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

运行结果:

五、存在的问题 

1、首先将客户端类对象设置为多实例创建状态,这样就可以创建出多个客户端对象

2、创建第二个客户端时,并没有按预想的连接服务端成功

 3、出现这个现象的原因

4、如何去解决这个问题,实现多个客户端呢?这时就需要引入多线程来执行任务。

利用线程来执行不同的客户端任务,就需要创建一个线程服务端类,该类继承服务端类,重写start()方法。

import tcp.TCPEchoSever;
import java.io.IOException;
import java.net.Socket;

public class TCPThreadServer extends TCPEchoSever {
    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(() ->{
                //接收到客户端连接之后,交给专门处理连接的方法
                try {
                    //在子线程中处理连接
                    processConnection(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            //启动线程
            thread.start();
        }
    }

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

运行结果:

5、手动创建线程太过麻烦,可以利用线程池来进行创建线程 ,还是跟上述操作差不过,重写start()方法

import java.io.IOException;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TCPThreadPoolServer extends TCPEchServer {

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

    @Override
    public void start() throws IOException {
        System.out.println("服务已启动,等待客户端上线...");
        //创建一个线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3,
                10,
                1,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10)); //阻塞队列可存10个服务器
        //循环接收连接
        while (true) {
            //接收客户端请求
            Socket clientSocket = socket.accept();
            //把处理请求连接的操作,加入到线程池中
            threadPool.submit(() -> {
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }
    }
    public static void main(String[] args) throws IOException {
        TCPThreadPoolServer server=new TCPThreadPoolServer(9090);
        server.start();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/736231.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

500个线程运行串行原因排查

场景&#xff1a;项目中有业务需要开启500个线程执行&#xff0c;需要证明有500个线程在执行。用的是一台128核的电脑。服务用docker启动的。所以理论上应该是要有128个线程并行执行的。 目录 一.证明有500个线程在执行(会发现并行度很低) 1.用top命令监控进程内的线程运行情…

netty学习(5):netty实现注册中心和发送JSON数据到指定的客户端

1. 实现&#xff1a;在netty客户端实现netty客户端注册功能&#xff0c;netty客户端需要发送注册消息到netty服务端。 2. 在父工程创建Message类&#xff0c;定义消息格式和消息类型 定义消息类型&#xff1a; package message;public enum MessageType {RegisterRequest,Re…

函数重载与函数递归

一、函数重载 定义&#xff1a;两个函数的函数名称相同&#xff0c;但是参数的个数或者类型不同 参考以下代码&#xff1a; //1.public static int add(int x,int y){return x y;}//2.与1构成重载public static int add(byte a,int b){return a b;}//3.与1构成重载public s…

文件上传漏洞总结

文件上传 文件上传漏洞产生的原理 文件上传漏洞是指用户通过界面上的上传功能上传了一个可执行的脚本文件&#xff0c;而WEB端的系统并未对其进行检测或者检测的逻辑做的不够好。 文件上传漏洞的危害 1、由于是上传的文件&#xff0c;所以文件由用户决定&#xff0c;上传we…

交换机架构整理

网口的基本结构 网口扫盲三:以太网芯片MAC和PHY的关系 问:如何实现单片以太网微控制器? 问:以太网MAC是什么? 问:什么是MII? 问:以太网PHY是什么? 问:造成以太网MAC和PHY单片整合难度高的原因是什么? 问: 网卡上除RJ-45接口外,还需要其它元件吗? 问:10BaseT和100BaseTX…

LeetCode[面试题17.14]最小的K个数

难度&#xff1a;中等 题目&#xff1a; 设计一个算法&#xff0c;找出数组中最小的k个数。以任意顺序返回这k个数均可。 示例&#xff1a; 输入&#xff1a; arr [1,3,5,7,2,4,6,8], k 4 输出&#xff1a; [1,2,3,4]提示&#xff1a; 0 < len(arr) < 1000000 <…

Java设计模式之创建型-建造者模式(UML类图+案例分析)

目录 一、基本概念 二、UML类图 三、角色设计 四、案例分析 五、总结 一、基本概念 建造者模式是一种创建型设计模式&#xff0c;它使我们将一个复杂对象的构建步骤分离出来&#xff0c;使得同样的构建过程可以创建不同的表示。该模式的目的是将构建复杂对象的过程抽象化…

JavaScrpt_13 Web API 正则表达式

JavaScrpt_13 Web API 正则表达式 一、 正则表达式1. 正则基本使用2. 元字符边界符量词范围字符类 3. 替换和修饰符4. change 事件5. 判断是否有类 一、 正则表达式 正则表达式&#xff08;Regular Expression&#xff09;是一种字符串匹配的模式&#xff08;规则&#xff09;…

12_Linux异步通知

目录 异步通知简介 驱动中的信号处理 应用程序对异步通知的处理 驱动程序编写 编写测试APP 运行测试 异步通知简介 在使用阻塞或者非阻塞的方式来读取驱动中按键值都是应用程序主动读取的,对于非阻塞方式来说还需要应用程序通过poll函数不断的轮询。最好的方式就是驱动…

【Python爬虫与数据分析】时间、日期、随机数标准库

目录 一、模块化概述 二、time库 1. 时间获取 2. 时间格式化 3. 程序计时 三、datetime库 1. datetime.datetime类 2. datetime.timedelta类 四、random库 1. 基本随机函数 2. 扩展随机函数 3. 随机时间的生成 一、模块化概述 Python程序由模块组成&#xff0c;一个…

MySQL基础篇第4章(运算符)

文章目录 1、算术运算符1.1 加法与减法运算符1.2 乘法与除法运算符1.3 求模&#xff08;求余&#xff09;运算符 2、比较运算符2.1 等号运算符2.2 安全等于<>2.3 不等于运算符2.4 空运算符2.5 非空运算符2.6 最小值运算符2.7 最大值运算符2.8 BETWEEN AND运算符2.9 IN运算…

typeScript(持续吐血版)

typeScript-02-进阶(TSVue3) 结合vue3来使用TypeScript 使用vite来创建vue3TS的项目 使用vite创建项目&#xff0c;并选择带ts的版本 npm create vitelatest my-vue-ts-app – --template vue-ts 参考链接&#xff1a;https://vuejs.org/guide/typescript/composition-api…

7.软件是怎么样炼成的:c++编译器过程

"重新生成解决方案"&#xff0c;"调试"的背后的四个阶段 故事&#xff1a; 渣男是有套路和步骤的。 代码变成软件也是有固定的套路的 总结&#xff1a; 1.预处理 g -e源程序&#xff0c;-o生成的结果。后面的a.cpp是新的源文件。这个时候还是源程序计算…

leetcode-977. 有序数组的平方

leetcode-977. 有序数组的平方 文章目录 leetcode-977. 有序数组的平方一.题目描述二.第1次提交(std::sort)三.第2次提交(左右指针) 一.题目描述 二.第1次提交(std::sort) class Solution {public:vector<int> sortedSquares(vector<int> &nums) {for (int i …

Centos或Linux编写一键式Shell脚本删除用户、组指导手册

文章目录 一、目的二、操作步骤 一、目的 本指导手册为了更加方便使用Centos或者Linux&#xff0c;并在里面删除用户、用户组。 注意点1&#xff1a;userdel命令删除该用户时&#xff0c;并不能删除该用户的所有信息&#xff0c;只是删除了/etc/passwd、/etc/shadow、/etc/gr…

Vue3 +TScript 基本开发

首先你要使用 vite 创建项目 npm init vuelatest 并选择带ts的版本 文件的结构 main.ts 文件 import { createApp } from "vue" import { createPinia } from piniaimport App from "./App.vue" const pinia createPinia() const app createApp(App)a…

map、multimap、set、multiset讲解

文章目录 &#x1f4cd;前言1. 关联式容器2. 键值对3. 树形结构的关联式容器3.1 set3.1.1 set的介绍3.1.2 set的使用 3.2 map3.2.1 map的介绍3.2.2 map的使用 3.3 muitiset3.3.1 multiset的介绍3.3.2 multiset的使用 3.4 multimap3.4.1 multimap的介绍3.4.2 multimap的使用 3.5…

山西电力市场日前价格预测【2023-07-10】

日前价格预测 预测明日&#xff08;2023-07-10&#xff09;山西电力市场全天平均日前电价为374.23元/MWh。其中&#xff0c;最高日前价格为417.10元/MWh&#xff0c;预计出现在19: 45。最低日前电价为323.51元/MWh&#xff0c;预计出现在13: 30。 价差方向预测 1&#xff1a;实…

利用VitePress部署静态网站

前言 之前看到过很多这样的静态网站&#xff0c;基于Markdown格式&#xff0c;风格基本统一&#xff0c;而且这种网站非常常见&#xff0c;例如&#xff1a; 例如&#xff0c;以下的几个网址&#xff1a; Java HashMap 源码分析 | 未读代码BAT大厂面试题与全栈知识体系结合…

实践:devops之K8s环境持续部署

实践&#xff1a;devops之K8s环境持续部署 目录 推荐文章 https://www.yuque.com/xyy-onlyone/aevhhf?# 《玩转Typora》 1、Kubectl 发布流水线 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X2Q6MzL1-1688896509292)(https://bucket-hg.oss-cn-…