计算机网络——Socket实验2

目录

一、实验目的

二、实验内容

三、实验环境

四、实验步骤与过程

1 套接字Socket

1.1 什么是Socket

1.2 Socket的TCP通信

1.3 Socket的UDP通信

2 Socket类与ServerSocket类编写一个C/S程序

扫描二维码关注公众号,回复: 14750741 查看本文章

附录

Server.java

Client.java

ChatRoom.java


一、实验目的

掌握Socket的TCP通信、 Socket的UDP通信

二、实验内容

Socket、ServerSocket类和DatagramPacket 、DatagramSocket类的使用

三、实验环境

使用Windows操作系统;Internet连接

IDEA+Java开发环境

四、实验步骤与过程

1 套接字Socket

1.1 什么是Socket

套接字是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口

套接字主要分为以下三种:

  1. 流套接字(SOCK_STREAM):流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP协议。
  2. 数据报套接字(SOCK_DGRAM):数据报套接字提供一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。
  3. 原始套接字(SOCK_RAW):原始套接字与标准套接字(即流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接

在具体使用套接字时,常常需要使用一对套接字而不是一个套接字,其中一个运行于客户端,即Client Socket,另一个运行于服务器端,即Server Socket。

根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:

① 服务器监听:所谓服务器监听,是指服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。

② 客户端请求:所谓客户端请求,是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端接字提出连接请求。

③ 连接确认:所谓连接确认,是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,就会响应客户端套接字的请求,建立一个新的线程,并把服务器端套接字的描述发送给客户端。一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,接收其他客户端套接字的连接请求。

此外,根据套接字的不同类型,可以将套接字调用分为面向连接服务和无连接服务。其中,面向连接的服务有着可靠性高,通信效率不高,数据传输过程必须经过建立连接、维护连接和释放连接3个阶段的特点,并且在传输过程中,各分组不需要携带目的主机的地址。而面向无连接的服务通信效率高,但可靠性相对较差,数据传输过程中不需要连接各个阶段,并且每个分组都携带完整的目的主机地址,在系统中独立传送。此外由于没有顺序控制,所以接收方的分组可能出现乱序、重复和丢失现象。

1.2 Socket的TCP通信

Java中提供了Socket和ServerSocket类作为标准的TCP套接字编程技术,通过它们实现主机与主机之间(应用程序间)的对话。这两个类位于:java.net包中。

ServerSocket 类是与 Socket 类相对应的用于表示通信双方中的服务器端,用于在服务器上开一个端口,被动地等待数据(使用 accept() 方法)并建立连接进行数据交互。服务器套接字一次可以与一个套接字连接,如果多台客户端同时提出连接请求,服务器套接字会将请求连接的客户端存入队列中,然后从中取出一个套接字与服务器新建的套接字连接起来。若请求连接大于最大容纳数,则多出的连接请求被拒绝;默认的队列大小是 50。ServerSocket类提供了如下的构造方法:

  • ServerSocket():无参构造方法。
  • ServerSocket(int port):创建绑定到特定端口的服务器套接字。
  • ServerSocket(int port,int backlog):使用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口。
  • ServerSocket(int port,int backlog,InetAddress bindAddr):使用指定的端口、监听 backlog 和要绑定到本地的 IP 地址创建服务器。

在上述方法的参数中 port 指的是本地 TCP 端口,backlog指的是监听backlog,bindAddr 指的是要将服务器绑定到的 InetAddress。此外,创建 ServerSocket 时可能会拋出 IOException 异常,所以要进行异常捕捉。

ServerSocket类有如下的常用方法:

  • Server accept():监听并接收到此套接字的连接。
  • void bind(SocketAddress endpoint):将 ServerSocket 绑定到指定地址(IP 地址和端口号)。
  • void close():关闭此套接字。
  • InetAddress getInetAddress():返回此服务器套接字的本地地址。
  • int getLocalPort():返回此套接字监听的端口。
  • SocketAddress getLocalSoclcetAddress():返回此套接字绑定的端口的地址,如果尚未绑定则返回 null。
  • int getReceiveBufferSize():获取此 ServerSocket 的 SO_RCVBUF 选项的值,该值是从 ServerSocket 接收的套接字的建议缓冲区大小。

调用 accept() 方法会返回一个和客户端 Socket 对象相连接的 Socket 对象,服务器端的 Socket 对象使用 getOutputStream() 方法获得的输出流将指定客户端 Socket 对象使用 getInputStream() 方法获得那个输入流。同样,服务器端的 Socket 对象使用的 getInputStream() 方法获得的输入流将指向客户端 Socket 对象使用的 getOutputStream() 方法获得的那个输出流。也就是说,当服务器向输出流写入信息时,客户端通过相应的输入流就能读取,反之亦然。

Socket 类表示通信双方中的客户端,用于呼叫远端机器上的一个端口,主动向服务器端发送数据(当连接建立后也能接收数据)。下面简单介绍一下 Socket提供了如下的构造方法:

  • Socket():无参构造方法。
  • Socket(InetAddress address,int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口。
  • Soclcet(InetAddress address,int port,InetAddress localAddr,int localPort):创建一个套接字并将其连接到指定远程地址上的指定远程端口。
  • Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指定端口。
  • Socket(String host,int port,InetAddress localAddr,int localPort):创建一个套接字并将其连接到指定远程地址上的指定远程端口。Socket 会通过调用 bind() 函数来绑定提供的本地地址及端口。

在上述方法的参数中,address 指的是远程地址,port 指的是远程端口,localAddr 指的是要将套接字绑定到的本地地址,localPort 指的是要将套接字绑定到的本地端口。

Socket类有如下的常用方法:

  • void bind(SocketAddress bindpoint):将套接字绑定到本地地址。
  • void close():关闭此套接字。
  • void connect(SocketAddress endpoint):将此套接字连接到服务器。
  • InetAddress getInetAddress():返回套接字的连接地址。
  • InetAddress getLocalAddress():获取套接字绑定的本地地址。
  • InputStream getInputStream():返回此套接字的输入流。
  • OutputStream getOutputStream():返回此套接字的输出流。
  • SocketAddress getLocalSocketAddress():返回此套接字绑定的端点地址,如果尚未绑定则返回 null。
  • SocketAddress getRemoteSocketAddress():返回此套接字的连接的端点地址,如果尚未连接则返回 null。
  • int getLoacalPort():返回此套接字绑定的本地端口。
  • intgetPort():返回此套接字连接的远程端口。

1.3 Socket的UDP通信

UDP通信是一种无连接的数据报通信。使用该协议,两个程序进行通信时不用建立连接;数据以独立的包为单位发送,包的容量限定在64KB以内;每个数据报需要有完整的收/发地址,可以随时进行收/发数据报,但不保证传送顺序和内容准确;数据报可能会被丢失、延误等。UDP通信是不可靠的通信,但通信速度较快,常常被应用在某些要求实时交互,准确性要求不高,但传输速度要求较高的场合(如视频会议系统等)。

Java中,基于UDP协议实现网络通信的类有三个:

  • 用于表示通信数据的数据报类:DatagramPacket
  • 用于进行端到端通信的类:DatagramSocket
  • 用于广播通信的类:MulticastSocket

本次实验着重介绍前两个。

java.net 包中的 DatagramPacket 类用来表示数据报包,数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。DatagramPacket有如下的常用构造方法:

  • DatagramPacket(byte[] buf,int length) 构造 DatagramPacket,用来接收长度为 length 的数据包。
  • DatagramPacket(byte[] buf,int offset,int length) 构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量。
  • DatagramPacket(byte[] buf,int length, InetAddress address,int port)构造 DatagramPacket,用来将长度为 length 的包发送到指定主机上的指定端口。
  • DatagramPacket(byte[] buf,int length, SocketAddress address)构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口。
  • DatagramPacket(byte[] buf,int offset, int length,InetAddress address,int port)构造 DatagramPacket,用来将长度为 length 偏移量为 offset的包发送到指定主机上的指定端口。
  • DatagramPacket(byte[] buf,int offset, int length,SocketAddress address)构造数据报包,用来将长度为 length、偏移量为 offset 的包发送到指定主机上的指定端口。

DatagramPacket类有如下的常用方法:

  • InetAddress getAddress() 返回某台机器的 IP 地址,此数据报将要发往该机器或者从该机器接收。
  • byte[] getData() 返回数据缓冲区。
  • int getLength() 返回将要发送或者接收的数据的长度。
  • int getOffset() 返回将要发送或者接收的数据的偏移量。
  • int getPort() 返回某台远程主机的端口号,此数据报将要发往该主机或者从该主机接收。
  • getSocketAddress() 获取要将此包发送或者发出此数据报的远程主机的
  • SocketAddress(通常为 IP地址+端口号)。
  • void setAddress(InetAddress addr) 设置要将此数据报发往的目的机器的IP地址。
  • void setData(byte[] buf) 为此包设置数据缓冲区。
  • void setData(byte[] buf,int offset,int length) 为此包设置数据缓冲区。
  • void setLength(int length) 为此包设置长度。
  • void setPort(int port) 设置要将此数据报发往的远程主机的端口号。
  • void setSocketAddress(SocketAddress address) 设置要将此数据报发往的远程主机的
  • SocketAddress 通常为 IP地址+端口号)。

DatagramSocket 类用于表示发送和接收数据报包的套接字。数据报包套接字是包投递服务的发送或接收点。每个在数据报包套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。DatagramSocket有如下的常用构造方法:

  • DatagramSocket() 构造数据报包套接字并将其绑定到本地主机上任何可用的端口。
  • DatagramSocket(int port) 创建数据报包套接字并将其绑定到本地主机上的指定端口。
  • DatagramSocket(int portJnetAddress addr) 创建数据报包套接字,将其绑定到指定的本地地址。
  • DatagramSocket(SocketAddress bindaddr) 创建数据报包套接字,将其绑定到指定的本地套接字地址。

DatagramSocket类有如下的常用方法:

  • void bind(SocketAddress addr) 将此 DatagramSocket 绑定到特定的地址和端口。
  • void close() 关闭此数据报包套接字。
  • void connect(InetAddress address,int port) 将套接字连接到此套接字的远程地址。
  • void connect(SocketAddress addr) 将此套接子连接到远程套接子地址(IP地址+端口号)。
  • void disconnect() 断开套接字的连接。
  • InetAddress getInetAddress() 返回此套接字连接的地址。
  • InetAddress getLocalAddress() 获取套接字绑定的本地地址。
  • int getLocalPort() 返回此套接字绑定的本地主机上的端口号。
  • int getPort() 返回此套接字的端口。

综上,在 Java 中使用 UDP 协议发送数据时需要先使用 DatagramSocket() 创建一个数据包套接字,然后用 DatagramPacket() 创建要发送的数据包,最后用 DatagramSocket 类的 send() 方法发送数据包。接收 UDP 数据包时需要先使用 DatagramSocket 创建数据包套接字,并将其绑定到指定的端口,然后使用 DatagramPacket 创建字节数组来接收数据包,最后使用 DatagramPacket 类的 receive() 方法接收 UDP 包。

2 Socket类与ServerSocket类编写一个C/S程序

接下来,我们将利用前面所学,完成一个C/S程序的编写。C/S程序包括客户端和服务器端两部分。首先编写客户端代码如图 1:

1 C/S程序中客户端代码

       首先,在前两行引入所必须的包。在第7行建立Socket套接字连接后,在10-12行通过套接字连接获得对应输入输出流。并通过循环读入,在第15行将本地的输入循环发送给服务器。第17行获取服务器返回的信息,输出并在第20行进行判断,如果是结束,则终止循环并结束,否则继续获取输入即可。

2 C/S程序中服务器端代码

       编写服务器端代码如图 2,首先,在前三行引入所必须的包。在第8行创建ServerSocket对象对10001端口进行监听,并通过第10行的accept方法获取由客户端发来的数据包。在第12,13行获取数据包的输入和输出。然后利用第16行的循环获取数据包,当从客户端获得的信息是“Time” 时,则返回当前的服务器时间;当从客户端获得的信息是“Exit”时,返回“Bye”,并结束循环。退出循环时,在第32到第35行关闭所有流和socket。

       接下来将对C/S程序进行测试。首先,运行Server.java开启服务端Socket

3 开启服务端

       如图 3,开启服务端之后,服务端输出“Server is on”。然后再运行Client.java,开启客户端并建立Socket连接。

4 建立Socket连接

       如图 4,开启客户端之后,服务器端的accept方法成功获取到了客户端的Socket。

5 客户端发送“Time”

       如图 5,客户端发送Time后,服务器端返回当前的服务器时间。

6 C/S程序结束

       如图 6,客户端发送Exit后,将返回“Bye”,并结束程序。至此,我们的C/S程序已经编程并测试完毕。

3 实现简单的数据报通信聊天软件

7 引入头文件

如图 7,首先引入实现数据报对应所需的包。

8 定义对应的变量

       如图 8,定义对应的变量以及GUI元素,以便后面进行调用。

9 聊天软件的GUI设置

       如图 9,定义了整个聊天软件的GUI设置。在第31行设置了UI界面的标题,32和33行设置了UI界面的大小和关闭操作。然后34-36行定义了选择按钮的面板,并将其加入到界面中。37-40行定义了文本框,并将可编辑设置为false,用以显示聊天记录。41-44行设置布局,采用BorderLayout进行布局。45-50行将“发送”,“清空”和“退出”按钮加入布局。51-59行获取IP和地址,并进行回显。最后将布局设置为可见,并调用获取信息方法以及为三个按钮加入监听。

图 10 获取消息方法

       70-74行通过创建socket数据包,76-77行定义DatagramPacket对象。为了方便获取信息,在第78-96用Lambda表达式创建了线程对象以获取信息,并在第97行完成线程的启用。

11 定义鼠标事件方法

       如图 11,定义了鼠标事件的方法,第103-109行为发送按钮,直接调用发送信息的方法并用catch捕获异常即可,第110-112行和第113-115行为清空和退出按钮,调用对应方法即可。

12 退出函数

       如图 12,第120-122行,退出函数直接调用系统函数进行退出即可。

13 清空函数

       如图 13,清空函数将文本框内的内容设置为空即可。

14 发送信息函数

       如图 14,发送信息函数通过第133-138行,使用IP地址以及报文内容建立数据报对象。并在第139行将内容回显。在第140-144行,将数据报发送。完成发送数据报后,在145行清空输入框。

15 主函数

       如图 15,主函数调用写好的类以及对应的函数,直接传入发送者和接受者的端口和昵称即可。

       接下来,将进行测试。

16 运行聊天软件

       首先,运行聊天软件,界面如图 16,可以看到,上方有我们设计好的发送,清空,退出三个按钮。中间是历史记录显示区,最下方是发送窗口。

17 测试发送一条消息

       接着,我们测试发送一条消息,如图 17,两侧的消息记录区将同步显示消息记录。

18 使用另一个账户发送一条消息

       接着,我们使用另一个账户发送一条消息,如图 18,两侧的消息记录区将同步显示消息记录,并且也会显示发送者的名字。

19 测试清屏

       我们还设置了清屏功能,如图 19,点击“Clear”按钮即可进行清屏。

图 20 清屏后发送一条消息

       清屏后,依然可以发送消息。不清屏的界面将显示全部历史记录,清屏的界面将显示从清屏之后的历史记录。

21 结束并退出

       最后,点击“Exit”按钮即可退出程序,结果如图 21。至此,我们已经完成了所有的聊天软件编程。

附录

Server.java

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

public class Server {
    public static void main(String[] args) throws IOException {
        //在10001端口监听
        ServerSocket server_socket = new ServerSocket(10001);
        System.out.println("Server is on.");
        Socket socket = server_socket.accept();
        System.out.println("Connection to client is created");
        DataInputStream in = new DataInputStream(socket.getInputStream());
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());
        //从客户端读取信息
        String str = in.readUTF();
        while (true) {
            //当客户端发送的信息是"Time"时服务器返回当前时间
            if (str.equals("Time")) {
                Date date = new Date();
                out.writeUTF("服务器当前时间为: " + date);
                System.out.println("服务器当前时间为: " + date);
            }
            //客户端发送的信息是"Exit"时服务器返回"Bye"并退出
            else if (str.equals("Exit")) {
                out.writeUTF("Bye");
                System.out.println("Bye");
                break;
            }
            str = in.readUTF();
        }
        //关闭所有流和socket
        in.close();
        out.close();
        socket.close();
        server_socket.close();
    }
}


