Java之网络编程TCP篇

Java之网络编程TCP篇

TCP简介

TCP(transmission control protocol)传输控制协议,属于传输层协议,是一种可靠的数据连接,面向连接,面向字节流,只支持点对点通信,拥有拥塞控制机制。

TCP的三次握手与四次挥手三次握手,简单来说可以这样解释:我要和你建立连接,你真的要和我建立连接吗,我真的要和你建立连接,成功。

具体而言:

第一次握手:client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN-SENT状态,等待server确认
第二次握手:server收到数据包后由标志位SYN=1知道client请求建立连接,server将标志位SYN和ACK都置为1,ack = J+1,随机产生一个值seq = k,并将该数据包发送给client以确认连接请求,Server进入SYN-RCVD状态
第三次握手:client收到确认后,检查ack是否为j+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,client和server进入established状态,完成三次握手,随后client与server之间可以开始开始传输数据了

四次挥手,简单来说可以这样解释:我要和你断开连接;好吧,断开吧;我也要和你断开连接;好吧,断开吧;

具体而言:

第一次挥手:client发送一个FIN,用来关闭client到server的数据传送,client进入FIN_WAIT_1状态
第二次挥手:server收到FIN后,发送一个ACK给client,确认序号为收到序号+1(和SYN相同,一个FIN占用一个序号),server进入close-wait状态,此时tcp连接出于半关闭状态,即客户端已经没有要发送的数据了,但服务端若发送数据,则客户端仍要接收。
第三次挥手:server发送一个FIN,用来关闭server到client的数据传送,server进入last-ack状态。
第四次挥手:client收到fin后,client进入time-wait状态,接着发送一个ack给server,确认序号为收到序号+1,server进入closed状态,完成四次挥手。

TCP传输的可靠性TCP提供一种面向连接的、可靠的字节流服务。其中,面向连接意味着两个使用TCP的应用(通常是一个客户和一个服务器)在彼此交换数据之前必须先建立一个TCP连接。在一个TCP连接中,仅有两方进行彼此通信;而字节流服务意味着两个应用程序通过TCP链接交换8bit字节构成的字节流,TCP不在字节流中插入记录标识符。对于可靠性,TCP通过以下方式进行保证:

数据包校验:目的是检测数据在传输过程中的任何变化,若校验出包有错,则丢弃报文段并且不给出响应,这时TCP发送数据端超时后会重发数据;
对失序数据包重排序:既然TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。TCP将对失序数据进行重新排序,然后才交给应用层;
丢弃重复数据:对于重复数据,能够丢弃重复数据;
应答机制:当TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒;
超时重发:当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段;
流量控制:TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据,这可以防止较快主机致使较慢主机的缓冲区溢出,这就是流量控制。TCP使用的流量控制协议是可变大小的滑动窗口协议。

TCP的拥塞控制计算机网络中的带宽、交换结点中的缓存及处理机等都是网络的资源。在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就会变坏,这种情况就叫做拥塞。拥塞控制就是 防止过多的数据注入网络中,这样可以使网络中的路由器或链路不致过载。注意,拥塞控制和流量控制不同,前者是一个全局性的过程,而后者指点对点通信量的控制。拥塞控制的方法主要有以下四种:

1). 慢启动:不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小;
2)拥塞避免:拥塞避免算法让拥塞窗口缓慢增长,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍,这样拥塞窗口按线性规律缓慢增长。
3)快速重传:快速重传要求接收方在收到一个 失序的报文段 后就立即发出 重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。
4)快恢复:快重传配合使用的还有快恢复算法,当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半,但是接下去并不执行慢开始算法:因为如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法。

TCP对应的应用层协议

FTP:定义了文件传输协议,使用21端口。常说某某计算机开了FTP服务便是启动了文件传输服务。下载文件,上传主页,都要用到FTP服务。
Telnet:它是一种用于远程登陆的端口,用户可以以自己的身份远程连接到计算机上,通过这种端口可以提供一种基于DOS模式下的通信服务。如以前的BBS是-纯字符界面的,支持BBS的服务器将23端口打开,对外提供服务。
SMTP:定义了简单邮件传送协议,现在很多邮件服务器都用的是这个协议,用于发送邮件。如常见的免费邮件服务中用的就是这个邮件服务端口,所以在电子邮件设置-中常看到有这么SMTP端口设置这个栏,服务器开放的是25号端口。
POP3:它是和SMTP对应,POP3用于接收邮件。通常情况下,POP3协议所用的是110端口。也是说,只要你有相应的使用POP3协议的程序(例如Fo-xmail或Outlook),就可以不以Web方式登陆进邮箱界面,直接用邮件程序就可以收到邮件(如是163邮箱就没有必要先进入网易网站,再进入自己的邮-箱来收信)。
HTTP:从Web服务器传输超文本到本地浏览器的传送协议。

