JAVA程序员笔记 第20篇——网络编程

23 篇文章 0 订阅
20 篇文章 0 订阅

网络编程

首先我们要知道,到目前为止,我们的代码力所能及的地方,都还在自己的电脑上。但现在上网,都是要与其他服务器交互,通信的。
网络编程即使用套接字来达到进程间通信,现在一般称为TCP/IP编程。

TCP/IP协议

传输控制协议/因特网互联协议(Transmission Control Protocol/Internet Protocal),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。

TCP/IP协议中的四层: 应用层、传输层、网络层和链路层

  • 应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。
  • 传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
  • 网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
  • 数据链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。

UDP

  • 用户数据报协议(User Datagram Protocol)。
  • 数据报(Datagram):网络传输的基本单位
  • UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
  • 由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
  • 但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时,不建议使用UDP协议。
  • 特点:数据被限制在64kb以内,超出这个范围就不能发送了。

TCP

  • 传输控制协议(Transmission Control Protocol)。
  • TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
  • 在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。

TC协议——“三次握手”

TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
在这里插入图片描述

  • 第一次握手,客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。
    (客户端向服务器端发出连接请求,等待服务器确认。)
  • 第二次握手,服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。(服务器端向客户端回送一个响应,通知客户端收到了连接请求。)
  • 第三次握手,客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。(客户端再次向服务器端发送确认信息,确认连接。)
    三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。

TC协议——“四次挥手”

其次,TCP的客户端和服务端断开连接,需要四次挥手

在这里插入图片描述

  • 客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
  • 服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSED_WAIT 状态。
  • 客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
  • 等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
  • 客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
  • 服务器收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
  • 客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。

用途中通俗理解就是:
三次握手:
男:我准备出门去找你了
女:ok,我知道了,你可以出门了
男:行,我出门了

四次挥手:
男:我要回家写代码去了
女:准备走了?
女:不再坐会儿?
男:走了走了,代码要紧

部分借鉴自博主:
ZaynFox
原文链接

套接字

创建套接字对象,连接服务器,流使用传输数据

public class Client {
            public static void main(String[] args)throws Exception {

                //创建一个 连接服务器的 套接字 对象
                Socket socket = new Socket();

                //连接服务器
                socket.connect(new InetSocketAddress("localhost",8080));

                try (//获取服务器响应 的数据
                     InputStream in = socket.getInputStream();
                     //包装成缓冲流
                     BufferedReader bis = new BufferedReader(new InputStreamReader(in));

                     //服务器连接后
                     // 获取 输出流
                     OutputStream out = socket.getOutputStream();
                     //包装成缓冲流
                     BufferedWriter bos = new BufferedWriter(new OutputStreamWriter(out));
                ){
                    //发送信息
                    bos.write("hello,服务器1");
                    bos.newLine();
                    bos.flush();

                    bos.newLine();
//            bos.write("");

                    bos.flush();

                    //接收数据
                    StringBuilder sb = new StringBuilder();
                    String line = "";
                    while ((line = bis.readLine()) != null){
                        sb.append(line);
                    }
                    System.out.println(sb);

                }finally {
                    socket.close();
                }
    }
}

绑定 IP和端口 并设置 监听数量
hostname 使用 本地IP地址 localhost : 代表本机 IP
127.0.0.1 代表本机 IPcnm输入ipconfig:
本机IP :192.168.0.118
port 端口号 是一个数字 不能超过65535:
8080,8000,8888,9999,7000,7001

public class Server {
    public static void main(String[] args) throws Exception{

        //创建一个 套接字 Socket 对象

        ServerSocket ss = new ServerSocket();

        //绑定 IP和端口 并设置 监听数量
        /**
         * hostname 使用 本地IP地址
         *
         *  localhost : 代表本机 IP
         *  127.0.0.1 代表本机 IP
         *
         *  cnm输入ipconfig: 本机IP :192.168.0.118
         *
         *  port 端口号
         *          是一个数字  不能超过65535
         *
         *    8080,8000,8888,9999,7000,7001
         *
         */
        ss.bind(new InetSocketAddress("localhost",8080),5);

        // 有很多人连接服务器,所以开启一个死循环,不断接收客户端的连接

        while (true) {

            // 被动等待 客户端的连接,会产生阻塞现象
            //返回一个 socket  用来连接 客户端与服务器
            //服务器 向 客户端 发送数据的通道
            Socket socket = ss.accept();
            new Thread(() ->{
            try ( //接收 客户端 发送的数据  获取输入流
                  InputStream in = socket.getInputStream();
                  //包装成缓冲流
                  BufferedReader bis = new BufferedReader(new InputStreamReader(in));

                  //读取客户端发送的数据
                  OutputStream out = socket.getOutputStream();
                  //包装成缓冲流
                  BufferedWriter  bos = new BufferedWriter(new OutputStreamWriter(out));
            ){
                //打印
                StringBuilder sb = new StringBuilder();
                String line = "";
                //读一行数据
                while (!(line = bis.readLine()).equals("")){
                    sb.append(line);
                }
                System.out.println(sb);

                //发个数据
                bos.write("我是给服务器发的数据:别卡了,给爷爬");

                //记得关闭资源  通道资源也要关
            }catch (Exception e){
                e.printStackTrace();
            }
            finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }).start();

        }
    }
}

