网络编程-----(Socket编程TCP)

news2024/9/22 21:22:30

 

 

在咱们的TCP  API中,也是主要是涉及到两个类:

1)ServerSocket:主要是给TCP服务器来进行使用的;

2)Socket:我们既需要给客户端来进行使用,也需要给服务器来进行使用;

这样就是说我们是不需要使用专门的类来进行表示传输的包,因为我们主要面向的是字节流,我们是以字节为单位进行传输的

咱们在进行创建实例的时候,我们希望把ServerSocket的实例叫做ListenSocket,listen的原意就叫做监听,因为在我们的JAVA中的Socket是体现不出来接听的含义的,但是实际上它还有监听的效果;

 

 

如果有连接accept方法就会返回一个Socket对象,也就是说进一步的客户端和服务器的交互就交给Socket了;

1)也就是说咱们的ServerSocket就是一个接线员,他并不会进行负责具体的办理业务,而是把这个业务交给其他人来进行处理;

2)每当我们的ServerSocket通过accept方法感知到了有一个客户端来和我们的服务器建立连接了,那么就会创建出一个Socket对象来进行通信,服务器针对每一个客户端,都会创建出一个Socket来进行通信;

ServerSocket和Socket之间的关系:

一:比如说我以后想去买房,刚出门迎面就走来了一个西装革履的小哥哥,这个小哥哥就带着我来到了楼盘售楼部,这里面是人山人海,这个小哥哥进去之后就打了声招呼,喊出了一个年轻的小姐姐;

二:这个小哥哥就说:这个小姐姐是咱们这个楼盘里面的置业顾问,由它来给你介绍这个该楼盘的销售情况,然后这个小哥哥就走了,他会继续回到这个马路上面,回到马路牙子上,继续去拉其他的人,因为他还是要负责和其他的客户端建立连接;

比如说如果这个小哥哥又拉到了一个人,还是会分配小姐姐的

三:然后我就和这个小姐姐不断地进行交流,由此可见小哥哥就是ServerSocket(只有一个人),小姐姐(多个)就是Socket,买房的人就是一个客户端(数据),对于咱们的每一个想要买房的人,都需要自动分配一个Socket(小姐姐),同时我也是一个Socket实例;

这里面就分成了两步,一个是专门负责数据连接,一个是专门负责数据通信

而我就相当于是客户端的Socket对象,我首先要和服务器和ServerSocket进行连接,最后才可以和服务器专门进行分配的Socket对象通过字节流或者是字符流来进行数据传输;

1)由于咱们的TCP是有连接的,我们一进入到循环是不可以能开始读取数据,而是需要先进行和客户端建立连接,再进行传输数据,先进行接电话,而接电话的前提是有人给你打电话;只有说在我们的客户端Socket中通过构造方法指定服务器的IP地址和端口号,这就相当于有人给你打电话,而咱们的服务器里面的ServerSocket中的accept方法就会感知到,并进行三次握手进行连接,就是相当于是接电话;
2)咱们的accept操作,如果说此时没有客户端和你建立连接,这个accept方法就会阻塞,直到有人向我们当前的服务器建立了连接;
3)因为我们的客户端的请求的主机可能有多份,所以我们服务器针对每一个客户端主机都会有一个Socket对象来进行处理;

4)UDP的服务器进入主循环之后,就尝试用receive读取请求了,这是无连接的
5)但是我们的TCP是有连接的,首先需要做的事,先建立起连接,相当于客户端的代码是货品,就要通过一条公路来把这些货发送过去
6)当服务器运行的时候,是否有客户端来建立连接,不确定,如果客户端没有建立连接,accept就会进行阻塞等待,也就是说咱们的TCP必须先通过accept方法先建立好连接才可以进行传输数据,而我们的UDP完全不管37,21就直接进行发送数据

1)在一开始服务器进行启动的时候,我们就需要指定一个端,,后续客户端要根据这个端口来进行访问。服务器的IP地址默认是主机IP
2)后续客户端进行访问服务器的时候,目的IP就是服务器的IP。不需要我们服务器的开发者来进行绑定了,只要我们确定服务器在一台电脑,IP地址就是确定了;

