网络编程笔记——套接字

文章目录

  • 1、什么是网络编程?
    • 1、网络编程的基本概念:
    • 2、请求和响应
    • 3、客户端和服务端
  • 2、Socket套接字
    • 1、什么是Socket套接字?
    • 2、Socket套接字的分类
  • 3、数据报套接字通信模型(UDP)
  • 4、UDP套接字编程
    • 1、DatagramSocket API
    • 2、DatagramPacket API
    • 3、InetSocketAddress API
    • 4、写一个简单的回显服务器(请求是啥,响应就是啥)
    • 5、写一个简单的查找服务器
  • 5、TCP套接字编程

1、什么是网络编程?

网络上的主机通过不同的进程,以编程的方式实现网络通信,我们称之为网络编程。
我们只要满足不同的进程即可,所以即便是同一个主机,只要是不同的进程,基于网络传输数据,也是属于网络编程


1、网络编程的基本概念:

在一次网络数据传输时,有发送端、接收端、收发端三方
在这里插入图片描述
注意:发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。

2、请求和响应

一般来说,获取一个网络资源,涉及到两次网络数据传输:
第一次:请求数据的发送;
第二次:响应数据的发送;
网络编程笔记——套接字_第1张图片

3、客户端和服务端

服务端:提供服务的一方进程,成为服务端,可以提供对外服务;
客户端:获取服务的一方进程,成为客户端;
对于服务来说,一般提供1、客户端获取服务资源。2、客户端保存资源在服务端


2、Socket套接字

1、什么是Socket套接字?

由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。
基于Socket套接字的网络程序开发就是网络编程。

2、Socket套接字的分类

Socket套接字主要针对传输层协议,分为三类:
1、流套接字:使用传输层TCP协议
TCP的特点:有连接、可靠传输、面向字节流,全双工
对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情
况下,是无边界的数据,可以多次发送,也可以分开多次接收。

2、数据报套接字:使用传输层UDP协议
UDP的特点:无连接、不可靠传输、面向数据报,全双工
对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如100个字节,必须一
次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节。

3、原始套接字

3、数据报套接字通信模型(UDP)

网络编程笔记——套接字_第2张图片
网络编程笔记——套接字_第3张图片
这就是一次发送端UDP数据报发送,即接收端的数据报接收,并没有返回的数据,也就是只有请求,没有响应;


4、UDP套接字编程

1、DatagramSocket API

DatagramSocket 是UDP Socket,用于接收和发送数据报;
认识一下DatagramSocket 的构造方法
网络编程笔记——套接字_第4张图片
普通方法

网络编程笔记——套接字_第5张图片

2、DatagramPacket API

DatagramPacket是UDP Socket发送和接收的数据报;
即socket.receive()的参数都是DatagramPacket类型的;
认识一下:DatagramPacket 构造方法
网络编程笔记——套接字_第6张图片
普通方法
网络编程笔记——套接字_第7张图片
构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。
注意:

每次接收(receive)、发送数据(send)都是在传输一个DatagramPacket 对象

3、InetSocketAddress API

InetSocketAddress ( SocketAddress 的子类 )构造方法:
在这里插入图片描述

4、写一个简单的回显服务器(请求是啥,响应就是啥)

服务端部分:

public class UdpEchoSever {
    DatagramSocket socket = null;

    public UdpEchoSever(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("启动服务端!");
        //Udp是不需要连接的,直接接收数据即可

        while(true){
            //第一步:读取客户端发来的需求
            //需求必须是由DatagramPacket类型的容器来放,由receive读取并填充到DatagramPacket对象中
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
            socket.receive(requestPacket);

            //请求一般是String的,所以把DatagramPacket解析成String
            String request = new String(requestPacket.getData(),0,requestPacket.getLength(),"UTF-8");
                                                         //      得到的长度不一定是1024,实际数据可能不够1024

            //第二步:根据请求计算响应
            String response = process(request);

            //第三步:把响应写会客户端
             DatagramPacket reponsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
            socket.send(reponsePacket);
             System.out.printf("[%s:%d] req: %s,resp:%s\n",reponsePacket.getAddress().toString(),responsePacket.getPort(),
                                                            request,response);
        }
    }
    private String process(String request){
        return request;
    }

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

网络编程笔记——套接字_第8张图片
网络编程笔记——套接字_第9张图片
客户端部分:

public class UdpEchoClient {
    /**
     * 五元组
     * 1、源ip
     * 2.源端口
     * 3、目的ip
     * 4、目的端口
     * 5.协议类型(UDP/TCP)
     */
    DatagramSocket socket = null;
    private String severIP;
    private int severPort;

    public UdpEchoClient(String ip, int port) throws SocketException {
        //此处的ip和port都是服务端的
        socket = new DatagramSocket();
        severIP = ip;
        severPort = port;
    }

    public UdpEchoClient() throws SocketException {
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        Scanner sc = new Scanner(System.in);
        while(true){
            //1、从控制台得到用户输入的字符串
            System.out.print("-> ");
            String request = sc.nextLine();

            //2、把这个字符串打包成一个UDP请求、并发送(明确知道服务器的IP和端口号)
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                                                              InetAddress.getByName(severIP),severPort);

            socket.send(requestPacket);

            //3、从服务器读取相应数据,并解析
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(),0,requestPacket.getLength(),"UTF-8");

            //4、打印响应数据
            System.out.printf("req: %s,resp: %s\n",request,response);
        }

    }

    public static void main(String[] args) throws IOException {
        //由于服务器和客户端在同一个机器上,所以ip仍然是127.0.0.1,如果是其他机器,ip地址就要改动
        UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090);
        udpEchoClient.start();
    }
}