Client.java

import java.net.*;
import java.io.*;

public class Client {
    public static void main(String[] args) throws IOException {
        //创建socket对象并连接到本地10001端口的服务器
        Socket socket = new Socket("localhost", 10001);
        System.out.println("Connection created.");
        //获得socket的输入输出流和获得系统标准输入
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        DataInputStream in = new DataInputStream(socket.getInputStream());
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());
        String str = input.readLine();
        while (true) {
            out.writeUTF(str);
            //发送输入给服务器
            String out_str = in.readUTF();
            //读取信息
            System.out.println(out_str);
            if (out_str.equals("Bye")) {
                break;
            }
            str = input.readLine();
        }
    }
}

ChatRoom.java

import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.net.*;
import javax.swing.*;

public class ChatRoom extends JFrame implements ActionListener {

    private final JTextArea text;
    private final JTextField ip_text;
    private final JTextField send_text;

    private final JButton button_exit;
    private final JButton button_clear;
    private final JButton button_send;

    private DatagramSocket socket;

    private final int port_send;
    private final int port_receive;
    private final String sender;
    private final String receiver;

    /****************************   GUI代码    ******************************/
    public ChatRoom(String sender, String receiver, int port_sender, int port_receiver)
            throws UnknownHostException {
        port_send = port_sender;
        port_receive = port_receiver;
        this.sender = sender;
        this.receiver = receiver;
        setTitle("UDP Chat Program");
        setBounds(100, 100, 400, 300);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout());
        JPanel select_bar = new JPanel(new FlowLayout());
        add(select_bar, BorderLayout.NORTH);
        text = new JTextArea();
        text.setEditable(false);
        JScrollPane textPanel = new JScrollPane(text);
        add(textPanel, BorderLayout.CENTER);
        JPanel panel = new JPanel();
        BorderLayout panelLayout = new BorderLayout();
        panelLayout.setHgap(8);
        panel.setLayout(panelLayout);
        button_send = new JButton("Send");
        button_clear = new JButton("Clear");
        button_exit = new JButton("Exit");
        select_bar.add(button_send);
        select_bar.add(button_clear);
        select_bar.add(button_exit);
        InetAddress inetaddress = InetAddress.getLocalHost();
        String local_address = inetaddress.getHostAddress();
        ip_text = new JTextField(local_address);
        JTextField host_text = new JTextField(sender);
        panel.add(host_text, BorderLayout.WEST);
        send_text = new JTextField();
        panel.add(send_text, BorderLayout.CENTER);
        add(panel, BorderLayout.SOUTH);
        setVisible(true);
        Get_Message();
        button_send.addActionListener(this);
        button_clear.addActionListener(this);
        button_exit.addActionListener(this);
    }

    /****************************    获得信息的方法    ******************************/

    private void Get_Message() {
        //创建数据包socket
        try {
            socket = new DatagramSocket(port_receive);
        } catch (SocketException e) {
            e.printStackTrace();
        }
        //用buffer存储数据
        byte[] buffer = new byte[1024];
        final DatagramPacket data = new DatagramPacket(buffer, buffer.length);
        Runnable runnable = () -> {
            while (true) {
                try {
                    //用线程控制命令
                    Thread.sleep(100);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                try {
                    socket.receive(data);
                } catch (IOException e) {
                    e.printStackTrace();
                }

                //输出接收到的消息
                String message = new String(data.getData(), 0, data.getLength());
                text.append(receiver + ":\n  " + message + "\n");
            }
        };
        new Thread(runnable).start();
    }

    /****************************   定义鼠标事件的方法    ******************************/

    public void actionPerformed(ActionEvent ev) {
        if (ev.getSource() == button_send) {
            try {
                Send_Message();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
        if (ev.getSource() == button_clear) {
            Clear();
        }
        if (ev.getSource() == button_exit) {
            Exit();
        }
    }

    /****************************   Exit按钮    ******************************/

    private void Exit() {
        System.exit(0);
    }

    /****************************   Clear按钮    ******************************/

    private void Clear() {
        text.setText("");
    }

    /****************************   Send按钮   ******************************/

    public void Send_Message() throws UnknownHostException {
        String ip = ip_text.getText();
        InetAddress inetaddress;
        inetaddress = InetAddress.getByName(ip);

        byte[] data = send_text.getText().getBytes();
        DatagramPacket data_package = new DatagramPacket(data, data.length, inetaddress, port_send);
        text.append(sender + ":\n  " + send_text.getText() + "\n");
        try {
            socket.send(data_package);
        } catch (IOException e) {
            e.printStackTrace();
        }
        send_text.setText(null);
    }

    public static void main(String[] args) throws UnknownHostException {
        //创建两个聊天室
        ChatRoom room_one = new ChatRoom("szudyh1", "szudyh2", 7654, 7655);
        room_one.setVisible(true);
        ChatRoom room_two = new ChatRoom("szudyh2", "szudyh1", 7655, 7654);
        room_two.setVisible(true);
    }

}

猜你喜欢

转载自blog.csdn.net/m0_46326495/article/details/124224069
今日推荐