在服务器里面获取到客户端的IP地址和端口号 

1)我们可以直接通过ServerSocket实例调用accept方法返回的Socket对象的:

getInetAddress()方法获取到IP地址;

2)我们可以直接通过ServerSocket实例调用accept方法返回的Socket对象的getPort()方法就可以来进行获取到端口号;

1)第一个类:ServerSocket,创建的是实例是listensocket,需要给listenSocket关联上一个端口号,他的构造方法是ServerSocket(int port),还是向服务器指定一个端口;

2)他的其中的accept这样的方法和TCP有连接有着很大的关系,accept就相当于接电话这个操作,监听指定端口,相当于在外场拉客(买房子在场外拉客,来和我们的业务人员沟通一下买房呗),把一个内核建立好的链接交给Socket代码来处理;

listenSocket是ServerSocket创建的实例,它主要的作用是:

1)调用accept方法,是用来与另一台主机进行连接,确定是有连接的,如果主机一直无法建立连接,就会出现阻塞

2)把主机传输过来的数据交给我们的climentSocket进行处理,场外拉客的人直接把客户交给业务人员

3)也就是说客户端尝试建立连接,首先是服务器的操作系统这一层和客户端进行一些相关的流程,先把这个连接建立好;

3)第二个类 Socket,在服务器收到客户端建立连接后,返回的服务器端Socket,它主要是在内场给客户端Socket提供具体的服务,把一台主机传输过来的数据进行解析,并进行返回;

那么我们具体的两个Socket是如何进行通信的呢? 

咱们这里面针对的TCPSocket的读写就和文件读写是一模一样的;

1)咱们在进行读Socket文件的数据,就是相当于是在读取网络上面别的主机给咱们发送过来的数据

2)咱们在向Socket文件中写数据的时候,就是相当于是在网络上面向目标主机发送数据

服务器:

1)再使用Socket对象的getInputStream和getoutputStream对象得到一个字节流对象,这时就可以进行读取和写入了;此时的读操作可以调用inputstream.read()里面传入一个字节数组,然后再转化成String,但是比较麻烦判定结束也是比较麻烦的,直到读到的结果是-1我们才会结束循环,所以我们优先使用Scanner来进行读取,这还是要多些循环,我们使用inputStream.read()的时候,就相当于是读取客户端发送过来的数据

2)我们可以直接通过Socket对象的.getInputStream()来进行获取到流对象,但是具体读的时候Scanner 来进行具体的读,new Scanner(InputStreaam),就巧妙地读到了客户端的请求,在调用scan.next(),写的时候,直接利用Printstream new Printstream()构造方法里面直接写Outputstream,当调用println方法的时候,就默认写回到客户端了

当我们使用outputStream.write()方法的时候,就相当于向我们的客户端返回了数据

客户端过程:全程只需要使用Socket对象

1)创建一个socket对象,创建的时候同时指定服务器的IP和端口号,然后把它们传入到socket中,这个过程就会让客户端和服务器建立连接,这就是三次握手,这个过程就是内核完成的;

2)这时客户端就可以通过Socket对象中的getInputStream和getOutputstream,来得到一个字节流对象,这时就可以与服务器进行通信了;

3)在读的时候,要注意此时只写一个Socket文件,直接把服务器的端口号和IP地址,传入进去进行构造,就自动地和客户端进行了连接;此时还要有InputStreamSocket和OutstreamSocket;

针对于关闭文件来说:

1)对于accept的返回值对应Socket这个文件,是有一个close方法的,如果打开一个文件后,是要记得去关闭文件的,如果不关闭,就可能会造成文件资源泄漏的情况,一个socket文件写完之后本应是要关闭的,但是咱们前面写的UDP程序是不可以关闭的,当时的socket是有生命周期的,都是要跟随整个程序的,当客户端不在工作时,进程都没了,PCB也没了,文件描述符就更没有了

