精华内容
下载资源
问答
  • 1,父进程listen,创建pipe(下面所有父子进程之间的通信都用该pipe) 2,父进程预fork n个子进程 3,各个子进程accept(listenfd),即所有子进程竞争accept请求。由于listenfd是在fork之前就有的,所以所有子进程都...
  • 连接不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。 什么是UDP? UDP(用户数据报协议)是一个简单的面向数据报的传输协议。提供的是非面向连接的、不可靠的数据流传输。 ...

    什么是Socket?

    Socket(套接字)可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑上的概念。

    什么是TCP?

    TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP旨在适应支持多网络应用的分层协议层次结构。
    连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。

    什么是UDP?

    UDP(用户数据报协议)是一个简单的面向数据报的传输层协议。提供的是非面向连接的、不可靠的数据流传输。

    TCP与UDP的区别与联系

    区别:

    1、 基于连接与无连接。
    2、 TCP 要求系统资源较多,UDP较少。
    3、 UDP 程序结构较简单 。
    4、 TCP 保证数据顺序,UDP不保证 。
    5、 TCP 保证数据正确性,UDP可能丢包。
    6、 TCP 是面向连接的传输控制协议,而UDP提供了无链接的

    实例:

    1.客服端向服务端上传一个图片(TDP)
    服务端Serve:
    /**
     * @author bingo
     * @ClassName TCPServe.java
     * @Description TODO
     * @createTime 2020年09月28日
     */
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class TCPServe {
        public static void main(String[] args) throws IOException {
            ServerSocket ss = new ServerSocket(8888);             //new一个socket接kou
            Socket s = ss.accept();                                     //接收数据
            InputStream in = s.getInputStream();                       //写入
            FileOutputStream fos = null;
            // 1. 接收从客户端发送的数据
            in = s.getInputStream();
            fos = new FileOutputStream(new File("C:\\Users\\Administrator\\Desktop\\ImageTest\\3.jpg"));
            byte[] buffer = new byte[1024];
            int length = -1;
            while ((length = in.read(buffer)) != -1) {
                // 2. 将数据存储到本机
                fos.write(buffer, 0, length);
                fos.flush();
            }
                System.out.println("接收完成!请在C:\\Users\\Administrator\\Desktop\\ImageTest\\3.jpg下查看");
                s.close();                                                  //停止接收
                ss.close();                                                  //接口关闭
    
        }
    }
    
    
    客戶端Client:
    /**
     * @author bingo
     * @ClassName TCPClient.java
     * @Description TODO
     * @createTime 2020年09月28日
     */
    import java.io.*;
    import java.net.Socket;
    
    public class TCPClient {
        public static void main(String[] args) throws IOException {
            Socket s=new Socket("127.0.0.1",8888);
         OutputStream out =s.getOutputStream();
    
            // 1. 创建一个文件,文件中存储一张图片
            FileInputStream fis = null;
            BufferedInputStream bis = null;
            // 2. 将文件封装成输入流
            fis = new FileInputStream(new File("D:\\1.jpg"));
            bis = new BufferedInputStream(fis);
            // 3. 读取文件,发送给服务器端
            byte[] buffer = new byte[1024];
            int length = -1;
            //判断读取完成
            while ((length = bis.read(buffer)) != -1) {
                out.write(buffer, 0, length);
                out.flush();
            }
                System.out.println("发送成功!");
                s.close();
    
        }
    }
    
    
    2.客服端向服务端发送用户名请求登录,服务端通过验证,返回"欢迎光临",未通过,用户不存在

    传输数据和本地数据比较,难点在于如何创建本地数据,new一个user对象

    服务端Serve:
    /**
     * @author bingo
     * @ClassName TCPServe.java
     * @Description TODO
     * @createTime 2020年09月28日
     */
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.List;
    
    public class TCPServe {
        public static void main(String[] args) throws IOException {
            // 1.创建服务器端Socket对象
            ServerSocket ss=new ServerSocket(8888);
    
            // 2.监听,返回客户端的Socket对象
            Socket s=ss.accept();
    
            // 3.得到收入 流,一次可接收一行的输入流
            BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream()));
    
            // 4.得到数据
            String username=br.readLine();
            String password=br.readLine();
            //封装成User对象
            User user=new User(username,password);
    
            // 5.简单校验
            boolean flag=false;//校验标记,初始为false
            //判断,如果验证成功,改变标准为true否则标记不变
    
            //得到所有User的集合
            List<User> users=UserDB.getUser();
            //判断集合中是否包含客服端发过来的user对象 使用contains放法 注意重写equals方法
            if(users.contains(user))
                flag=true;
    
            // 6.得到输出流
            PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
    
           // 7.发送校验结果
            if(flag){
                pw.println(username+" "+"欢迎光临");
            }
            else{
                pw.println("用户不存在");
            }
            // 8.关闭连接
            s.close();
            ss.close();
    
        }
    }
    
    
    客戶端Client:
    /**
     * @author bingo
     * @ClassName TCPClient.java
     * @Description TODO
     * @createTime 2020年09月28日
     */
    import java.io.*;
    import java.net.InetAddress;
    import java.net.Socket;
    
    public class TCPClient {
        public static void main(String[] args) throws IOException {
            Socket s=new Socket(InetAddress.getLocalHost(),8888);
            // 2.得到用户输入的用户名和密码
            // 键盘输入两行的内容
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("请输入用户名:");
            String username = br.readLine();
            System.out.println("请输入密码:");
            String password = br.readLine();
    
            // 3.得到输出流
            // PrintWriter可实现输入一行自动换行
            PrintWriter out = new PrintWriter(s.getOutputStream(), true);
    
            // 4.发送数据
            out.println(username);
            out.println(password);
    
            // 5.的到输入流
            BufferedReader sbr = new BufferedReader(new InputStreamReader(s.getInputStream()));
    
            // 6.得到服务器返回结果并输出
            String result = sbr.readLine();
            System.out.println(result);
    
            // 7.关闭资源
            s.close();
        }
    }
    
    
    User对象:
    /**
     * @author bingo
     * @ClassName UserDB.java
     * @Description TODO
     * @createTime 2020年09月28日
     */
    import java.util.ArrayList;
    import java.util.List;
    
    public class UserDemo {
        //创建静态user对象users
        private static List<User> users=new ArrayList<User>();
    
        static{
            //add()添加用户
            users.add(new User("zhangbin","123456"));
            users.add(new User("lisi","123456"));
            users.add(new User("wangwu","123456"));
        }
        public static List<User> getUser(){
            return users;
        }
    }
    
    /**
     * User对象类
     */
    class User {
        private String username;
        private String password;
    
        public User(){}
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            User other = (User) obj;
            if (password == null) {
                if (other.password != null)
                    return false;
            } else if (!password.equals(other.password))
                return false;
            if (username == null) {
                if (other.username != null)
                    return false;
            }
            else if (!username.equals(other.username))
                return false;
            return true;
        }
        /*
         *含参构造函数
         */
        public User(String username, String password) {
            super();
            this.username = username;
            this.password = password;
        }
        /*
         *set,get方法
         */
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
    }
    
    运行示例:

    在这里插入图片描述

    展开全文
  •   网络层提供的主机与主机之间的通信,而运输层具体了不同主机中进程和进程之间的通信。另外在网络层及以下传输的可靠性无法保证,但运输层实现了可靠传输。   可靠传输是如何实现的?   可靠传输的实现是...

      我认为应该带着问题进入运输层。做了通信的最高层,运输层和网络层有什么区别呢?都说运输层实现了可靠传输,那可靠传输是如何实现的呢?当信道中发生拥塞的时候,在运输层是怎么进行处理的呢?

      运输层和网络层的区别:

      网络层提供的主机与主机之间的通信,而运输层具体到了不同主机中进程和进程之间的通信。另外在网络层及以下传输的可靠性无法保证,但运输层实现了可靠传输。

      可靠传输是如何实现的?

      可靠传输的实现是依靠的确认机制,确认机制在稍后介绍的停等协议中都有所体现。同时为了提高效率,也慢慢开发出了连续的AQR等协议。

      运输层是如何处理阻塞的?

      阻塞发生的原因的是处理速度赶不上发送速度,导致接受窗口慢载甚至溢出丢失。通常处理速度无法更改,更为普遍的方法是更改发送速度。
      下面进行知识点的梳理,相信读完之后能够更深刻的理解这几个问题。
      运输层的功能是为应用层提供服务。从运输层的角度看,通信的真正端点是进程而不是主机。这个进程就是指应用进程。还有提运输层的时候总说上端口这个词。那什么是端口呢?端口其实分两种:软件端口和硬件端口(路由器或者交换机上的)。软件端口指在协议栈层间的抽象的协议端口。而硬件端口是不同硬件设备进行交互的接口。暂时不提硬件,软件端口和进程有什么关系呢?软件端口是找到具体进程的方法
      运输层主要包含了两类协议:UDP和TCP,下面对这两者进行介绍:

      用户数据报协议UDP(User Datagram Protocol)

      首先说明UDP是面向无连接的,也就是说明UDP是不可靠的。虽然UDP是不可靠的但它仍是一种有效地工作方式。那不可靠的UDP和网络层相比有什么区别呢?
      UDP提供了额外的一些功能:复用和分用,差错检测
      复用和分用:主机间相互交流的时候有可能不止一个进程,为每一个进程通信建立一个通信过程不太现实。于是使用复用技术把所有进程都统一在一起进行传输,等到接收端的运输层再使用分用技术将各个进程分开分别进行交付。
      差错检测:记得网络层也进行了差错检测,那UDP的差错检测和网络层的有什么不同吗?其实网络层的差错检测只是检查了数据报的头部,而没有查具体的数据。但运输层实现了数据部分的差错检测。而且UDP对于错误数据的处理方式就是直接扔。
      UDP还有这么几个特点:
        1) UDP是面向报文的:啥叫面向报文呢?可以理解成不管报文长短,来了就发送。只要应用层给过来数据,不管大小,加上首部后就会给到应用层。
        2) UDP没有拥塞控制:
    没有拥塞控制就代表UDP的发送方不管信道中什么样子,发送速率保持恒定。这有好有坏。后面会介绍基于此特性的应用。
        3) UDP支持一对一,一对多。多对一和多对多的交互通信:
        4) UDP的首部开销小:这个因为UDP的首部固定8个字节,而TCP是20个字节。
      那UDP有什么应用呢?
      流媒体视频播放:UDP提供不可靠传输,丢包不可避免。但在视频文件传输过程中丢失部分包也不要紧。只要保证流畅就可以,而UDP正好满足时延小这个特性。
      QQ:我是真的没想到QQ是用UDP来实现的,准确来说是UDP为主,TCP为辅。不过腾讯也怕聊天丢包,也在上层做了一些处理。但QQ用的人实在太多,并发量太大只有UDP的才满足条件。感兴趣的可以看看:https://blog.csdn.net/qq_42514453/article/details/85574949

      传输控制协议TCP(Translation Control Protocol)

      其实TCP没有什么太多东西可以说的,特点就是可靠传输,会在后面介绍。但TCP是面向字节流的:什么叫面向字节流的呢?可以这么理解:TCP会根据应用层传下来的数据大小做一个决策。如果太大会将数据切分,如果太小会暂放缓存区,等下次的数据一起到达一定规模后发送。
      TCP还有一个点是套接字,又叫socket。Socket是一个二元组<IP地址,端口号>。通过socket可以实现通信,我们的课程设计就是使用socket完成一个聊天室(用的语言是python),我把github代码链接放在这里有需要的可以去看看:(https://github.com/Max-luo-song/Chat-room)。Socket总体分为两部分服务器端和用户端,之间建立连接的过程可以用这张图来表示:
    在这里插入图片描述

      可靠的传输原理

      传输层的主要内容还是在这里,按照教材介绍两种:停等协议和连续ARQ协议

      停等协议

      停等协议的思想是发送方将数据发送出去,接受方接受后进行确认,确认无误后反发送一个确认信息。发送方一直等着,直到收到确认信息,认为这是一次成功的发送。于是接着按照上面的思路发送后面的数据。
      停等协议有三种情况,这里用图表示特别清楚,我就用图表示了:
      1) 无差错情况:
    在这里插入图片描述
      2) 出现差错:
    在这里插入图片描述
      这个解释一下,当接收方确认发送的数据有错误时,不做任何处理,丢弃报文。而发送方有一个超时重传的计时器,如果超过计时器时间,仍没有接受到确认信息。认为发送失败,再次发送同样的数据,直至成功。这种计时器的思想在可靠传输中多次得到应用。
      3) 确认丢失和确认迟到:
    在这里插入图片描述
      其实确认丢失和发送错误对于发送方来说是一样的,都是没有收到确认信息,按时重传。但接收方在确认丢失的时候已经收到了一份数据,对于再次重传的相同数据选择直接丢失。确认迟到是轮到发送方对确认数据进行丢弃。
      从上面的描述应该基本了解了停等协议了吧,但它有个缺点太明显了:慢,太慢了,效率太低了,一个一个的,啥时候能够完事儿?效率低换个专业点儿的名词就是信道利用率太低。对此进行改进:流水线模式
      流水线模式就是同时发送n组,n组分别确认。这样大大的提高了信道利用率了。但思考下流水线模式有没有什么问题?如果一口气不停的发送,也不管是否接收到是否会出现发了第100个,但第一个还没确定的情况。为了对其进行规范化,提出了连续ARQ协议。

      连续ARQ协议:

      连续ARQ协议结合停等协议和流水线思路,但做了一点儿限制。限制发送窗口的大小,只有接收到窗口末端的数据确认时才能向前移动。有没有觉得一个包一次确认有点麻烦?在连续ARQ协议中对这个进行了一个优化:累计确认。累计确认指对按序到达的最后一个分组发送确认。累计确认优点是容易实现,确认丢失也不必重传。缺点是不能向发送方反映出接收方已经正确收到的所有分组的信息。在累计确认的背景下,接收方是按序接受,乱序到达也丢弃。这样一种情况,发了五个,但只收到第二个确认信息,发送方应当从第三个开始重发,此时窗口已经移动就需要回退,这个动作就叫Go-back-N。所以通信质量不好的时候,连续发送回退,连续ARQ会有负面影响的
      再想一下,这个连续ARQ还有什么地方可以改善的吗?还按照上面的例子,如果第一遍第四个正常收到。但Go-back-N后,第四个又要再发一遍。这是一种浪费啊!于是提出选择确认SACK,作用就是防止发送方重复发送已成功接受的,给发送方信息,那些成功到达。肯定是方便,但实现起来要在接收方加入缓存,也算多加了硬件条件。
      最开始停等协议里有一个超时重传时间,那这个时间具体是怎么确定的呢?这个实在是不懂了,有需要的教材P225页会给你答案的。
      TCP有两种控制:流量控制和拥塞控制。课上的时候重点的介绍了拥塞控制。对于流量控制我简单的说说为啥要进行流量控制:当大量数据包涌入网络时,路由器的处理速度有限,来不及处理的要放到缓存中排队(排队方式是FIFO)。缓存也是有限的,满了就要丢弃数据包,这是不希望看到的(如果对传输质量质量要求比较高的地方)。所以控制发送速率成了一种手段,也就是流量控制。所以流量控制的核心是控制接收方
      拥塞控制
      有的地方其实不是特别区分流量控制和拥塞控制这两个概念。它们的目的是共同的:为了网络传输的顺利进行。如果非要区分可以这样,有两点不同:
      流量控制的核心控制接收方,拥塞控制的核心是控制发送方
      流量控制是局部的,拥塞控制是全局的。(稍后解释,为什么这么说)。
      拥塞控制包括四个过程(就是ADMI算法(Additive Increase Multiplicative Decrease)):
      1) 慢开始:开始的时候不知道具体窗口,起点设置的比较低。但增长速率比较快,每轮倍数增长。
      2) 拥塞避免:已经发现一些问题,猜测有拥塞的可能。开始线性增长,每轮加1.
      3) 快重传:当发现数据丢失时,立即重传。(更像是一种思想)
      4) 快恢复:针对于丢失后的重复确认(累计确认思想),达到三次认为出现拥塞,调整发送窗口及上限值。
    整个拥塞控制过程可以用这样两张图来表示:
    在这里插入图片描述
    在这里插入图片描述
      Ssthresh代表门阀值(进入拥塞避免阶段的临界值),cwnd代表发送窗口的当前大小。、
    说了这么多,那判断网络拥塞的具体标志是什么呢?超时就是发生了网络拥塞
      下面解释上面的问题,为什么流量控制是局部的,拥塞控制是全局的
      流量控制中的局部指的是端到到端的,比如上面我们举得例子都是在一个发送过程中进行的。至于拥塞控制是全局的获取可以这样解读:拥塞控制本身面对的就是全网络的负荷,本身就是一个全局变量。这个里面给了一个相对清晰的解释:http://wenda.tianya.cn/question/0cbf85dd193d4aedlink

      TCP运输连接管理

      前面说到TCP是面向连接的,那么自然有建立连接和释放连接两个过程了。不过和普通的两报文握手连接不同,TCP建立连接使用三次报文握手,释放连接使用四次报文握手。为什么?
      先解释下握手:即TCP发送数据报文进行通信的过程(单向)

      TCP三次报文握手建立连接

      首先看看三次报文都是那三次,看个图吧:
    在这里插入图片描述
      其次,为什么要三次握手呢,A在一次交互后为什么要再发送一次呢?
      这个主要是为了防止失效的连接请求再次占据B的资源。如果A第一次发送请求,B没有收到,那么A超时后会再次发送请求,这次B收到了并回复。但第一次A发送的并没有丢,而是迟到了。B收到了,以为A又请求一次,那又要分配资源给这次的连接。通过在最后加上第三次握手,如果这种情况发生,B收到第一次发送的第一条报文,但迟迟收不到第三条,就了解情况啦,及时进行释放~

      TCP四次报文握手释放连接

      先看看哪四次握手释放连接
    在这里插入图片描述
      其次,为什么要四次握手呢?
      释放连接的四次各有作用:A主动进行关闭,发送提示信息给B(第一次握手)。B收到信息后回复(第二次握手)。但B回复的时候数据可能还没发送完,有的A发的B还没收到,需要等待一个FIN-WAIT-2时间。等到B收到全部数据后,发送信息给A(第三次握手),告诉收集全了。最后A在对B最后的信息进行确认(第四次握手),等待2MSL的原因就是确保B收到了第四次握手的信息(否则B会主动发送没收到第四次握手,如果在2MSL时间段内监听到,则再次发送第四次信息),其次也可以等待信道中所有有关于本次通信的信息全部消失,以免影响后续的通信。

    展开全文
  • 我们知道网络,可以实现两个主机之间的通信。但是这并不具体,因为,真正进行通信的实体是在主机中的进程,是一个主机中的一个进程与另外一个主机中的一个进程在交换数据。IP协议虽然能把数据报文送目的主机,...

    作者:小书go https://blog.csdn.net/qzcsu/article/details/72861891

    背景描述

    我们知道网络层,可以实现两个主机之间的通信。但是这并不具体,因为,真正进行通信的实体是在主机中的进程,是一个主机中的一个进程与另外一个主机中的一个进程在交换数据。IP协议虽然能把数据报文送到目的主机,但是并没有交付给主机的具体应用进程。而端到端的通信才应该是应用进程之间的通信。

    UDP,在传送数据前不需要先建立连接,远地的主机在收到UDP报文后也不需要给出任何确认。虽然UDP不提供可靠交付,但是正是因为这样,省去和很多的开销,使得它的速度比较快,比如一些对实时性要求较高的服务,就常常使用的是UDP。对应的应用层的协议主要有 DNS,TFTP,DHCP,SNMP,NFS 等。

    TCP,提供面向连接的服务,在传送数据之前必须先建立连接,数据传送完成后要释放连接。因此TCP是一种可靠的的运输服务,但是正因为这样,不可避免的增加了许多的开销,比如确认,流量控制等。对应的应用层的协议主要有 SMTP,TELNET,HTTP,FTP 等。

    ###

    常用的熟知端口号

    TCP的概述

    TCP把连接作为最基本的对象,每一条TCP连接都有两个端点,这种断点我们叫作套接字(socket),它的定义为端口号拼接到IP地址即构成了套接字,例如,若IP地址为192.3.4.16 而端口号为80,那么得到的套接字为192.3.4.16:80。

    TCP报文首部

    源端口和目的端口,各占2个字节,分别写入源端口和目的端口;

    序号,占4个字节,TCP连接中传送的字节流中的每个字节都按顺序编号。例如,一段报文的序号字段值是 301 ,而携带的数据共有100字段,显然下一个报文段(如果还有的话)的数据序号应该从401开始;

    确认号,占4个字节,是期望收到对方下一个报文的第一个数据字节的序号。例如,B收到了A发送过来的报文,其序列号字段是501,而数据长度是200字节,这表明B正确的收到了A发送的到序号700为止的数据。因此,B期望收到A的下一个数据序号是701,于是B在发送给A的确认报文段中把确认号置为701;

    数据偏移,占4位,它指出TCP报文的数据距离TCP报文段的起始处有多远;

    保留,占6位,保留今后使用,但目前应都位0;

    紧急URG,当URG=1,表明紧急指针字段有效。告诉系统此报文段中有紧急数据;

    确认ACK,仅当ACK=1时,确认号字段才有效。TCP规定,在连接建立后所有报文的传输都必须把ACK置1;

    推送PSH,当两个应用进程进行交互式通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应,这时候就将PSH=1;

    复位RST,当RST=1,表明TCP连接中出现严重差错,必须释放连接,然后再重新建立连接;

    同步SYN,在连接建立时用来同步序号。当SYN=1,ACK=0,表明是连接请求报文,若同意连接,则响应报文中应该使SYN=1,ACK=1;

    终止FIN,用来释放连接。当FIN=1,表明此报文的发送方的数据已经发送完毕,并且要求释放;

    窗口,占2字节,指的是通知接收方,发送本报文你需要有多大的空间来接受;

    检验和,占2字节,校验首部和数据这两部分;

    紧急指针,占2字节,指出本报文段中的紧急数据的字节数;

    选项,长度可变,定义一些其他的可选的参数。

    TCP连接的建立(三次握手)

    最开始的时候客户端和服务器都是处于CLOSED状态。主动打开连接的为客户端,被动打开连接的是服务器。

    TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;

    TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。

    TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。

    TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。

    当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。

    为什么TCP客户端最后还要发送一次确认呢?

    一句话,主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。

    如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。

    如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。关注Java技术栈微信公众号,在后台回复关键字:Java,可以获取更多栈长整理的Java技术干货。

    TCP连接的释放(四次挥手)

    数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,然后客户端主动关闭,服务器被动关闭。

    客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。

    服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

    客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

    服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

    客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。

    服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。关注Java技术栈微信公众号,在后台回复关键字:Java,可以获取更多栈长整理的Java技术干货。

    为什么客户端最后还要等待2MSL?

    MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。

    第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。

    第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。

    为什么建立连接是三次握手,关闭连接确是四次挥手呢?

    建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。

    而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。

    如果已经建立了连接,但是客户端突然出现故障了怎么办?

    TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

    关注Java技术栈微信公众号,在后台回复关键字:_Java_,可以获取更多栈长整理的Java技术干货。

    推荐去我的博客阅读更多:

    1.Java JVM、集合、多线程、新特性系列教程

    2.Spring MVC、Spring Boot、Spring Cloud 系列教程

    3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

    4.Java、后端、架构、阿里巴巴等大厂最新面试题

    觉得不错,别忘了点赞+转发哦!

    展开全文
  • nodejs 实现套接字服务

    2019-09-28 02:12:09
    nodejs实现套接字服务 一 什么是套接字 1.套接字允许一个进程他通过一个IP地址和端口与另一个进程通信,当你实现对运行在同一台服务器上的两个不同...2.套接字位于HTTP下面并提供服务器之间的点对点通信。套...
    nodejs实现套接字服务
     
     
    一 什么是套接字
    1.套接字允许一个进程他通过一个IP地址和端口与另一个进程通信,当你实现对运行在同一台服务器上的两个不同进程的进程间通信或访问一个完全不同的服务器上运行的服务时,套接字很有用。node提供的net模块,允许你既创建套接字服务器又创建可以连接到套接字服务器的客户端。
    2.套接字位于HTTP层下面并提供服务器之间的点对点通信。套接字使用套接字地址来工作,这是IP地址和端口的组合。在套接字连接中,有两种类型的点:一类是服务器,它监听连接;一类是客户端,它打开一个到服务器的连接。服务器和客户端都需要一个唯一的IP地址和端口的组合。
    3.套接字是HTTP模块的底层结构,如果你不需要处理如get何post的web请求,只需要点对点的传输数据,那么使用套接字就可以就能为你提供一个轻量级的解决方案和更多的控制。                                                                                                                                                                                                                                                                                                                                                                                                                                    二 net.Socket对象
    1.Socket对象同时在套接字服务器和客户端套接字上创建,并允许数据在它们之间来回写入和读取。在套接字客户端,当你调用net.connect()或net.createConnection()时,Socket对象在内部创建,这个对象是为了表示到服务器的套接字连接。使用Socket对象来监控连接,将数据发送到服务器并处理来自服务器的响应。在套接字服务器上,当客户端连接到服务器时,Socket对象被创建,并被传递到连接事件处理程序,这个对象是为了表示对客户端的套接字连接。      在NodeJS中有三种socket:TCP,UDP,Unix域套接字,主要介绍NodeJS中TCP的基本编程知识。
     
    2.创建一个Socket对象,可以使用以下方法:
    //第一种方法,通过一个options参数
    var SocketClient = net.connect(options, [connectionListener]);
    var SocketClient = net.createConnection(options, [connectionListener]);
     
    //第二种方法,通过接受port和host值作为直接的参数
    var SocketClient = net.connect(port, [host], [connectionListener]);
    var SocketClient = net.createConnection(port, [host], [connectionListener]);
     
    //第三种方法,通过接受指定文件系统位置的path参数,这个位置是一个Unix套接字在创建Socket对象时使用的。
    var SocketClient = net.connect(path, [connectionListener]);
    var SocketClient = net.createConnection(path, [connectionListener]);
    无论你使用哪种,都将返回一个Socket对象,唯一的区别在于接受的第一个参数,而最后一个参数都是当连接对服务器打开时执行的回调函数。而无论你使用net.connect还是net.createConncetion,它们的工作方式是完全相同的。
    那么至于第一个参数,指定的选项为:
    port:客户端应连接到的端口。此选项是必需的。
    host:客户端应该连接到的服务器的域名或IP地址。默认为localhost
    localAddress:客户端应该绑定的用于网络连接的本地IP地址。
    allowHalfOpen:一个布尔值,如果为true,则表示当套接字的另一端发送一个FIN数据包时,该套接字将不会自动发送一个FIN数据包,从而使Duplex流的一半保持开放。默认为false
     
    3.一旦Socket对象被创建,它就提供了在连接到服务器的生命周期中发出的几个事件,如下:
    connect:成功建立与服务器的连接时发出。回调函数不接受任何参数
    data:在套接字上收到数据时发出。如果没有数据时间处理程序被连接,那么数据可能会丢失。回调函数必须接受一个buffer对象作为参数,它包含从套接字读取的数据的块。
    end:当服务器通过发送一个FIN终止连接时发出。回调函数不接受任何参数
    timeout:由于不活动,因此到服务器的连接超时时发出。
    drain:当写缓冲区变为空时发出。你可以使用此事件截回被写入套接字中的数据流。回调函数不接受任何参数
    error:在套接字连接上发生错误时发出。回调函数应该接受错误的唯一参数。
    close:套接字已完全关闭时发出,它可能是由一个end()方法关闭的,或者因为发生错误而关闭。回调函数不接受任何参数
     
    5.Socket对象还提供了可以访问以获得该对象的信息的几个属性:
    bufferSize   当前已缓冲并等待写入套接字的流中的字节数
    remoteAddress   套接字连接到的远程服务器的IP地址
    remotePort    套接字连接到的远程服务器的端口
    localAddress   远程客户端用于套接字连接的本地IP地址
    localPort     远程客户端用于套接字连接的本地端口
    byteRead     由套接字读取的字节数
    byteWritten    由套接字写入的字节数
     
    三 net.Server对象
    可以使用net.Server对象创建一个TCP套接字服务器,并监听对它的连接,你将能够读取和写入数据。
    要创建一个服务器独享,使用net.createServer()方法:
    net.createServer([options],[connectListener])
    其中,options是一个对象,指定创建套接字Server对象时要使用的选项,如allowHalfOpen,可以使一半的Duplex流保持开放,默认为false。connectLlistener是connection事件的回调函数,它在接收到连接时被执行。
     
    举例:
    var net = require('net');
    var HOST = '127.0.0.1';
    var PORT = 6969;
     
    // 创建一个TCP服务器实例,调用listen函数开始监听指定端口
    // 传入net.createServer()的回调函数将作为”connection“事件的处理函数
    // 在每一个“connection”事件中,该回调函数接收到的socket对象是唯一的
    net.createServer(function(sock) {
     
        // 我们获得一个连接 - 该连接自动关联一个socket对象
        console.log('CONNECTED: ' + sock.remoteAddress + ':' + sock.remotePort);
     
        // 为这个socket实例添加一个"data"事件处理函数
        sock.on('data', function(data) {
            console.log('DATA ' + sock.remoteAddress + ': ' + data);
            // 回发该数据,客户端将收到来自服务端的数据
            sock.write('You said "' + data + '"');
        });
     
        // 为这个socket实例添加一个"close"事件处理函数
        sock.on('close', function(data) {
            console.log('CLOSED: ' +
                sock.remoteAddress + ' ' + sock.remotePort);
        });
     
    }).listen(PORT, HOST);
    console.log('Server listening on ' + HOST +':'+ PORT);
    服务端也可以用稍不同的方式接受TCP连接,即显式处理"connection"事件:
    var server = net.createServer();
    server.listen(PORT, HOST);
    console.log('Server listening on ' +
        server.address().address + ':' + server.address().port);
     
    server.on('connection', function(sock) {
        console.log('CONNECTED: ' +
             sock.remoteAddress +':'+ sock.remotePort);
        // 其它内容与前例相同
    });
    上述两个例子只是写法不同,并无本质区别。
     
    创建TCP客户端
    现在让我们创建一个TCP客户端连接到刚创建的服务器上,该客户端向服务器发送一串消息,并在得到服务器的反馈后关闭连接。下面的代码描述了这一过程。
    var net = require('net');
    var HOST = '127.0.0.1';
    var PORT = 6969;
     
    var client = new net.Socket();
    client.connect(PORT, HOST, function() {
        console.log('CONNECTED TO: ' + HOST + ':' + PORT);
        // 建立连接后立即向服务器发送数据,服务器将收到这些数据
        client.write('I am Chuck Norris!');
    });
     
    // 为客户端添加“data”事件处理函数
    // data是服务器发回的数据
    client.on('data', function(data) {
        console.log('DATA: ' + data);
        // 完全关闭连接
        client.destroy();
    });
     
    // 为客户端添加“close”事件处理函数
    client.on('close', function() {
        console.log('Connection closed');
    });

     

     
    再举个例子:
    var net=require('net');
    function getConnection(connName){
        var client=net.connect({port:8017,host:'127.0.0.1'},function(){
            console.log(connName+'  connected: ');
            console.log(' local=%s:%s',this.localAddress,this.localPort);
            console.log( ' remote=%s:%s',this.remoteAddress,this.remotePort);
            this.setTimeout(500);
            this.setEncoding('utf8');
            this.on('data',function(data){
                console.log(connName+' From Server: '+data.toString());
                this.end();
            });
            this.on('end',function(){
                console.log(connName+' Client disnected');
            });
            this.on('error',function(err){
                console.log('Socket Error: ',JSON.stringify(err));
            });
            this.on('timeout',function(){
                console.log('Socket Time Out');
            });
            this.on('close',function(){
               console.log('Socket Closed');
            });
        });
        return  client;
    }
    function writeData(socket,data){
        var success=!socket.write(data);
        if(!success){
            (function (socket,data){
                socket.once('drain',function(){
                    writeData(socket,data);
                });
            })(socket,data);
        }
    }
    var example1=getConnection('example1');
    var example2=getConnection('example2');
    writeData(example1,'This is example1');
    writeData(example2,'This is example2');
     
    var server=net.createServer(function(client){
       console.log('Client connection: ');
        console.log(' local=%s:%s',client.localAddress,client.localPort);
        console.log( ' remote=%s:%s',client.remoteAddress,client.remotePort);
        client.setTimeout(500);
        client.setEncoding('utf8');
        client.on('data',function(data){
           console.log('Received data from client on port %d:%s',client.remotePort,data.toString());
            console.log(' Bytes received:'+data.toString());
            writeData(client,'Sending: '+data.toString());
            console.log(' Bytes sent: '+client.bytesWritten)
        });
        client.on('end',function(){
            console.log('Client disconnected');
            server.getConnections(function(err,count){
                console.log('Remaining Connections: '+count);
            });
        });
        client.on('error',function(err){
            console.log('Socket Error: '+JSON.stringify(err));
        });
        client.on('timeout',function(){
            console.log('Socket Time Out');
        });
    });
    server.listen(8017,function(){
        console.log('Server listening: '+JSON.stringify(server.address()));
        server.on('close',function(){
            console.log('Server Terminated');
        });
        server.on('error',function(err){
            console.log('Server Error: ',JSON.stringify(err));
        });
    });

     

     

     

    转载于:https://www.cnblogs.com/lyy-2016/p/6747616.html

    展开全文
  • 第三章 运输 运输位于应用和网络之间。该为运行在不同主机上的应用进程提供直接的通信服务起着至关重要的作用。...即将网络的在两个端系统之间的交互服务扩展运行在两个不同端系统上的应用进程...
  • 2. 套接字位于HTTP下面并提供服务器之间的点对点通信。套接字使用套接字地址来工作,这是IP地址和端口的组合。在套接字连接中,有两种类型的点:一类是服务器,它监听连接;一类是客户端,它打开一个服务器的...
  • 4.1.1 进程之间的通信 从通信和信息处理的角度看,传输向它上面的应用提供通信服务,它属于面向通信部分的最高处,同时是用户功能中的最低。两个主机进行通信就是两个主机中的应用进程...
  • 在网络上主机与主机之间的通信,实质上是主机上运行的应用进程之间的通信。比如我们两个用电脑或手机实现聊天,就是两个QQ程序(进程)之间的通信。 倘若当你浏览这篇博客的同时,还挂着QQ,还使用ftp下载大文件...
  • 文章目录一、什么是TCP协议二、三次握手(通俗)三、四次... 连接不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。 其传输的可靠性依赖于三次握手和四次挥手。 二、三次握手(通
  • 网络编程1.TCP是什么2.代码实现 1.TCP是什么 传输控制协议(TCP,Transmission Control Protocol... 连接不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级...
  • 设想一下,若现在没有互联网,只有局域网,那么在局域网内实现端到端的通信(端到端的通信是指应用进程之间的通信),是不是可以不用设计网络了,因为数据链路已经解决了主机主机的问题(这个时候你就不要再想...
  • 尹成Python27天入门项目实战

    千人学习 2020-12-05 17:04:22
    _name__进程mutiprocessing创建过程join作用进程不可以用全局变量共享数据进程同步进程pipe发送接收数据进程队列进程队列传输数据进程共享数据进程之间共享数组进程共享字典与list简单小结day23down ...
  • 网络编程学习目标:网络基础 + 网络API 为什么需要网络通信?...分层好处:各层之间是独立/灵活性好/结构上可分割开/易于实现和维护/能促进标准化工作 物理通信介质信号数字信号(二进制0101
  • 计算机网络必知必会

    2021-03-16 23:21:52
    什么是网络协议,为什么要对网络协议分层 * 网络协议是计算机在通信过程中要遵循的一些约定好的规则。... 应用的任务是通过应用进程之间的交互来完成特定的网络作用,常见的应用协议有域名系统DNS,HTTP协
  • 信号

    2020-11-11 23:09:40
    信号是进程间通信机制中唯一异步通信机制,一个进程不必通过任何操作来等待信号到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事
  • Java核心技术-并发

    2018-05-09 12:08:00
    共享变量使线程之间的通信比进程之间的通信更有效、更容易 1 什么是线程 1.1 使用线程给其他任务提供机会 下面是在一个单独的线程中执行一个任务的简单过程: 实现Runnable接口: 1.将任务代码移到实现...
  • 01 数据库表与表之间的一对多多对多的关系 02 Django的ORM的概念 03 ORM对单表的增删改操作 04 ORM查询API 05 模糊查询之万能的双下换线 第52章 01 上节知识回顾 02 ORM多表操作之一对多增加记录 03 ORM多表操作之...
  • Socket究竟是什么呢? 简单来说Socket就是用来完成客户端与服务器之间的通信 例如浏览器访问网页,例如网络游戏等一切基于客户端服务器...套接字在最开始的时候,是应用于进程与进程之间的通信,之后被广泛应用互联...
  • 引用可以转换接口类型或从接口类型转换,instanceof 运算符可以用来决定某对象类是否实现了接口。 21、heap和stack有什么区别。  栈是一种线形集合,其添加和删除元素操作应在同一段完成。栈按照后进先出...
  • 组件之间的参数传递 数据类型 使用分布式事务 分布式事务协调器(MSDTC) COM+事务的工作 事务与有状态对象 使用共享属性管理器(SPMSharedProperyManager) 小结 第14章 了解MSMQ 何谓MSMQ MSMQ的好处 ...
  • 网络原理复习题

    2013-06-28 08:18:56
    在这些网络之间的通信协议(即通信规则)可以是任意的。Internet(因特网)则是一个专用名词,它指当前全球最大的、开放的、由众多网络相互连接而成的特定计算机网络,它采用TCP/IP协议作为通信的规则,且其前身是...

空空如也

空空如也

1 2 3 4 5 ... 7
收藏数 129
精华内容 51
关键字:

什么层实现进程到进程之间的通信