【网络编程】TCP流套接字编程 | Socket类 | ServerSocket类 | 文件资源泄露 | TCP回显服务器 | 网络编程

本文主要是介绍【网络编程】TCP流套接字编程 | Socket类 | ServerSocket类 | 文件资源泄露 | TCP回显服务器 | 网络编程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

        • TCP流套接字编程
          • 1.ServerSocket类
          • 2.Socket类
          • 3.文件资源泄露
          • 4.**TCP回显服务器**


TCP流套接字编程

​ ServerSocket类和Socket类这两个类都是用来表示socket文件(抽象了网卡这样的硬件设备)。

TCP是面向字节流的,传输的基本单位是byte 字节。和UDP不同,UDP传输的单位是数据报。

1.ServerSocket类

给服务器使用的类,用这个类来绑定端口号

2.Socket类

既会给服务器用,又会给客户端用

因为TCP的有连接的,会保存对端的连接。不用像UDP那样每次发送都要手动在send方法中指定目标地址。

​ TCP的建立连接,由系统内核自动负责完成的。客户端,要发起“建立连接”的动作。服务器,要把建立好的连接从内核中拿到应用程序里。如果客户端和服务器建立连接,服务器的应用程序不需要任何操作,系统内核直接完成了连接建立的流程(三次握手),完成流程后,就会在内核的队列中排队(每个ServerSocket都会有这个队列)。应用程序要想和这个客户端进行通信,就需要通过按accept方法,把内核队列里已经建立好连接的对象,拿到应用程序中。

符合生产者消费者模型

            //通过accept方法,把内核中已经建立好的连接拿到应用程序中//建立连接的细节流程,都是内核自动完成的,应用程序只需要用现成的Socket clientSocket = serverSocket.accept();

返回的是一个Socket对象

ServerSocket专门用来接收连接, Socket类型的 clientSocket用来后续的客户端进行通信。

马路上招揽人头的销售 和 售楼部的员工

        clientSocket.getInputStream();clientSocket.getOutputStream();

InputStream和OutputStream就是字节流,TCP是传输内容同样是字节流。借助这两个对象,完成数据的“发送”和“接收”。

通过InputStream进行read操作,就是“接收”

通过OutputStream进行write操作,就是“发送”

3.文件资源泄露

​ 由于DatagramSocket和ServerSocket在程序中,只有一个对象,生命周期都是贯穿整个程序的。随时有请求过来,都会使用到。不涉及到一直频繁申请导致的泄露问题。

​ 但是clientSocket,每个循环中,每有一个新的客户端来建立连接,都会创建出新的clientSocket。并且这个Socket最多使用到该客户端断开连接。如果此时有很多客户端频繁建立连接,就会出现文件资源泄露的问题。

        try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {

这里只是关闭了clientSocket自带的流对象,并没有关闭本身。需要手动进行关闭

        }finally {clientSocket.close();//进行clientSocket的关闭//processConnection方法就是在处理一个连接,这个方法执行完毕,连接就处理完了。}
4.TCP回显服务器

服务器

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) {//通过accept方法,把内核中已经建立好的连接拿到应用程序中//建立连接的细节流程,都是内核自动完成的,应用程序只需要用现成的Socket clientSocket = serverSocket.accept();processConnection(clientSocket);}}/*** 通过这个方法,来处理当前的连接** @param clientSocket*/public void processConnection(Socket clientSocket) throws IOException {//1.进入方法后,先打印日志,表示有客户端连接System.out.printf("[%s,%d] 客户端上线\n", clientSocket.getInetAddress(), clientSocket.getPort());//2.进行数据交互try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {//使用try(){}来自动关闭close//由于客户端发送来的数据,可能是“多条数据”,进行循环处理while (true){Scanner scanner = new Scanner(inputStream);if (!scanner.hasNext()){//如果没有下一条数据,连接断开,循环结束System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());break;}String request = scanner.next();//以空白符为本次读取字节流结束的标记//1.读取请求并解析String response = process(request);//2.根据请求,计算响应//3.把响应写回到客户端://可以把String转成字节数组,写进 OutputStream//也可以使用PrintWriter把OutputStream包裹一下,写进字符串PrintWriter printWriter = new PrintWriter(outputStream);//此处的println是写入到outputStream对应的流对象中,也就是写入到clientSocket里面,// 数据就通过网络发送出去了。发送给当前连接的另外一端。//因为之前连接本身就记录了对方的地址和端口,在写数据时直接写数据内容即可,不需要手动指定发给谁。printWriter.println(response);//写就是输出的体现形式//此处使用println带有\n,也是为了后续客户端可以使用Scanner.next来读取数据printWriter.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 {clientSocket.close();//进行clientSocket的关闭//processConnection方法就是在处理一个连接,这个方法执行完毕,连接就处理完了。}}public String process(String request){//回显服务器return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();  }}
服务器启动
[/127.0.0.1,63510] 客户端上线
[/127.0.0.1:63510] req = 你好 ,resp = 你好
[/127.0.0.1:63510] req = hello ,resp = hello
[/127.0.0.1:63510] 客户端下线
[/127.0.0.1,63523] 客户端上线
[/127.0.0.1:63523] req = 6666666 ,resp = 6666666

客户端

public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serverIp, int serverPort) throws IOException {//需要在创建Socket的同时,和服务器“建立连接”,告诉Socket,服务器在哪。//当new这个对象时,操作系统的内核就会完成三次握手的具体细节,完成建立连接的过程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()) {PrintWriter writer = new PrintWriter(outputStream);Scanner scannerNetwork = new Scanner(inputStream);while (true) {//1.从控制台读取用户输入的内容System.out.println("->");String request = scanner.next();//2.把字符串作为请求,发送给服务器writer.println(request);//客户端发的时候有换行,和服务器的scanner.next匹配writer.flush();//3.从服务器读取响应String response = scannerNetwork.next();//和服务器的PrintWrite.println匹配//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();}
}
客户端启动
->
6666666
6666666
->