2)咱们的TCP服务器有一个listenSocket,但是会有多个clientsocket,可以说每一个客户端,每一个请求都对应一个climentSocket,如果这个客户端断开链接了,对应的clientSocket也就需要销毁了

1)在我们写TCP服务器的时候,我们都针对了这里面的climentSocket(Socket创建的实例)关闭了一下,但是我们对于listenSocket(ServerSocket创建的实例)却没有进行关闭,直到服务器进行关闭;

2)同时在UDP的代码里面我们也没有针对DatagramSocket对象和DatagramPacket来进行关闭

catch (IOException e) {
            e.printStackTrace();
        }finally {
            clientSocket.close();
            //listenSocket.close();在这里面是不能进行关闭的
        }

1)我们关闭的目的是为了释放资源,释放资源的一个前提是这个资源已经不再进行使用了,对于咱们的UDP的程序和ServerSocket来说,这些Socket都是贯穿程序始终的,只要程序启动运行我们就要用到,什么时候咱们的服务器进程关闭,什么时候不用;但是咱们的服务器针对每一个客户端的Socket文件什么时候客户端断开链接了,啥时候就不会再进行使用了

2)这些实例什么时候不用?啥时候咱们的服务器进行关闭,啥时候不用;

3)咱们的这些资源最迟最迟也就会随着进程的退出一起进行释放了,进程才是操作系统分配资源的最小单位,那么这个进程曾经进行申请的资源也就没有了;

4)但是咱们的climentSocket的生命周期是很短的,针对咱们的每一个客户端程序,都要进行创建一个climentSocket,当我们的对应的客户端断开连接之后,咱们的服务器的对应的客户端的climentSocket对象也就永远不会再进行使用了,我们就需要关闭文件释放资源,咱们的climentSocket对象有很多,每一个客户端都对应一个Socket对象,我们就需要保证,每一次进行处理完成的连接就必须进行释放;

下面的这些代码我们本质上读的是Socket文件:

我们通过调用Socket对象里面的getInputStream()方法,我们就可以进行获取到对应的流对象

全双工双向通信,我们既可以读,也是可以写的; 

 下面是客户端的代码:

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

 class Request{
    int serverport=0;
    String serverIp=null;
    Socket socket=null;
    public  Request(String serverIp,int serverport)throws IOException {
        this.serverIp=serverIp;
        this.serverport=serverport;
        this.socket=new Socket(serverIp,serverport);
    }
    public void start()throws IOException {
//1从键盘也就是控制台上读取请求
        Scanner scan = new Scanner(System.in);
try (InputStream inputStream = socket.getInputStream();
  OutputStream outputStream = socket.getOutputStream()) {
        while (true) {
            System.out.println("请输入你的请求内容");
            System.out.println("->");
            String request = scan.next();
            if (request.equals("goodbye")) {
                System.out.println("即将退出客户端");
                break;
            }
 //2把这个从键盘读取的内容,构造请求发给服务器
                PrintStream printStream=new PrintStream(outputStream);
                printStream.println(request);
//在这里我们怀疑println不是把数据发送到服务器中了,而是放到缓冲区里面了,我们刷新一下缓冲区,强制进行发送
                   printStream.flush();//如果不刷新,服务器无法及时收到数据
 //3我们从服务器那边读取响应,并进行解析
                Scanner scanreturn=new Scanner(inputStream);
                String response=scanreturn.next();
//4我们把结果显示在控制台上面
                String string=String.format("请求是 %s,回应是 %s",request,response);
                System.out.println(string);
                   }
            }catch(IOException e)
            {
                e.printStackTrace();
            }
     }
    public static void main(String[] args) throws IOException {
    //此处构建request对象的时候就相当于是和服务器建立连接
    Request request=new Request("127.0.0.1",9090);
    request.start();
    }
}

