网络编程2

Socket

Socket是基于TCP/IP协议的网络通信的基本操作单元;

Socket是操作系统给应用程序提供的一组API,充当了应用层和传输层的桥梁.

传输层中两类核心协议:UDP  TCP

UDP:无连接 不可靠传输 面向数据报 全双工

TCP:有连接 可靠传输 面向字节流 全双工


什么是有/无连接?

有连接:就像是我们打电话,拨打电话后要等待对方接通后,我们才可以交流

无连接:就像是我们发qq消息,我只关心我发送了这个消息,不关心你收没收到这个消息

               我们发了消息就是发了 在网络中也难免存在网络波动导致这个信息可能有所丢失,所以也导致接收方压根没接收到这个信息


什么是可/不可靠传输?

可靠传输:明确知道对方是否收到消息;就像我们依旧在qq发信息,当我们把信息发送出去,等待对方确认回应是否收到信息;如果一段时间没有应答,那我们将继续重新发送消息(超时重传)以便对方确认收到信息.

                   简单来说:传输过程中,发送方知道接收方有没有接收到信息.

但是对于可靠传输,只能保证我100%确定这个消息被对方接收,并不代表这个传输就是安全传输,不能保证安全性.

不可靠传输:传输过程中,发送方不知道接收方有没有接收到信息.

对于UDP、TCP(有舍有得)

UDP是不可靠传输,但传输效率更高.

TCP是可靠传输,但传输效率降低了.

对应上述结论:UDP是不可靠传输,TCP是可靠传输;所以TCP比UDP更安全?大错特错!


面向字节流/面向数据报

TCP和文件操作特别类似 都是以“流”的方式(在这里传输单位是字节,称作字节流~)

UDP是面向数据报 读写单位是一个UDP数据报(包含了一系列的数据/属性 后期会写的~)


全双工/半双工

全双工:一个通道,可以双向通信

半双工:一个通道,只能单向通信


UDP

先认识UDP的socket api  两个核心类:DatagramSocket、DatagramPacket

DatagramSocket

是一个socket对象 要进行网络通信必须得先有socket对象

DatagramPacket

表示一个UDP数据报 代表了系统中设定的UDP数据报的二进制结构


UDP客户端服务器

写网络程序的代码时,经常会遇到下面这种异常

上述最典型的情况:端口被占用

端口号主要区分主机上的应用程序;一个应用程序可以占据主机上的多个端口,一个端口只能被一个进程占用(排除特例~)

当端口已经被别的进程占用,你再尝试创建这个socket对象,占用该端口,此时就肯定会报错啦~

下述代码:

1.读取客户端发来的请求

2.根据请求计算机响应

3.把响应写回给客户端

4.站在服务器的角度:

        源IP:本机IP;源端口:指定的端口 (服务器绑定的端口)

        目的IP:包含在收到的数据报中;目的端口:包含在收到的数据报中协议类型 UDP

//Udp 的回显服务器
//客户端发送的请求是啥,服务器返回的就是啥
public class UdpEchoServer {
    private DatagramSocket socket = null;

    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }
    //使用这个方法启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            //反复长期执行客户端请求处理的逻辑
            //一个服务器在运行过程中 要做的三件事 主要是三个核心环节
            //1.读取请求 并解析
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            //这样字符串的前提是: 后续客户端本身发送的就是一个文本的字符串
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            //2.根据请求,计算出响应
            String response = process(request);
            //3.把响应写回给客户端
            //需要告知网卡 发的内容是什么 发给谁
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            //记录日志 方便查看
            System.out.printf("[%s:%d] req: %s,resp: %s\n",requestPacket.getAddress().toString(),
                    requestPacket.getPort(),
                    request,response);
        }
    }
    //根据请求计算响应 由于是回显程序,响应内容和请求是完全一致的
    public String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}

为啥子服务器一上来就接收信息 而不是发送呢?

一个服务器要给需要客户端提供服务,服务器也不知道客户端啥时候来,只能随时准备着~

被动的一方:服务器;主动的一方:客户端


创建客户端,向服务器发送数据并且获取服务器返回的数据,并且打印到屏幕

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;

    //服务器的ip 服务器的端口
    public UdpEchoClient(String ip,int port) throws SocketException {
        serverIp = ip;
        serverPort = port;
        //这个new操作 就不定义端口了 让系统自动分配一个空闲端口
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        System.out.println("客户端启动!");
        Scanner scanner = new Scanner(System.in);
        while (true) {
            //1.从控制台读取用户输入
            System.out.printf("->");//命令提示符
            String request = scanner.next();
            //2.构造请求对象 并发给服务器
            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());
            //4.显示在屏幕上
            System.out.println(response);
        }
    }

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

