Java网络编程(w)

本文详细介绍了计算机网络的基本概念,包括网络协议的重要性,特别是TCP/IP协议的原理和应用。同时,讲解了网络通信的两个关键要素——IP地址和端口,并通过Java代码示例展示了如何操作。此外,还探讨了TCP的三次握手和四次挥手过程,以及TCP与UDP协议的区别。最后,通过实例展示了TCP和UDP协议在聊天和文件上传中的应用。
摘要由CSDN通过智能技术生成

1、什么是计算机网络

计算机网络学习的核心内容就是网络协议的学习。网络协议是为计算机网络中进行数据交换而建立的规则、标准或者说是约定的集合。因为不同用户的数据终端可能采取的字符集是不同的,两者需要进行通信,必须要在一定的标准上进行。一个很形象地比喻就是我们的语言,我们大天朝地广人多,地方性语言也非常丰富,而且方言之间差距巨大。A地区的方言可能B地区的人根本无法接受,所以我们要为全国人名进行沟通建立一个语言标准,这就是我们的普通话的作用。同样,放眼全球,我们与外国友人沟通的标准语言是英语,所以我们才要苦逼的学习英语。

计算机网络协议同我们的语言一样,多种多样。而ARPA公司与1977年到1979年推出了一种名为ARPANET的网络协议受到了广泛的热捧,其中最主要的原因就是它推出了人尽皆知的TCP/IP标准网络协议。目前TCP/IP协议已经成为Internet中的"通用语言",下图为不同计算机群之间利用TCP/IP进行通信的示意图。

在这里插入图片描述
网络编程的目的:数据交换、通信。

2、网络通信的两个要素

1.通信双方地址:ip、端口
2.通信协议 tcp、udp

ip+端口号就可以连接到具体的一台计算机上的具体的一个应用。

3、IP地址