1)让Socket创建的同时,就与服务器建立了链接,相当于拨电话的操作,这个操作对标于服务器中的climentSocket.accept接电话操作,我们客户端的IP地址就是本机的IP地址,咱们的端口号是由系统自动进行分配的;
2)我们在这里面传入的IP和端口号的含义表示的是,不是自己进行绑定,而是表示和这个IP端口建立连接 

下面是服务端的代码:

我们一般是先启动服务器,再启动客户端;

1)当启动服务器的时候,此时的服务器就会阻塞到accept这里,此时还没和客户端建立连接;

2)客户端在这里再启动,就会调用Socket的构造方法,在构造方法中就会和服务器建立连接;

3)当客户端和服务器建立连接后,accept方法就会返回;

4)procession方法中,就会进入循环,尝试读取数据,就会阻塞在if语句里面的next方法中,当客户端真正发送请求为止;

5)此时的客户端也进入到start中的next方法中,也会阻塞在next方法中,等待用户在键盘上输入一个字符串(标准键盘输入)

6)当下的状态是客户端阻塞在用户往建盘中输入数据,服务器阻塞在等待客户端的请求if语句里面的hasNext;

7)接下来,用户输入数据的时候,此时客户端的阻塞就结束了,然后会发送一个数据给服务器(Outputstream),同时服务器就会从等待读取客户端请求的状态中恢复过来(结束if语句里面的阻塞),执行后面的procession逻辑;

7)当客户端发送完请求之后,又会在第二个传递InputStream里面的scanner的next方法里面进行阻塞,等待服务器的响应的数据;

8)服务器响应并把数据发送回去后,客户端才会在传递scanner的next的等待中返回,最后再来进行打印相关信息;

咱们的服务器要想拿到客户端的端口号就要通过climentSocket(Socket创建的实例)

IP地址:climentSocket.getInetAddress().toString();

端口号:climentSocket.getPort();

9)我们要使用printWriter中的println方法而不是write();

public class TCPServer {
    ServerSocket listenSocket=null;
    public TCPServer(int serverPort) throws IOException {
        listenSocket=new ServerSocket(serverPort);
    }
    public void start() throws IOException {
        System.out.println("TCP服务器开始进行启动");
        while(true)
        {
            Socket clientSocket= listenSocket.accept();
            procession(clientSocket);
        }
    }

    private void procession(Socket clientSocket) throws IOException {
        System.out.printf("我们这一次客户端请求的IP地址是%s 端口号是%d",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        try(InputStream inputStream=clientSocket.getInputStream()){
            try(OutputStream outputStream= clientSocket.getOutputStream()){
//我们来进行循环处理请求,来进行处理响应,我们的一台主机是要给服务器发送多次请求的
                Scanner scanner=new Scanner(inputStream);
                while(true)
                {
                   if(!scanner.hasNext())
                   {
//客户端断开连接的代码
                       System.out.printf("客户端断开连接%s %d",clientSocket.getInetAddress(),clientSocket.getPort());
                       break;
                   }
                }
我们在这里面使用Scanner是更方便的,如果说我们不使用Scanner就需要进行使用原生的inputStream中的read方法就可以了,只不过我们需要创建一个字节数组,然后使用stringbulider来进行拼接
// 1.读取请求并进行解析,读取Socket网卡
                String request= scanner.next();
//2.根据请求计算并执行逻辑,我们创建process方法执行
                String response=process(request);
//3.帮我们写的逻辑返回给客户端,为了方便起见,我们直接使用PrintWriter来进行对OutputStream来进行包裹一下
                PrintWriter printWriter=new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();
//4打印信息
System.out.printf("[客户端的端口号是%d 客户端的IP地址是%s],请求数据是%s,响应数据是%s",clientSocket.getPort(),clientSocket.getInetAddress(),request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            clientSocket.close();
            listenSocket.close();
        }
    }
    private String process(String request) {
        return request+"我爱你";
    }
    public static void main(String[] args) throws IOException {
        TCPServer server=new TCPServer(9099);
        server.start();
    }
}

1)针对写操作,我们要进行刷新缓冲区,如果没有这个刷新,那么我们的客户端时不能第一时间获取到这个响应结果

2)对于每一次请求,都对应着一个Socket,我们都要创建一个procession方法来进行处理,我们接下来就来处理请求和响应
3)这里面的针对TCP Socket的文件读写是和文件读写是一模一样的,我们在里面主要是对socket文件来进行读和写,TCP和UDP是全双工,我们既可以读Socket文件,也可以写Socket文件

