网络编程套接字,看懂掌声

网络编程初识

  1. 网络上不同的主机,以编程的方式实现网络通信(网络数据传输)
  2. 只要满足进程不同就行
  3. 即便是同一个主机,只要是不同的进程,基于网络来传输数据,也属于网络编程

网络编程套接字

也叫作 Socket套接字

  1. Socket库(Socket API):是操作系统提供给应用程序的一组API(也可以说是程序组件的集合),其用于调用操作系统的网络功能,进行网络通信;
    应用层按照指定顺序调用Socket库中的程序,就可以向操作系统内部的协议栈发出委托,委托协议栈向服务器发送消息

  2. 在协议栈(操作系统中)内部有一块用于存放控制信息的内存空间,这里记录了用于控制通信操作的控制信息,例如通信对象的IP地址、端口号、通信操作的进行状态等
    在这里插入图片描述

  3. 套接字(Socket):套接字是一个概念,我们可以说,套接字代表这些控制信息,或者说代表存放控制信息的内存空间
    而真正的套接字(套接字的实体),可以通过 netstat -ano查看套接字内容,每一行就相当于一个套接字,当创建套接字的时候,就会在这里添加一行新的控制信息

  4. 而且协议栈是根据套接字中记录的控制信息来工作的

UDP数据报套接字编程

  1. 面向数据报
    以数据报为单位进行读写
  2. 无连接
    不建立连接,直接通信,相当于“发微信”
  3. 不可靠传输
    发送消息后,不知道接收端有没有接收到
  4. 全双工
    可收可发
  • DatagramSocket
    描述了一个Socket对象,用于发送和接收数据报
