【浅学Java】网络编程

1. 网络编程基础

1.1 什么是网络编程

网络编程就是通过代码,来控制两个主机的进程之间的数据交互。

1.2 靠什么来进行网络编程

操作系统把网络编程的一些操作封装起来,对外提供一组 API 供程序员使用,在这里我们使用的是Socket API,它其实就是传输层提供给应用层的服务。

1.3 常见的Socket API 分类

  1. 数据报套接字:底层使用UDP协议,数据传输的单位是数据报
  2. 流套接字:底层使用TCP协议,数据传输的单位是字节

1.4 初始UDP,TCP协议

UDP和TCP协议都是传输层的协议。

  1. UDP协议:无连接,不可靠传输,面向数据报,全双工
  2. TCP协议:有连接,可靠传输,面向字节流,全双工

1.5 什么是socket?

在操作系统中,一切皆文件,网卡作为一个硬件设备,操作系统也是用文件的形式来管理网卡,此处用来管理网卡的文件就是socket。

socket就是一个文件描述符表。
当某个进程被创建出来的时候,进程就会对应的创建一个PCB,PCB中就包含一个文件描述符表,文辞打开文件,就会为对应的文件分配一个表项。

1.6 客户端/服务器端通讯的基本流程

在这里插入图片描述
其中第三步是程序最核心的部分,其他部分都是大同小异。

2. UDP数据报套接字编程

2.1 UDP socket中核心的两个类

DatagramSocket:

用来描述一个socket对象,里面的方法有:

  1. receive:用来接收数据,如果数据没有过来,就会阻塞等待,如果有数据了,就会返回一个DatagramPacket对象。
  2. send:用来发送数据,以DatagramPacket为单位进行发送

注意:发送的时候,得知道发送得目标在哪里,接收得时候,也得知道数据从哪里来。

DatagramPacket

用来描述一个数据报,发送、接收都是以DatagramPacket为单位进行的。

2.2 UDP编程——回显服务

服务器端:

package 回显服务UDP;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 3020104637
 * Date: 2022-08-01
 * Time: 23:49
 */
public class UdpEchServer {
    private DatagramSocket socket = null;
    //port 表示端口号,服务器启动的时候,需要关联(绑定)一个端口号
    //收到数据的时候,就会根据这个端口号来决定把数据就给哪个进程
    //虽然这里的 port 写的是int类型,但是实际上是一个两个字节的无符号整数,范围为:0 ~ 65535
    public UdpEchServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    //启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动");
        //服务器一般都是维持运行 7*24h
        while(true){
            //1.读取请求,当前服务器不知道客户端啥时候发送过来请求,此时一直处于阻塞状态
            //   如果真的有请求过来了,此时 receive 就会返回
            //   参数DattagramPacket是一个输出型参数,socket读到的数据就会设置到这个对象中
            //   DatagramPacket 在构造的时候,需要指定一个内存缓冲区(就是一段内存空间,通常使用byte[])
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);//把receive的返回值给requestPacket
            //把requestPacket对象里面的内容取出来,作为一个字符串
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            //2.根据请求来响应计算
            String response = process(request);
            //3.把响应写回到客户端,这时候也需要构造一个DatapramPacket
            //  此处给DatagramPacket中设置长度,必须是“字节数的个数”
            //  如果直接取response.length(),此处得到的是,字符串的长度,也就是“字符的个数”
            //  当前的responsePacket在构造的时候,还需要指定这个包要发给谁
            //  其实发送给的目标,就是发请求的一方,用requestPacket.getSocketAddress()来获取
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
                    response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            //4.加上日志打印,用格式化字符串的方式
            String log = String.format("[%s:%d] req: %s; resp: %s",
                    requestPacket.getAddress().toString(),
                    requestPacket.getPort(),
                    request,response);
            System.out.println(log);
        }
    }
    //次数的 process 方法负责的功能就是根据请求来计算响应
    //由于当前是一个回显服务,于是就将客户端发的请求直接返回即可
    private String process(String request){
        return request;
    }

    public static void main(String[] args) throws IOException {
        UdpEchServer server = new UdpEchServer(9090);
        server.start();
    }
}

客户端:

package 回显服务UDP;

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 3020104637
 * Date: 2022-08-01
 * Time: 23:49
 */
//客户端发什么,服务器回复什么
public class UdpEchClient {
    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;
    public UdpEchClient(String serverIp,int serverPort) throws SocketException {
        this.serverIp=serverIp;
        this.serverPort=serverPort;
        //构造的是客户端的socket,所以得有自己的端口,此时不指定端口就会随机获取一个空闲得端口
        this.socket=new DatagramSocket();
    }
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (true){
            //1.从标准输入读入一个数据
            System.out.println("-> ");
            String request=scanner.nextLine();
            if(request.equals("exit")){
                System.out.println("exit");
                return;
            }
            //2.把字符串构造成一个 UDP 请求
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
                    request.getBytes().length,
                    InetAddress.getByName(serverIp),
                    serverPort);
            socket.send(requestPacket);
            //3.尝试从服务器读取响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            //显示这个结果
            String log = String.format("req: %s; resp: %s",request,response);
            System.out.println(log);
        }
    }
    public static void main(String[] args) throws IOException {
        //127.0.0.1 是一个环回ip,表示主机本身
        UdpEchClient client = new UdpEchClient("127.0.0.1",9090);
        client.start();
    }
}