如果同时启动两个客户端,同时连接服务器。先启动的客户端正常运行,另一个后启动的客户端,无法与服务器进行交互

​ 在第一个客户端过来后,accept就返回得到了一个clientSocket,进入了processConnection方法。又进入了一个while循环,反复处理客户端发来的请求数据,如果客户端没发请求,服务器的代码就会阻塞在scanner.hasNext。此时第二个客户端也过来建立连接,连接建立成功后,连接对象就会在内核的队列里面,等待accept把连接取出来,在代码中处理。此时无法第一时间执行到第二次accept

第一个循环是循环获取连接,第二个循环是循环获取请求。第一个客户端就会使服务器处于processConnection方法内部, 此时卡在了方法中的循环,无法第二次执行accept方法。只有第一个客户端退出, 方法中的循环才能结束,从而第二次执行 accept

  • 要解决这个问题,就要在处理第一个客户端请求的过程中,让代码能够快速的第二次执行accept

​ 让两个循环能够“并发”执行,各执行各的,不会因为进入循环而影响另一个循环。所以,需要创建一个新的线程,由线程来执行processConnection方法。主线程就可以继续执行下次accept。新线程负责processConnection方法内部的循环。每有一个客户端,就要分配一个线程。

一个人是无法同时完成拉客 和 介绍楼盘的工作的

    public void start() throws IOException {System.out.println("服务器启动");while (true) {//通过accept方法,把内核中已经建立好的连接拿到应用程序中//建立连接的细节流程,都是内核自动完成的,应用程序只需要用现成的Socket clientSocket = serverSocket.accept();//直接执行processConnection方法,会导致服务器不能处理客户端//创建线程调用。Thread thread = new Thread(()->{try {processConnection(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});thread.start();}}

新的线程负责在processConnection里面来循环处理客户端的请求。

  • 如果有很多客户端,频繁的建立、断开连接。就会导致服务器频繁的创建销毁线程,造成大量开销。可以使用线程池来进行优化。
    public void start() throws IOException {System.out.println("服务器启动");ExecutorService service = Executors.newCachedThreadPool();while (true) {//通过accept方法,把内核中已经建立好的连接拿到应用程序中//建立连接的细节流程,都是内核自动完成的,应用程序只需要用现成的Socket clientSocket = serverSocket.accept();//直接执行processConnection方法,会导致服务器不能处理客户端//创建线程调用。
//            Thread thread = new Thread(()->{
//                try {
//                    processConnection(clientSocket);
//                } catch (IOException e) {
//                    throw new RuntimeException(e);
//                }
//            });
//            thread.start();//使用线程池进行优化service.submit(new Runnable() {@Overridepublic void run() {try {processConnection(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}}});}}
  • 但是如果出现巨量的线程。可以用协程来解决。除了携程,可以使用IO多路复用/IO多路转接的方法来处理(用一个线程,同时处理多个客户端的socket)->NIO

点击移步博客主页,欢迎光临~

偷cyk的图

这篇关于【网络编程】TCP流套接字编程 | Socket类 | ServerSocket类 | 文件资源泄露 | TCP回显服务器 | 网络编程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/939468

相关文章

一份LLM资源清单围观技术大佬的日常;手把手教你在美国搭建「百万卡」AI数据中心;为啥大模型做不好简单的数学计算? | ShowMeAI日报

👀日报&周刊合集 | 🎡ShowMeAI官网 | 🧡 点赞关注评论拜托啦! 1. 为啥大模型做不好简单的数学计算?从大模型高考数学成绩不及格说起 司南评测体系 OpenCompass 选取 7 个大模型 (6 个开源模型+ GPT-4o),组织参与了 2024 年高考「新课标I卷」的语文、数学、英语考试,然后由经验丰富的判卷老师评判得分。 结果如上图所

2024.6.24 IDEA中文乱码问题(服务器 控制台 TOMcat)实测已解决

1.问题产生原因: 1.文件编码不一致:如果文件的编码方式与IDEA设置的编码方式不一致,就会产生乱码。确保文件和IDEA使用相同的编码,通常是UTF-8。2.IDEA设置问题:检查IDEA的全局编码设置和项目编码设置是否正确。3.终端或控制台编码问题:如果你在终端或控制台看到乱码,可能是终端的编码设置问题。确保终端使用的是支持你的文件的编码方式。 2.解决方案: 1.File -> S

【Altium】查找PCB上未连接的网络

【更多软件使用问题请点击亿道电子官方网站】 1、文档目标: PCB设计后期检查中找出没有连接的网络 应用场景:PCB设计后期,需要检查是否所有网络都已连接布线。虽然未连接的网络会有飞线显示,但是由于布线后期整板布线密度较高,虚连,断连的网络用肉眼难以轻易发现。用DRC检查也可以找出未连接的网络,如果PCB中DRC问题较多,查找起来就不是很方便。使用PCB Filter面板来达成目的相比DRC

零基础STM32单片机编程入门(一)初识STM32单片机

文章目录 一.概要二.单片机型号命名规则三.STM32F103系统架构四.STM32F103C8T6单片机启动流程五.STM32F103C8T6单片机主要外设资源六.编程过程中芯片数据手册的作用1.单片机外设资源情况2.STM32单片机内部框图3.STM32单片机管脚图4.STM32单片机每个管脚可配功能5.单片机功耗数据6.FALSH编程时间,擦写次数7.I/O高低电平电压表格8.外设接口

16.Spring前世今生与Spring编程思想

1.1.课程目标 1、通过对本章内容的学习,可以掌握Spring的基本架构及各子模块之间的依赖关系。 2、 了解Spring的发展历史,启发思维。 3、 对 Spring形成一个整体的认识,为之后的深入学习做铺垫。 4、 通过对本章内容的学习,可以了解Spring版本升级的规律,从而应用到自己的系统升级版本命名。 5、Spring编程思想总结。 1.2.内容定位 Spring使用经验

通过SSH隧道实现通过远程服务器上外网

搭建隧道 autossh -M 0 -f -D 1080 -C -N user1@remotehost##验证隧道是否生效,查看1080端口是否启动netstat -tuln | grep 1080## 测试ssh 隧道是否生效curl -x socks5h://127.0.0.1:1080 -I http://www.github.com 将autossh 设置为服务,隧道开机启动

通信系统网络架构_2.广域网网络架构

1.概述          通俗来讲,广域网是将分布于相比局域网络更广区域的计算机设备联接起来的网络。广域网由通信子网于资源子网组成。通信子网可以利用公用分组交换网、卫星通信网和无线分组交换网构建,将分布在不同地区的局域网或计算机系统互连起来,实现资源子网的共享。 2.网络组成          广域网属于多级网络,通常由骨干网、分布网、接入网组成。在网络规模较小时,可仅由骨干网和接入网组成

【服务器运维】MySQL数据存储至数据盘

查看磁盘及分区 [root@MySQL tmp]# fdisk -lDisk /dev/sda: 21.5 GB, 21474836480 bytes255 heads, 63 sectors/track, 2610 cylindersUnits = cylinders of 16065 * 512 = 8225280 bytesSector size (logical/physical)

【服务器运维】CentOS6 minimal 离线安装MySQL5.7

1.准备安装包(版本因人而异,所以下面的命令中版本省略,实际操作中用Tab自动补全就好了) cloog-ppl-0.15.7-1.2.el6.x86_64.rpmcpp-4.4.7-23.el6.x86_64.rpmgcc-4.4.7-23.el6.x86_64.rpmgcc-c++-4.4.7-23.el6.x86_64.rpmglibc-2.12-1.212.el6.x86_64.r

【服务器运维】CentOS7 minimal 离线安装 gcc perl vmware-tools

0. 本机在有网的情况下,下载CentOS镜像 https://www.centos.org/download/ 1. 取出rpm 有的情况可能不需要net-tools,但是如果出现跟ifconfig相关的错误,就把它安装上。另外如果不想升级内核版本的话,就找对应内核版本的rpm版本安装 perl-Time-Local-1.2300-2.el7.noarch.rpmperl-Tim