1)对于咱们的UDP的DatagramSocket来说,咱们构造方法指定的端口,就是表示自己想要绑定的端口;

2)对于咱们的TCP的ServerSocket来说,构造方法指定的端口,也是表示自己要进行绑定哪一个端口;

3)对于咱们的TCP的Socket来说,对于构造方法指定的端口,表示要进行连接的服务器的IP地址和端口号,创建实例,就相当于是进行了三次握手,就和服务器建立了连接;

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

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

相关文章

Java学习之抽象模板模式

目录 一、基本介绍 二、模板设计模式能解决的问题 三、最佳实践 一、AA类 二、BB类 三、main方法实现 四、提取相同语句 五、建立继承关系 父类-Template 子类-AA类 子类-BB类 六、运行中的动态绑定机制 一、基本介绍 抽象类体现的就是一种模板模式的设计&#xff…

【Git】概述

目录 1.1 是什么 介绍 历史时间轴 版本控制工具 1.2 能干嘛 作用 Git工作机制 代码托管中心 集中式版本控制系统 分布式版本控制系统 1.3 去哪下 命令行工具:Git for windows 操作系统中可视化工具:TortoiseGit(了解) GitHub网站 1.1 是什…

带你走进Java8新特性Stream流的小世界

目录 一. 什么是流(Stream) 1.1 流的定义 1.2 流的特点 1.3 操作流 1.4 创建流 二. 流的中间操作 2.1 流的筛选与切片 2.1.1 filter 2.1.2 limit 2.1.3 skip 2.1.4 distinct 2.2 流的映射 2.2.1 map 2.2.2 flatMap 2.3 流的排序 2.3.1 s…

智公网:2023年教师编必背30考点

1、制度化教育阶段开始于:近代。 2、各国的学校教育系统基本形成于:19世纪末。 3、现在世界上大多数国家的义务教育年限在:9年或9年以上。 4、“不愤不启,不悱不发”启发教学法的最早倡导者是:孔子。 5、“建国君民…

【Spring】Spring 6 新特性一一HTTP Interface

简介 Spring 6 的第一个 GA 版本发布了,其中带来了一个新的特性——HTTP Interface。 这个新特性,可以让开发者将 HTTP 服务,定义成一个包含特定注解标记的方法的 Java 接口,然后通过对接口方法的调用,完成 HTTP 请求…

硬盘损坏数据恢复怎么操作?恢复数据的常用方法

硬盘一般固定在电脑里面的存储装置,里面保存着我们大量的数据。随着电脑的使用越加广泛,有时不免出现一些问题,比如硬盘在使用过程中出现数据错误,或者是硬盘的内部零件出现故障。出现这些问题,硬盘损坏数据恢复怎么操…

Redis实现UV统计 | 黑马点评

一、HyperLogLog 1、为什么用HyperLogLog 先介绍两个概念: UV:全称 Unique Visitor,也叫独立访客量,是指通过互联网访问、浏览这个网页的自然人、1 天内同一个用户多次访问该网站,只记录 1 次。PV:全称 …

车载以太网 - SomeIP测试专栏 - 总纲

关于车载以太网中的SomeIP在网上也逐渐有越来越多的资料,讲的也是非常好;但是个人认为讲的泛,很难让初学者或者初入门者真正了解SomeIP到底是一个什么东西,以及它究竟在车载上有什么作用,本专栏会由浅入深的讲解SomeIP整个协议内容规范,并且对Tc8中SomeIP相关的协议测试用…

实习日记!