2.3 程序解析

DatagramPacket的三种构造方法:
在这里插入图片描述
第一种构造方法的特殊之处
在这里插入图片描述

3. TCP流套接字编程

3.1 TCP socket种核心的类

ServerSocket

ServerSocket是创建TCP服务器的Socket API
在这里插入图片描述

Socket

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。

不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

在这里插入图片描述
在这里插入图片描述

ServerSocket和socket的区别

ServerSocket 一般仅用于设置端口号和监听,真正进行通信的是服务器端的Socket与客户端的Socket,在ServerSocket 进行accept之后,就将主动权转让了。
详见: ServerSocket和socket的区别

3.2 TCP编程——回显服务

客户端

public class TcpEchClient {
    private Socket socket =null;
    private String serverIp;
    private int serverPort;
    public TcpEchClient(String serverIp,int serverPort) throws IOException {
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        //让socket创建的同时,就 尝试和服务器建立连接,此处就发生TCP的三次握手
        this.socket = new Socket(serverIp,serverPort);
    }
    public void start(){
        Scanner scanner =new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()) {
            while(true){
                //1.从键盘上输入内容
                System.out.println("->");
                String request = scanner.next();
                if(request.equals("exit")){
                    System.out.println("exit");
                    break;
                }
                //2.把读取的内容,构造成请求
                PrintWriter writer = new PrintWriter(outputStream);
                writer.println(request);
                writer.flush();//刷新缓冲区
                //3.从服务器读取响应并解析
                Scanner respScanner = new Scanner(inputStream);
                String response=respScanner.next();
                //4.把结果显示到界面上
                String log = String.format("req:%s, res:%s",request,response);
                System.out.println(log);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchClient client = new TcpEchClient("127.0.0.1",9000);
        client.start();
    }
}

几个注意点:
1.
在这里插入图片描述
2.
在这里插入图片描述

服务器端

public class TcpEchServer {
    private ServerSocket listenSocket=null;
    public TcpEchServer(int port) throws IOException {
        listenSocket = new ServerSocket(port);
    }
    public void satrt() throws IOException {
        System.out.println("服务器启动");
        //可能会有多次连接
        while(true){
            //1.建立连接
            //  当没有服务器请求建立连接的话,accept就进行阻塞
            //  当没服务器请求建立连接的话,accept就会返回一个Socket对象
            Socket clientSocket=listenSocket.accept();
            //2.处理连接
            processConnection(clientSocket);
        }
    }

    private void processConnection(Socket clientSocket) throws IOException {
        //处理一个连接,在这里可能会设计到客户端和服务器端的多次交互
        String log = String.format("[%s:%d] 客户端上线了",
                clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        System.out.println(log);
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){
            while(true){
                //1.读取请求并解析
                Scanner scanner =new Scanner(inputStream);
                if(!scanner.hasNext()){
                    log = String.format("[%s:%d] 客户端下线!",
                            clientSocket.getInetAddress().toString(),
                            clientSocket.getPort());
                    System.out.println(log);
                    break;
                }
                String request = scanner.next();
                //2.根据请求计算两句
                String response = process(request);
                //3.把响应写回给客户端
                PrintWriter writer = new PrintWriter(outputStream);
                writer.println(response);
                writer.flush();
                log = String.format("[%s:%d], req:%s, res:%s",
                        clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),
                        request,
                        response);
                System.out.println(log);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            //当前的clientSocket不是跟随整个生命周期,而是与连接有关
            //因此每个连接结束,都要进行关闭
            //否则,随着socket的增多,就会出现资源泄漏的问题
            clientSocket.close();
        }
    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchServer server = new TcpEchServer(9000);
        server.satrt();
    }
}

上述版本的话,当有多个客户端发起请求时,就无法处理,原因分析:
在这里插入图片描述
那怎么解决呢?
可以采用多线程的方式解决这个问题。
在这里插入图片描述
但是上面的多线程方式,有多少个请求,就要创建多少个线程,这样下去就比较消耗资源。
现实中有的线程并不一直有活可干,这样下去就比较消耗资源,因此可以采用线程池的方式。
在这里插入图片描述

4. Wrieshark抓包工具的使用

1.选项
在这里插入图片描述
2.设置过滤器
在这里插入图片描述
3.根据结果分析问题
在这里插入图片描述

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值