综合应用

创建一个在线聊天系统
聊天软件其实是两个用户发给服务器,服务器再反送给对方。同时,支持多人聊天,需要用到多线程的操作。
首先创建服务器类

public class ChatClient {

    private String qq;

    private Socket socket;
    /**
     * 接受一个 聊天的 客户端身份
     */
    public ChatClient(String qq)throws Exception{
        this.qq = qq;

        //创建一个Socket 对象
        socket = new Socket();
        socket.connect(new InetSocketAddress("localhost",9999));

        //获取输出流,表明身份
        OutputStream stream = socket.getOutputStream();
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(stream));

        out.write(qq);
        out.newLine();
        out.flush();

    }

    public void Chat(String to){
        //开启一个线程 负责发送信息
        new Thread(new WriteMsg(socket,to)).start();
        //开启另一个线程 负责读取 服务器发来的 信息
        new Thread(new ReadMsg(socket)).start();

    }

}

然后编写Server层逻辑

public class ChatServer {

    /**
     * 存储所有在线用户的 身份和 socket 通道
     */
    private static Map<String, Socket> map = new HashMap<>();

    public static void main(String[] args) throws Exception {

        ServerSocket serverSocket = new ServerSocket();
        //绑定IP和端口
        serverSocket.bind(new InetSocketAddress("localhost",9999));

        //开启一个死循环,负责监听 和 可客户端的连接
        while (true){
            //阻塞式 等待客户端的连接
            Socket socket = serverSocket.accept();
            //获取 客户端 发送的身份信息 ,并存储到 map 容器中

            //开线程
            new Thread(() ->{
                try{
                    BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

                    //获取 客户端的发送的信息
                    String s = in.readLine();
                    map.put(s,socket);

                    System.out.println(map);

                    //开启一个while死循环 ,不断接收客户端发来的信息
                    while (true){
                        String msg = in.readLine();//处理后的信息
                        if(msg == null)continue;
                        //开始处理
                        String[] split = msg.split(":",2);//拆分

                        System.out.println(msg);
                        String id = split[0];//拿到 发送人
                        msg = split[1];//拿到 需要发送的信息

                        //根据 发送人去 map中找到对应身份的 socket
                        //其中 他与某人聊天的通道Socket是value
                        Socket socket1 = map.get(id);

                        //判空
                        if(socket1 == null){
                            //提示对方不在线
                            out.write("对方不在线");
                            out.newLine();
                            out.flush();
                        }else {
                            //不为空 找到对应的Socket
                            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket1.getOutputStream()));

                            bw.write(String.format("%s:%s",s,msg));
                            bw.newLine();
                            bw.flush();

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

            }).start();
        }
    }
}

然后创建两个类来模拟两个用户:

public class ChatClientTest {
    public static void main(String[] args) throws Exception{
        //发送人
        ChatClient client = new ChatClient("脖子");
        //发送给
        client.Chat("门子");

    }
}
public class ChatClientTest2 {
    public static void main(String[] args) throws Exception{
        //发送人
        ChatClient client = new ChatClient("门子");
        //发送给
        client.Chat("脖子");

    }
}

先启动服务器,然后启动两个用户:
然后就可以发送信息了,还能在服务器查看数据:

发送人:
在这里插入图片描述
接收人:
在这里插入图片描述
服务器
在这里插入图片描述

简单的缓冲流显示数据

必须实现Runnable 接口 重写run()方法

写:

public class WriteMsg implements Runnable{

    private Socket socket;
    private String to;

    public WriteMsg(Socket socket,String to){
        this.socket = socket;
        this.to = to;
    }


    @Override
    public void run() {

        //获取从控制台输入的信息
        Scanner scanner = new Scanner(System.in);

        while (true){
            String text = scanner.nextLine();

            if("exit".equals(text))break;

            //将信息发送给 服务器
            String msg = String.format("%s:%s",to,text);

            //将信息发送给服务器
            try {
                //获取输出流,表明身份
                OutputStream outputStream = socket.getOutputStream();
                BufferedWriter out = new BufferedWriter(new OutputStreamWriter(outputStream));

                out.write(msg);
                out.newLine();
                out.flush();

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

        }
    }
}

读:

public class ReadMsg implements Runnable{

    private Socket socket;

    public ReadMsg(Socket socket){
        this.socket = socket;
    }


    @Override
    public void run() {

        while (true){
            try{
                //创建缓冲流
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                //读数据、并显示在控制台上 即可
                String s = in.readLine();


                if(s == null)continue;
                System.out.println(s);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值