同时启动客户端和服务端,我们就可以运行一个简单的回显服务器,但是IDEA默认设置只有一个实例,如果启动多个实例,就会销毁之前的实例,因此如果我们想启动多个实例,就要修改一下运行配置,如下图:
网络编程笔记——套接字_第10张图片
下图是打开三个客户端和一个服务端的情况:
网络编程笔记——套接字_第11张图片

5、写一个简单的查找服务器

简述该服务器:输入名字,如果存在该同学,就输出该同学对应的号码,否则输出“错误”;
在上面的服务器的基础上,重新写一个服务器继承上面的服务器,客户端不需要改动,看代码示例 :

public class UdpFindSever extends UdpEchoSever{
    private HashMap<String,String> dict = new HashMap<>();//哈希表来存储所有同学信息

    //父类只声明了有参的构造函数,所以子类必须重写父类有参的构造方法
    public UdpFindSever(int port) throws SocketException {
        super(port);

        dict.put("王某某","111222333");
        dict.put("李某某","145634788");
        dict.put("赵某某","009909876");
        dict.put("钱某某","475839583");

    }

    @Override
    public String process(String request) {
        return dict.getOrDefault(request,"错误!");
    }

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

结果:
网络编程笔记——套接字_第12张图片


5、TCP套接字编程

网络编程笔记——套接字_第13张图片

服务端

package network;

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

public class TcpThreadEchoSever {
    private ServerSocket severSocket = null;

    public TcpThreadEchoSever(int port) throws IOException {
        severSocket = new ServerSocket(port);

    }

    public void start() throws IOException {
        System.out.println("服务器启动!");

        while(true){
            /**
             * 1、TCP是有连接的,所以不能直接读取数据,要先建立连接
             * 2、accept就是在建立连接,如果没有客户端建立连接,accept就会阻塞
             * 3、如果有客户端建立连接,accept就会返回一个socket对象,称之为clientSocket,后续客户端和服务器的沟通就是通过clientSocket来完成的
             *
             */

            Socket clientSocket = severSocket.accept();
            //利用多线程来改进,每有一个客户端申请连接,服务端就会有一个线程进行接待,因此可以做到一个服务端接收多个客户端
            Thread thread = new Thread(()->{
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }

            });
            thread.start();

        }



    }
    public void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端建立连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());

        /**
         * 处理请求和响应
         */
        try(InputStream inputStream = clientSocket.getInputStream()){
            try(OutputStream outputStream = clientSocket.getOutputStream()) {
                //循环处理每个需求
                Scanner sc = new Scanner(inputStream);
                while(true){
                    //1、读取请求
                    if(!sc.hasNext()){
                        System.out.printf("[%s:%d],客户端断开连接\n",clientSocket.getInetAddress().toString(),
                                clientSocket.getPort());
                        break;
                    }

                    String request = sc.nextLine();

                    //2.根据请求,计算响应
                    String response = process(request);

                    //3.把响应返回给客户端
                    PrintWriter printWriter = new PrintWriter(outputStream);//把outputStream用PrintWriter包裹一下
                    printWriter.println(response);

                    //刷新缓冲区
                    printWriter.flush();
                    System.out.printf("[%s:%d] req: %s,resp: %s\n",clientSocket.getInetAddress().toString(),
                            clientSocket.getPort(),request,response);

                }

            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            clientSocket.close();
        }
    }
    private String process(String request){
        return request;
    }

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

客户端

package network;

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

public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String severIP,int severport) throws IOException {
        /**
         * 这里的ip和端口号是我们要连接的服务器的iip和端口号,并不是定义的客户端的ip和端口号
         */
        socket = new Socket(severIP,severport);

    }
    public void start(){
        System.out.println("和服务端连接成功!");

        Scanner sc = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream()){
            try(OutputStream outputStream = socket.getOutputStream()){

                while(true){
                    //1、从控制台读取用户需求
                    System.out.printf("-> ");
                    String request = sc.next();

                    if(request.equals("exit")){
                        System.out.println("断开连接!\n");
                        break;
                    }

                    //2、将独到的内容打包成请求,并发送给服务端
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(request);

                    printWriter.flush();

                    //3、读取服务器响应
                    Scanner respScanner = new Scanner(inputStream);
                    String response = respScanner.next();

                    //4、将相应内容显示在控制台上
                    System.out.printf("req: %s,resp: %s\n",request,response);
                }
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

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

结果:
网络编程笔记——套接字_第14张图片

注意:
1、因为TCP是有连接通信,所以我们要用多线程的方式来接收客户端的请求,否则一个服务端只能链接客户端,当我们用多线程的方式来接待客户端,此时一个服务端就可以调用多个线程来对应的响应多个客户端;
2、为什么UDP版本不需要使用多线程?
因为UDP是无连接通信,客户端不需要服务端确认接收也可以发送,简言之就是UDP没有TCP依赖性强;UDP就像是发信息,直接发送内容即可,不需要确认对方是否正在盯着手机看。但是TCP就像是打电话,我们拨电话之后,要等到对方接电话才可以说谈话内容,如果对方不接电话,我们就没法进行沟通。在TCP版本的情况下运用多线程,就是我们拨电话之后,对方派A和我们沟通,又来一个电话,对方派B来沟通。

你可能感兴趣的:(计算机网络,网络,网络协议,tcp/ip)