构造方法
public DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机的任意一个端口上(一般用于客户端
public DatagramSocket(int port)创建一个UDP数据报套接字的Socket,绑定到本机的指定端口上(一般用于服务器
普通方法
public synchronized void receive(DatagramPacket p)接收数据报;没有收到数据报,会阻塞等待
public void send(DatagramPacket p)发送数据报,不会阻塞等待,直接发送
public void close()关闭此套接字
  • DatagramPacket
    表示数据报
构造方法
public DatagramPacker(byte[ ] buf, int offset, int length)用来接收数据,接收的数据保存在字节数组中,从数组的指定位置开始放,注意length是接收指定长度(字节)的数据,此处需要双方约定好
public DatagramPacket(byte buf[ ], int offset, int length,InetAddress address, int port)用来发送数据,发送的数据为字节数组[offset, offset+length),address为目的IP,port为目的端口号
public DatagramPacket(byte buf[ ], int offset, int length,SocketAddress addres)用来发送数据,address中包含目的IP和目的端口号
普通方法
public synchronized InetAddress getAddress()从接收的数据报中,获取发送端主机的IP地址;或从要发送的数据报中,获取接收端主机的IP地址
public synchronized int getPort()从接收的数据报中,获取发送端主机的端口号;或从要发送的数据报中,获取接收端主机的端口号
public synchronized byte[ ] getData()获取数据报的内容
public synchronized int getLength()获取数据报的长度
public synchronized SocketAddress getSocketAddress()从接收的数据报中,获取发送端主机的SocketAddress;或从要发送的数据报中,获取接收端主机的SocketAddress

InetAddress 表示IP地址
比较重要的两个方法

  1. public static InetAddress getByName(String host)
    将 字符串 转变为 InerAddress(ip地址)
  2. public String toString()
    将 InerAddress(ip地址)转变为 字符串

socketAddress 是一个抽象类

  1. InetSocketAddress 继承自 socketAddress,其中又维护了一个 InetSocketAddressHolder静态内部类,有字段保存Socket的IP地址、端口号、域名
  2. int getPort()
  3. InetAddress getAddress()
  4. String getHostName()

在这里插入图片描述

实现一个Udp协议的客户端服务器程序

服务器程序

package net;

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


public class UdpServer {
    private DatagramSocket socket = null;


    public UdpServer(int port) throws SocketException {
        // 创建 socket,指定端口号; 没指定ip,就是localhost
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        while(true) {
            // 1. 接收请求
            // 1.1 创建字节数组,用于存放数据
            byte[] bytes = new byte[1024];
            // 1.2 构建 DatagramPacket数据报,接收数据, length表示可以接收的数据的长度(字节)
            // 此处构建的 DatagramSocket 是一个 空的 数据报,里面是没有数据的
            DatagramPacket requestPacket = new DatagramPacket(bytes, bytes.length);
            // 1.3 接收数据报,将数据存在 DatagramPacket 中
            // 此处的 requestRacket 就是一个输出型参数
            socket.receive(requestPacket);
            // 1.4 将 请求 转变为 字符串,方便计算响应
            String request = new String(bytes, 0, requestPacket.getLength());

            // 2. 处理请求,获得响应
            String response = process(request);

            // 3. 返回响应
            // 3.1 构建 要发送的响应数据报
            // 3.11 获取 客户端的IP地址 和端口号
            InetAddress clientAddress = requestPacket.getAddress();
            int port = requestPacket.getPort();
            // 3.12 将 response 字符串转成字节数组
            byte[] b = response.getBytes();
            // 3.13 构建
            DatagramPacket responsePacket = new DatagramPacket(b, 0, b.length, clientAddress, port);

            // 3.2 发送 响应数据报
            socket.send(responsePacket);

            // 记录一下传输过程
            System.out.printf("[%s : %d] req: %s; resp: %s \n", clientAddress.toString(), port, request, response);
        }

    }

    public String process(String request){
        return "拼搏百天,我要去" + request + "!!!";
    }

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


客户端程序

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

public class UdpClient {
    private DatagramSocket socket ;
    private InetAddress ipServer ;
    private int portServer;

    public UdpClient(String ipServer, int portServer) throws SocketException, UnknownHostException {
        this.ipServer = InetAddress.getByName(ipServer);
        this.portServer = portServer;

        // 创建服务器 Socket 对象,随机分配一个未被占用的端口
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while(true){
            // 1. 用户输入数据
            System.out.println(">");
            String request = scanner.next();
            if(request.equals("exit")){
                System.out.println("bye-bye!!!");
                break;
            }

            // 2.构建 请求数据报
            // 2.1 将 字符串类型的 request 转变为 字节数组
            byte[] bytes = request.getBytes();
            DatagramPacket requestPacket = new DatagramPacket(bytes, 0, bytes.length, this.ipServer, this.portServer);

            // 3. 发送数据报
            socket.send(requestPacket);

            // 4. 获取响应数据报
            byte[] b = new byte[1024];
            // 此处构建的 DatagramSocket 是一个 空的 数据报,里面是没有数据的
            DatagramPacket responsePacket = new DatagramPacket(b, 0, b.length);
            // 此处的 requestRacket 就是一个输出型参数
            socket.receive(responsePacket);

            // 5. 打印响应
            String response = new String(b, 0, responsePacket.getLength());
            System.out.printf("req: %s; resp: %s \n", request, response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpClient client = new UdpClient("localhost", 9960);
        client.start();
    }
}

TCP套接字编程

  1. 面向字节流
    以字节为单位进行读写
  2. 有连接
    通信前先打招呼,相当于“打电话”
  3. 可靠传输
    发送消息后,可以知道接收端有没有接收到消息
  4. 全双工
    可收可发
  • ServerSocket

构造方法
ServerSocket(int port) 创建一个服务端Socket,绑定到指定端口

普通方法
Socket accept()
监听指定端口(创建时绑定的端口),有客户端连接后,返回 与客户端建立连接的 服务端Socket,没有客户端连接时就阻塞等待

服务器的应用程序先使用ServerSocket操纵网卡,有客户端连接时,再使用Socket操纵网卡

  • Socket
    客户端 Socket
    或 服务器中,与客户端建立连接的 服务端Socket

构造方法
Socket(String host, int port)
Socket(InetAddress ip, int port)
创建客户端Socket,参数是 服务器的Ip和端口号(目的Ip、目的端口)

普通方法
public InetAddress getInetAddress()得到Socke连接的地址
public int getPort()得到Socke连接的端口号
public InputStream getInputStream()Socket的输入流,从Socket中取数据
public InputStream getInputStream()Socket的输出流,往Socket中放数据

关于客户端Socket

  1. 实例化客户端Socket的时候,客户端与服务器就建立连接
  2. 客户端的端口是随机分配一个未被占用的端口
  3. 每个客户端与服务器之间都有一个独立的Socket连接,当一个客户端与服务器通信结束时,注意关闭 它的服务端Socket,释放资源
  4. 使用多线程,实现多个客户端同时与服务器通信

关于 服务器中ServerSocket和Socket的关系

  1. ServerSocket 监听指定的端口,获取到与 客户端建立连接的 Socket(也可以把Socket看作是连接)
  2. Socket 就是 与客户端建立连接的 服务端Socket
  3. 比如,马路牙子上的房产中介,我想买房,他就拉着我到售楼处,会有专业的 销售 来向我介绍楼盘,然后中介继续去马路上“蹲守”;
    我就类似于客户端Socket,房产中介就类似于ServerSocket,销售 就类似于 服务端Socket,销售向我介绍楼盘 就类似于与网络通信

在这里插入图片描述

实现一个 TCP协议的客户端服务器程序

服务器程序

package net;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;


public class TcpServer {
    private ServerSocket serverSocket = null;

    // 指定一个端口
    public TcpServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        // 线程池
        ExecutorService pool = Executors.newCachedThreadPool();
        
        while(true){
            // 1. 监听服务器指定的端口,获取 与发送连接请求的客户端 建立的连接
            Socket socket = serverSocket.accept();

            String clientIp = socket.getInetAddress().toString();
            int clientPort = socket.getPort();
            System.out.printf("与 [%s : %d] 建立连接!!!", clientIp, clientPort);

            // 使用多线程,做到多个客户端同时和服务器进行网络通信
            // 一个连接(客户端)独占一个线程,互不影响
            Thread t = new Thread( () -> {
                // 2. 进行通信
                communicate(socket);
            });
            t.start();

// 线程池,创建销毁线程交给用户态
//            pool.submit(new Runnable() {
//                @Override
//                public void run() {
//                    processConnection(clientSocket);
//                }
//            });


        }
    }

    public void communicate(Socket socket) {
        // 1. 获取socket的输入/输出流   方法执行完毕,关闭 I/O 输入输出流,释放资源
        try (InputStream in = socket.getInputStream()) {
            try (OutputStream out = socket.getOutputStream()) {

                Scanner scanner = new Scanner(in);
                PrintWriter writer = new PrintWriter(out);

                // 客户端的 ip地址、端口号
                String clientIp = socket.getInetAddress().toString();
                int clientPort = socket.getPort();

                while (true) {
                    String request = null;

                    // 2. 获取请求
                    // 如果 客户端关闭了,scanner.hasNext() == false, break 退出循环,结束进程
                    if (scanner.hasNext()) {
                        request = scanner.nextLine();
                    } else {
                        System.out.printf("[%s : %d] 断开连接!!!", clientIp, clientPort);
                        break;
                    }

                    // 3. 处理请求,得到响应
                    String response = process(request);

                    // 4. 返回响应
                    writer.println(response);
                    // 为了保证数据能及时地返回给客户端,手动加上一个刷新缓冲区的操作
                    // 通过 TCP连接 返回给客户端的数据,先写到客户端的缓冲区中,积累一些在写到网卡中
                    writer.flush();

                    // 打印信息
                    InetAddress ip = socket.getInetAddress();
                    int port = socket.getPort();
                    System.out.printf("[%s : %d] req: %s; resp: %s \n", ip.toString(), port, request, response);
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭 socket,释放资源
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }


    public String process(String request){
        return "拼搏百天, 我要去" + request + "!!!";
    }


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

客户端程序

package net;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;


public class TcpClient {

    private Socket socket;
    private InetAddress serverIp;
    private int port;

    // 指定 服务器的ip和端口号
    // 客户端的端口是 随机分配了一个 未被占用的端口
    public TcpClient(String serverIp, int port) throws IOException {
        this.serverIp = InetAddress.getByName(serverIp);
        this.port = port;

        // 客户端与服务器建立连接
        socket = new Socket(serverIp, port);
    }

    public void start(){

        // 获取 socket 的输入输出流

        try (InputStream in = socket.getInputStream()){
            try (OutputStream out = socket.getOutputStream()){
                Scanner scanner = new Scanner(System.in);

                Scanner sc = new Scanner(in);
                PrintWriter writer = new PrintWriter(out);

                socket = new Socket(serverIp, port);
                while(true){
                    // 1. 用户输入请求
                    System.out.print("> ");
                    String request = scanner.nextLine();
                    if(request.equals("exit")){
                        break;
                    }
                    // 2. 传输请求
                    writer.println(request);
                    writer.flush();

                    // 3. 获得响应
                    String response = sc.nextLine();

                    // 4. 处理响应
                    System.out.printf("req: %s; resp: %s", request, response);
                    System.out.println();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpClient client = new TcpClient("localhost", 8088);
        client.start();
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

威少总冠军

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值