public class internet {
    public static void main(String[] args) {
        try {
            InetAddress byName = InetAddress.getByName("127.0.0.1");
            System.out.println("byName = " + byName);

            InetAddress byName1 = Inet4Address.getByName("www.baidu.com");
            System.out.println("byName1 = " + byName1);

            InetAddress loopbackAddress = InetAddress.getLoopbackAddress();
            System.out.println("loopbackAddress = " + loopbackAddress);

            InetAddress localHost = InetAddress.getLocalHost();
            System.out.println("localHost = " + localHost);


            System.out.println(byName1.getCanonicalHostName());//获取此IP地址的标准域名。
            System.out.println(byName1.getAddress());//返回原始IP地址
            System.out.println(byName1.getHostAddress());//以文本形式返回IP地址字符串。
            System.out.println(byName1.getHostName());//获取此IP地址的主机名
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}

byName = /127.0.0.1
byName1 = www.baidu.com/14.215.177.38
loopbackAddress = localhost/127.0.0.1
localHost = huangkongshenghugh/192.168.123.2
14.215.177.38
[B@a14482
14.215.177.38
www.baidu.com

4、端口

端口表示一台计算机的进程,一个进程就分配的一个端口号。

  • 端口规定:0~65535
  • TCP和UDP的端口总数:65535*2,单个协议端口不能重复
  • 端口分类:
    • 公有端口 0~1023
      Https :443
      Http: 80
      Ftp: 21
      Telent :23
    • 程序注册端口 1024~49151
      tomcat:8080
      mysql:3306
    • 动态私有 49152~65535

    netstat -ano #查看所有进程
    netstat -ano|findstr “6500”

public class MYInetAddressSocket {
    public static void main(String[] args) {

        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",8080);

        System.out.println("inetSocketAddress = " + inetSocketAddress);
        InetAddress address = inetSocketAddress.getAddress();
        System.out.println("address = " + address);
        String hostName = inetSocketAddress.getHostName();
        System.out.println("hostName = " + hostName);
        String hostString = inetSocketAddress.getHostString();
        System.out.println("hostString = " + hostString);
        int port = inetSocketAddress.getPort();
        System.out.println("port = " + port);

    }
}

inetSocketAddress = /127.0.0.1:8080
address = /127.0.0.1
hostName = 127.0.0.1
hostString = 127.0.0.1
port = 8080

5、协议

TCP/IP协议:
TCP/IP协议是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而IP是给因特网的每一台联网设备规定一个地址。

TCP协议的三次握手和四次挥手:

在这里插入图片描述

注:seq:"sequance"序列号;ack:"acknowledge"确认号;SYN:"synchronize"请求同步标志;;ACK:“acknowledge"确认标志”;FIN:"Finally"结束标志。

TCP连接建立过程: 首先Client端发送连接请求报文,Server段接受连接后回复ACK报文,并为这次连接分配资源。Client端接收到ACK报文后也向Server段发生ACK报文,并分配资源,这样TCP连接就建立了。

TCP连接断开过程: 假设Client端发起中断连接请求,也就是发送FIN报文。Server端接到FIN报文后,意思是说"我Client端没有数据要发给你了",但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以你先发送ACK,“告诉Client端,你的请求我收到了,但是我还没准备好,请继续你等我的消息”。这个时候Client端就进入FIN_WAIT状态,继续等待Server端的FIN报文。当Server端确定数据已发送完成,则向Client端发送FIN报文,“告诉Client端,好了,我这边数据发完了,准备好关闭连接了”。Client端收到FIN报文后,“就知道可以关闭连接了,但是他还是不相信网络,怕Server端不知道要关闭,所以发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。”,Server端收到ACK后,“就知道可以断开连接了”。Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,我Client端也可以关闭连接了。Ok,TCP连接就这样关闭了!

为什么要三次握手?

在只有两次"握手"的情形下,假设Client想跟Server建立连接,但是却因为中途连接请求的数据报丢失了,故Client端不得不重新发送一遍;这个时候Server端仅收到一个连接请求,因此可以正常的建立连接。但是,有时候Client端重新发送请求不是因为数据报丢失了,而是有可能数据传输过程因为网络并发量很大在某结点被阻塞了,这种情形下Server端将先后收到2次请求,并持续等待两个Client请求向他发送数据…问题就在这里,Cient端实际上只有一次请求,而Server端却有2个响应,极端的情况可能由于Client端多次重新发送请求数据而导致Server端最后建立了N多个响应在等待,因而造成极大的资源浪费!所以,"三次握手"很有必要!

为什么要四次挥手?

试想一下,假如现在你是客户端你想断开跟Server的所有连接该怎么做?第一步,你自己先停止向Server端发送数据,并等待Server的回复。但事情还没有完,虽然你自身不往Server发送数据了,但是因为你们之前已经建立好平等的连接了,所以此时他也有主动权向你发送数据;故Server端还得终止主动向你发送数据,并等待你的确认。其实,说白了就是保证双方的一个合约的完整执行!

使用TCP的协议:FTP(文件传输协议)、Telnet(远程登录协议)、SMTP(简单邮件传输协议)、POP3(和SMTP相对,用于接收邮件)、HTTP协议等。

UDP协议

UDP用户数据报协议,是面向无连接的通讯协议,UDP数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送。

UDP通讯时不需要接收方确认,属于不可靠的传输,可能会出现丢包现象,实际应用中要求程序员编程验证。

UDP与TCP位于同一层,但它不管数据包的顺序、错误或重发。因此,UDP不被应用于那些使用虚电路的面向连接的服务,UDP主要用于那些面向查询—应答的服务,例如NFS。相对于FTP或Telnet,这些服务需要交换的信息量较小。

每个UDP报文分UDP报头和UDP数据区两部分。报头由四个16位长(2字节)字段组成,分别说明该报文的源端口、目的端口、报文长度以及校验值。UDP报头由4个域组成,其中每个域各占用2个字节,具体如下:

(1)源端口号;
(2)目标端口号;
(3)数据报长度;
(4)校验值。
使用UDP协议包括:TFTP(简单文件传输协议)、SNMP(简单网络管理协议)、DNS(域名解析协议)、NFS、BOOTP。

TCP 与 UDP 的区别:TCP是面向连接的,可靠的字节流服务;UDP是面向无连接的,不可靠的数据报服务。

6、TCP实现聊天

//服务端
public class ServerDome1 {
    public static void main(String[] args) {
        try {
            //指定服务端端口号
            ServerSocket serverSocket = new ServerSocket(8989);
            //等待客户端连接
            Socket accept = serverSocket.accept();
            InputStream inputStream = accept.getInputStream();
            
            //字节输出流
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

            byte[] bytes=new byte[1024];
            int len;
            while ((len=inputStream.read(bytes))!=-1) {
                outputStream.write(bytes,0,len);
            }
            System.out.println(outputStream.toString());

            //关闭服务
            outputStream.close();
            inputStream.close();
            accept.close();
            serverSocket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
//客户端
public class ClientDome1 {
    public static void main(String[] args) {
        try {
            InetAddress IPAddress = InetAddress.getByName("127.0.0.1");
            int port=8989;
            //指定ip和端口
            Socket socket = new Socket(IPAddress, port);
            //使用输出流发送消息
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("nothing's gonna change my love for you".getBytes());

            //关闭服务
            outputStream.close();

        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

7、文件上传(TCP)

public class ServerDome {
    public static void main(String[] args) throws IOException {
        //创建服务
        ServerSocket serverSocket = new ServerSocket(7878);
        //监听客户端,监听到客户端才会继续走
        Socket socket = serverSocket.accept();
        InputStream inputStream = socket.getInputStream();

        //保存上传的文件
        FileOutputStream fileOutputStream = new FileOutputStream(new File("tt.jpg"));
        byte[] bytes=new byte[1024];
        int len;
        while ((len=inputStream.read(bytes))!=-1) {
            fileOutputStream.write(bytes,0,len);
        }
        
        //接收上传的文件完毕
        socket.shutdownInput();

        //向客户端发送上传成功的消息
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("我已经收到了".getBytes());

        //关闭服务
        outputStream.close();
        fileOutputStream.close();
        inputStream.close();
        socket.close();
        serverSocket.close();
    }
}

public class ClientDome {
    public static void main(String[] args) throws Exception {
        //创建socket连接
        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 7878);
        OutputStream upstream = socket.getOutputStream();

        //输入流来读取本地文件进行上传
        FileInputStream fileInputStream = new FileInputStream(new File("dd.jpg"));
        byte[] bytes=new byte[1024];
        int len;
        while ((len=fileInputStream.read(bytes))!=-1) {
            upstream.write(bytes,0,len);
        }

        //上传结束
        socket.shutdownOutput();

        //接收服务器发来的上传成功的消息
        InputStream inputStream = socket.getInputStream();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] bytes1=new byte[1024];
        int len1;
        while ((len1=inputStream.read(bytes1))!=-1) {
            byteArrayOutputStream.write(bytes1,0,len1);
        }
        System.out.println(byteArrayOutputStream.toString());

        //关闭服务
        byteArrayOutputStream.close();
        inputStream.close();
        fileInputStream.close();
        upstream.close();
        socket.close();


    }
}

8、UDP发送消息

public class ServerDome {
    public static void main(String[] args) throws IOException {
        int port=9090;
        //创建socket,相当于建码头
        DatagramSocket socket = new DatagramSocket(port);

        byte[] bytes=new byte[102];
        //创建DatagramPacket,相当于创建集装箱,用于接收发来的数据
        DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
        //DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
        //接收数据
        socket.receive(packet);

        System.out.println(packet.getAddress().getHostName());
        //jdk1.8以后才能用
        System.out.println(new String(packet.getData(),0, packet.getLength()));
        //关闭
        socket.close();
    }
}
public class ClientDome {
    public static void main(String[] args) throws IOException {
        //建立socket连接
        //相当于码头
        DatagramSocket socket = new DatagramSocket();
        //数据,相当于货物
        String msg="Hello Server";

        InetAddress ipaddress = InetAddress.getByName("127.0.0.1");
        int prot=9090;

        //创建DatagramPacket,相当于货轮,将数据打包(要发送的数据,数据的长度,Ip地址,端口)
        DatagramPacket packet = new DatagramPacket(msg.getBytes(),0,msg.length(),ipaddress,prot);
        //发送
        socket.send(packet);
        //关闭
        socket.close();
    }
}

9、UDP聊天实现

单项发送

public class UDPServerDome {
    public static void main(String[] args) throws IOException {
        int port=9090;
        DatagramSocket socket = new DatagramSocket(port);
        boolean b=true;
        while (b){
            byte[] bytes=new byte[1024];
            DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
            socket.receive(packet);

            String s = new String(packet.getData(), 0, packet.getLength());
            System.out.println(s);

            if ("close".equals(s)){
                b=false;
            }
        }
        socket.close();
    }
}

public class UDPClientDome {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket();
        BufferedReader bufferedReader =null;
        boolean b=true;
        while (b){
            int port=9090;

            bufferedReader = new BufferedReader(new InputStreamReader(System.in));
            String line = bufferedReader.readLine();
            DatagramPacket packet = new DatagramPacket(line.getBytes(), 0, line.length(),InetAddress.getByName("127.0.0.1"), port);

            socket.send(packet);

            if ("close".equals(line)){
                b=false;
            }
        }
        bufferedReader.close();
        socket.close();
    }
}

双向发送(使用多线程)
学生

public class H_Student {
    public static void main(String[] args) {
        new Thread(new UDPClientDome1(6767,8787)).start();
        new Thread(new UDPServerDome1(7171,"老师")).start();
    }
}

老师

public class H_Teacher {
    public static void main(String[] args) {
        new Thread(new UDPClientDome1(7070,7171)).start();
        new Thread(new UDPServerDome1(8787,"学生")).start();
    }
}

接收

public class UDPServerDome1 implements Runnable {
    int port;//自身端口
    String Speaker;
    DatagramSocket socket =null;
    boolean b=true;
    public UDPServerDome1(int port,String Speaker){
        this.port=port;
        this.Speaker=Speaker;
    }

    @Override
    public void run() {
        try {
            socket = new DatagramSocket(port);
            while (b){
                byte[] bytes=new byte[1024];
                DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
                socket.receive(packet);
                String s = new String(packet.getData(), 0, packet.getLength());
                System.out.println(Speaker+":"+s);
                if ("close".equals(s)){
                    b=false;
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            socket.close();
        }
    }
}

发送

public class UDPClientDome1 implements Runnable {
    int port;//自身端口
    int toport;//要发送去的端口
    DatagramSocket socket = null;
    BufferedReader bufferedReader=null;
    boolean b=true;
    public UDPClientDome1(int port,int toport){
        this.port=port;
        this.toport=toport;
    }

    @Override
    public void run() {

        try {
            socket= new DatagramSocket(port);
            while (b){
                bufferedReader=new BufferedReader(new InputStreamReader(System.in));
                String s = bufferedReader.readLine();
                DatagramPacket packet = new DatagramPacket(s.getBytes(), 0, s.getBytes().length(), InetAddress.getByName("127.0.0.1"), toport);
                //之前使用这个包中文乱码,底下有详解。DatagramPacket packet = new DatagramPacket(s.getBytes(), 0, s.length(), InetAddress.getByName("127.0.0.1"), toport);
                socket.send(packet);
                if ("close".equals(s)){
                    b=false;
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            socket.close();
        }

    }
}

在UDP客户端构造发送信息,将信息打包到DatagramPacket中,在打包过程中,如果发送的是纯英文字符,自然是一个字符占据一个字节,如果包含中文,则一个中文字符占据两个字节,所以发送数据的长度如果用s.length()计算,英文字符不会出错,计算中文字符就会少计算了要发送的总字节数,造成UDP服务端接收中文乱码.
统一使用: s.getBytes().length

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值