目录 http://localhost:5789实习第三天 接下来几天的target 实习第四天 Git的操作 实习第五天 12月5日-Mon 12月6日 12月9日 12月12日 12月15日 useState() hook 12月16日 useEffect() hook async 函数 异步编程 回调函数 12月17日 C#中的?&#x…

postgresql源码学习(54)—— HotStandby从库必须设置大于等于主库的参数

新的一篇本来计划研究lazy_scan_heap函数,但过于复杂还没研究出来… 下午做题遇到一个这样的问题,之前没太关注过,打算学习学习,避免主从配置踩坑。 题干搜一搜,没搜出啥有用的玩意…渣翻成英文搜一搜,搜出…

windows搭建go语言开发环境,IDEA安装go插件并运行Hello world代码

2023年1月27日1.Windows上安装Go语言开发包参考链接:http://c.biancheng.net/view/3992.html1.1.下载Go语言开发包可以在Go语言官网 (https://golang.google.cn/dl/) 下载Windows 系统下的Go语言开发包,如下图所示。这里我们下载的…

学习Docker就应该掌握的dockerfile语法与指令

在日常的工作中,常常需要制作自己的项目的镜像,一般通过以下两种方式制作镜像:Docker commit、Dockerfile。Docker commitDocker commit一般用做从一个运行状态的容器来创建一个新的镜像。定制镜像应该使用Dockerfile来完成。docker commit 容…

Git场景分析

当前的开发环境如下,我们每个人都对这个项目已经开发一段时间,接下来我们要切换成团队开发模式。 也就是我们由一个团队来完成这个项目实战的内容。团队有组长和若干组员组成(组长就是开发中的项目经理)。 练习场景如下: 1.由组长&#xff0…

LeetCode[1202]交换字符串中的元素

难度:中等题目:给你一个字符串 s,以及该字符串中的一些「索引对」数组 pairs,其中 pairs[i] [a, b]表示字符串中的两个索引(编号从 0 开始)。你可以 任意多次交换 在 pairs中任意一对索引处的字符。返回在…

java递归问题——汉诺塔

目录 🌲🌲什么是汉诺塔? 🐰 当只有1个圆盘的时候: 🐰 当只有2个圆盘的时候: 🐰 当只有3个圆盘的时候: 🌲🌲汉诺塔代码 🐰思路 &am…

电脑安全模式怎么进?3种方式教会你!

安全模式经常是电脑死机的时候,我们会选择的一种方式。因为安全模式可以帮助我们修复电脑系统里面的一些错误,电脑安全模式怎么进?其实很简单,主要有以下3种方式,你可以根据你的需要来选择其中一种! 操作环…

剑指 Offer 第7天(中午睡起来都十二点了,今天摆了吧)

目录 剑指 Offer 26. 树的子结构 剑指 Offer 27. 二叉树的镜像 剑指 Offer 28. 对称的二叉树 剑指 Offer 26. 树的子结构 输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构) B是A的子结构, 即 A中有出现和B相同的结构和节点…

极光厂商通道集成指南

小米集成指南 1、使用JCenter自动化集成步骤 确认AndroidStudio的Project根目录的主gradle中配置了jcenter支持。(新建project默认配置就支持) buildscript { repositories { jcenter() } } allprojects {repositories { jcenter() } } 在应用module的gr…

ffmpeg为mkv封装格式的音视频文件添加内挂字幕

现在好莱坞的电影,都是全球看,一个地区的人看电影时,电影屏幕上应该展示对应的本地区语言字幕。故电影画面在不同的地区,需要配置不同的语言字幕。故视频画面里面的字幕应该可以拆出来,不能像老版三国演义,…

数据中心Spine/Leaf+VXLAN的结构

大家过年好,我是技福的小咖老师。今天我们继续聊聊网络架构。 随着业务系统对IT基础设备灵活度要求的不断提升,云计算、大数据以及虚拟化等技术在新型数据中心的建设中发挥着重要作用。如何更好地满足数据中心计算资源灵活调配以及服务扩展,…