1.先在控制台读取用户输入的数据

2.把用户输入的内容 构建成一个UDP请求而发送

3.从服务器读取响应数据并解析

4.把响应结果打印到到控制台

5.在客户端角度:

        源IP:本机IP;源端口:系统分配的端口

        目的IP:服务器的IP;目的端口:服务器的端口

        协议类型:UDP

启动上述代码并输入~

那么我们可以开启多个客户端吗?可以!

这样就可以开启多个服务端~ 同样可以运行~


TCP

分量比UDP更重 用的更多的协议

TCP提供的api也是主要的两个类

ServerSocket:给服务器使用的socket

Socket:即会给服务器用,也会给客户端使用 面向字节流,一个字节一个字节进行传输

一个TCP数据报就是一个字节数组 byte[]

TCP回显服务器

public class TcpEchoServer {
    private ServerSocket serverSocket = null;
    // 此处不应该创建固定线程数目的线程池.
    private ExecutorService service = Executors.newCachedThreadPool();

    // 这个操作就会绑定端口号
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    // 启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 这个写法, 是能自动关闭, 也行. 实现 Closeable 接口就可以这么写.
            // 这么写会有其他问题. (结合后面讲第二个问题, 再说这个事)
            Socket clientSocket = serverSocket.accept();

            // 使用线程池, 来解决上述问题
            service.submit(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });

            // 单个线程, 不太方便完成这里的一边拉客, 一边介绍. 就需要多搞线程. 主线程专门负责拉客. 每次有一个客户端, 都创建一个新的线程去服务
//            Thread t = new Thread(() -> {
//                processConnection(clientSocket);
//            });
//            t.start();

        }
    }

    // 通过这个方法来处理一个连接的逻辑.
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
        // 接下来就可以读取请求, 根据请求计算响应, 返回响应三步走了.
        // Socket 对象内部包含了两个字节流对象, 可以把这俩字节流对象获取到, 完成后续的读写工作
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            // 一次连接中, 可能会涉及到多次请求/响应
            while (true) {
                // 1. 读取请求并解析. 为了读取方便, 直接使用 Scanner.
                Scanner scanner = new Scanner(inputStream);
                if (!scanner.hasNext()) {
                    // 读取完毕, 客户端下线.
                    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                    break;
                }
                // 这个代码暗含一个约定, 客户端发过来的请求, 得是文本数据, 同时, 还得带有空白符作为分割. (比如换行这种)
                String request = scanner.next();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 把响应写回给客户端. 把 OutputStream 使用 PrinterWriter 包裹一下, 方便进行发数据.
                PrintWriter writer = new PrintWriter(outputStream);
                //    使用 PrintWriter 的 println 方法, 把响应返回给客户端.
                //    此处用 println, 而不是 print 就是为了在结尾加上 \n . 方便客户端读取响应, 使用 scanner.next 读取.
                writer.println(response);
                //    这里还需要加一个 "刷新缓冲区" 操作.
                writer.flush();

                // 日志, 打印当前的请求详情.
                System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(), clientSocket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 在 finally 中加上 close 操作, 确保当前 socket 被及时关闭!!
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

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

编写客户端

public class TcpEchoClient {
    private Socket socket = null;

    // 要和服务器通信, 就需要先知道, 服务器所在的位置.
    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        // 这个 new 操作完成之后, 就完成了 tcp 连接的建立.
        socket = new Socket(serverIp, serverPort);
    }

    public void start() {
        System.out.println("客户端启动");

        Scanner scannerConsole = new Scanner(System.in);

        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            while (true) {
                // 1. 从控制台输入字符串.
                System.out.print("-> ");
                String request = scannerConsole.next();
                // 2. 把请求发送给服务器
                PrintWriter printWriter = new PrintWriter(outputStream);
                //    使用 println 带上换行. 后续服务器读取请求, 就可以使用 scanner.next 来获取了
                printWriter.println(request);
                //    不要忘记 flush, 确保数据是真的发送出去了!!
                printWriter.flush();
                // 3. 从服务器读取响应.
                Scanner scannerNetwork = new Scanner(inputStream);
                String response = scannerNetwork.next();
                // 4. 把响应打印出来
                System.out.println(response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值