java中的TCP

在java中,对TCP方式的网络编程提供了很好的支持,主要提供了两个类,Socket类和ServerSocket类。一个Socket实例代表了TCP连接的一段。一个TCP连接(TCP connection)是一条抽象的双向信道,两端分别由IP地址和端口号确定。在实际实现时,以java.net.Socket类代表客户端连接,以java.net.ServerSocket 类代表服务器端连接。在进行编程时,底层的网络通信的细节已经实现了比较高的封装,所以程序员在实际编程时,只需要指定IP地址和端口号就可以建立连接了。

建立服务端

在开始通信之前,要建立一个TCP连接,这需要先由客户端TCP向服务器端TCP发送连接请求(TCP三次握手)。ServerSocket实例则监听TCP连续请求,并为每个请求创建新的Socket实例。也就是说,服务器端要同时处理ServerSocket实例和Socket实例,而客户端只需要使用Scoket实例。服务器端的工作是建立一个通信终端,并被动地等待客户端的连接。创建一个典型的TCP服务器有以下四个步骤。

(1)创建一个ServerSocket实例并指定本地端口。此套接字的功能是侦听该指定端口收到的连接
(2)调用ServerSocket的accept()方法以获取下一个客户端连接。基于新建的客户端连接,创建一个Socket实例,并由accept()方法返回
(3)使用所返回的Socket类的InputStream和OutPutStream与客户端进行通信。
(4)通信完成以后,使用Socket类的close()方法关闭该客户端套接字连接。

代码如下(接收客户端传来的边长,实现正方形面积的计算,并向客户端返回计算的结果):

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
​
public class Server {
    public static void main(String[] args){
        int port =9997 ;
        ServerSocket server = null;
        Socket socket = null;
        DataOutputStream dos = null;
        DataInputStream dis = null;
        try{
            server = new ServerSocket(port);
            System.out.println("服务器创建成功!端口号为:"+port);
            while (true){
                    socket = server.accept();
                    dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
                    dis = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
                    double length = dis.readDouble();
                    double result = length*length;
                    dos.writeDouble(result);
                    dos.flush();
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                dis.close();
                dos.close();
                socket.close();
                server.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
​
    }
}

建立客户端

代码如下:

import java.io.*;
import java.net.ConnectException;
import java.net.Socket;
import java.util.Scanner;
​
public class Client {
    public static void main(String[] args){
        int port = 9997 ;
        String host = "localhost";
        Socket socket = null;
        DataOutputStream dos = null;
        DataInputStream dis = null;
        try {
            socket = new Socket(host,port);
            dis = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
            dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
            Scanner sc = new Scanner(System.in);
            boolean flag = false;
            while (!flag){
                System.out.println("请输入正方形的边长");
                double length = sc.nextDouble();
                dos.writeDouble(length);
                dos.flush();
                double area = dis.readDouble();
                System.out.println("服务器返回的面积为:"+ area);
                while (true){
                    System.out.println("继续计算?(Y/N)");
                    String str = sc.next();
                    if(str.equalsIgnoreCase("N")){
//                        dos.writeInt(0);
//                        dos.flush();
                        flag = true;
                        break;
                    }else if(str.equalsIgnoreCase("Y")){
//                        dos.writeInt(1);
//                        dos.flush();
                        break;
                    }else {
                        System.out.println("请输入Y or N");
                    }
                }
            }
        }catch (ConnectException c){
            System.out.println("连接服务器失败");
        } catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                dis.close();
                dos.close();
                socket.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

先启动服务端,再启动客户端,便可以运行程序。

参考博文:https://blog.csdn.net/justloveyou_/article/details/78303617

猜你喜欢

转载自blog.csdn.net/qq_38180223/article/details/81478664