深圳大学计算机网络实验五:Socket编程

更好的阅读体验

IP Address

网络编程模型

image-20220403113204888

InetAddress

  • InetAddress类主要是用来得到所指定的网络地址
  • InetAddress类没有直接显式的构造函数。要生成一个InetAddress对象,必须运用一个可用的工厂方法。工厂方法(factory method)仅是一个类中的静态方法返回一个该类实例的约定。这是在一个带有各种参数列表的重载构造函数中完成的,当持有惟一方法名时可使结果更清晰。
  • InetAddress有三个方法可以用来创建InetAddress的实例
    • static InetAddress getLocalHost( ) throws UnknownHostException
    • static InetAddress getByName(String hostName) throws UnknownHostException
    • static InetAddress[ ] getAllByName(String hostName) throws UnknownHostException

InetAddress类的非静态方法

  1. boolean equals(Object other)
    如果对象具有和other相同的Internet地址则返回true。
  2. byte[ ] getAddress( )
    返回此InetAddress对象的原始 IP 地址。
  3. String getHostAddress( )
    返回与InetAddress对象相关的主机地址的字符串。
  4. String getHostName( )
    返回与InetAddress对象相关的主机名的字符串。
  5. int hashCode( )
    返回调用对象的散列码。
  6. boolean isMulticastAddress( )
    如果Internet地址是一个多播地址则返回true;否则返回false。
  7. String toString( )
    返回主机名字符串和IP地址。

URL

  • URL类封装了使用统一资源定位器(Uniform Resource Locator)访问一个WWW上的资源的方法。这个类可以生成一个寻址或指向某个资源的对象。URL类生成的对象指向WWW资源(Web页、文本文件、图形文件、声频片段等等)
  • URL的基本表示方法是:Protocol://hostname:port/resourcename#anchor,即 协议://主机名:端口/资源名#标记

构造方法

  1. public URL (String spec)
    • 通过一个表示URL地址的字符串可以构造一个URL对象。如以下语句:
    • URL urlBase=new URL("http://www. 263.net/");
  2. public URL(URL context, String spec)
    • 通过基URL和相对URL构造一个URL对象。如以下语句:
    • `URL net263=new URL (“http://www.263.net/”);``
    • ``URL index263=new URL(net263, “index.html”)`
  3. public URL(String protocol, String host, String file);
    URL url=new URL("http", "www.gamelan.com", "/pages/Gamelan.net. html");
  4. public URL(String protocol, String host, int port, String file);
    URL gamelan=new URL("http", "www.gamelan.com", 80, "Pages/Gamelan.network.html");

URL常用方法

  • public String getProtocol() 获取该URL的协议名。
  • public String getHost() 获取该URL的主机名。
  • public int getPort() 获取该URL的端口号,如果没有设置端口,返回-1。
  • public String getFile() 获取该URL的文件名。
  • public String getQuery() 获取该URL的查询信息。
  • public String getPath() 获取该URL的路径。
  • public String getAuthority() 获取该URL的权限信息。
  • public String getUserInfo() 获得使用者的信息。
  • public String getRef() 获得该URL的引用。

URL类的应用

  • URL类经常用于下载网络资源,URL通过构造函数(构造函数为URL地址)可以得到一个对象,该对象的openStream()方法可以得到InputStream对象,得到InputStream就可以把网站上的资源下载下来了

  • 下面是一个实例,使用URL类下载某个网站上的一张图片并保存到本地。

    import java.net.*;
    import java.io.*;
    public class TestURL
    {
        public static void main(String aregs[ ])throws Exception
        {
            URL url=new URL("http://images.sohu.com/uiue/sohu_logo/beijing2008/sohu.gif");
            InputStream in=url.openStream();
            FileOutputStream fout=new FileOutputStream(new File("sohu.gif"));
            int a=0;
            while(a>-1)
            {
                a=in.read();
                fout.write(a);
            }
        }
    } 
    

    运行成功后,在当前目录生成一个gif图片

URLConnection

  • URL类中的openConnection()方法可生成一个URLConnection对象,URLConnection类的实例可用于读取和写入此URL引用的资源。
  • 在网络编程工作中,JAVA的URLConnection是一个常用的类,它提供了一个非常方便的接口,只要提供需要连接的主机的URL地址,使用URL类的openConnection()方法就可以得到一个HttpURLConnection的对象,其中HttpURLConnection类是URLConnection类的子类,然后在此基础上分析HTTP内容,完成相关任务。在这种方式下,使用者不必考虑底层的实现细节,避免了烦琐的Socket类的代码编写,因此比较常用。

URLConnection常用方法

  • String getHeaderField(String name)
    返回指定的头字段的值。
  • InputStream getInputStream()
    返回在此打开的连接读取的输入流。
  • String getContentEncoding()
    返回content-encoding头字段的值。
  • int getContentLength()
    返回content-length头字段的值。
  • String getContentType()
    返回content-type头字段的值。
  • long getDate()
    返回date头字段的值。

练习

  1. 使用InetAddress类的方法获取本地机的名称和IP地址

    package com.ipaddress;
    import java.net.*;
    
    public class IPAddress {
        public static void main(String[] args) {
            try {
                InetAddress inetAddress = InetAddress.getLocalHost();
                String hostName = inetAddress.getHostName();
                String hostAddress = inetAddress.getHostAddress();
                System.out.println(hostName);
                System.out.println(hostAddress);
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    打印结果:

    MacBook-Air.local
    192.168.0.101
    
  2. 使用InetAddress类的方法获取网站www.csdn.net的IP地址,如果存在多个IP地址,要求全部返回。

    package com.ipaddress;
    import java.net.*;
    public class CSDNAddress {
        public static void main(String[] args) {
            try {
                InetAddress[] inetAddresses = InetAddress.getAllByName("www.csdn.net");
                for (InetAddress inetAddress: inetAddresses) {
                    String hostName = inetAddress.getHostName();
                    String hostAddress = inetAddress.getHostAddress();
                    System.out.println(hostName + " " + hostAddress);
                }
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
    }
    

    打印结果

    www.csdn.net 39.106.226.142
    
  3. 使用URL类下载深圳大学首页http://www.szu.edu.cn,并统计下载得到网页文件的大小

    package com.ipaddress;
    
    import java.net.*;
    import java.io.*;
    
    public class SZUurl {
        public static void main(String[] args) throws Exception {
            URL url = new URL("http://www.szu.edu.cn");
            URLConnection connection = url.openConnection();
            int size = connection.getContentLength();
            InputStream in = url.openStream();
            FileOutputStream fout = new FileOutputStream(new File("szu.html"));
            int a = 0;
            while(a > -1) {
                a = in.read();
                fout.write(a);
            }
            fout.close();
            System.out.println(size + "B");
        }
    }
    

    打印结果

    137 B
    

    此时目录结构,可以看到szu.html文件在此目录下

    $ tree      
    .
    ├── Network.iml
    ├── out
    │   └── production
    │       └── Network
    │           └── com
    │               └── ipaddress
    │                   ├── CSDNAddress.class
    │                   ├── IPAddress.class
    │                   └── SZUurl.class
    ├── src
    │   └── com
    │       └── ipaddress
    │           ├── CSDNAddress.java
    │           ├── IPAddress.java
    │           └── SZUurl.java
    └── szu.html
    
    8 directories, 8 files
    

网络聊天室

套接字(Socket)

  • 用于实现网络上客户端程序和服务器程序之间的连接。
  • 套接字负责网络上进程之间的通信
  • 客户端程序可以向套接字里写入请求,然后服务器会处理这个请求,并把处理结果通过套接字送回。
  • 服务器应用程序一般会侦听一个特定端口,用来等待客户的连接请求,当一个连接请求到达时,客户和服务器会建立一个通信连接,在连接过程中,客户被分配一个本地端口号并与一个Socket连接,客户通过写Socket来通知服务器,再通过读取Socket来获取服务器发送过来的信息。
  • 类似地,服务器也获取一个本地端口号,它需要一个新的端口号来侦听原始端口上的其他连接请求。服务器也给它的本地端口连接一个Socket,通过读写它来与客户通信。
  • Socket可以根据通信性质分类,这种性质对于用户是可见的。
  • 应用程序一般仅在同一类的套接字之间进行通信。不过只要底层的通信协议允许,不同类型的套接字之间也可以通信。
  • 目前可以使用两种套接字,即流套接字和数据报套接字。
    • 流套接字提供了双向的、有序的、无重复的并且无记录边界的数据流服务。
    • TCP是一种流套接字协议
    • 数据报套接字支持双向的数据流,但并不保证是可靠、有序、无重复的,也就是说,一个以数据报套接字来接收信息的进程有可能发现信息重复了,或者和发出的顺序不同了。数据报套接字的一个重要特点是它保留了记录边界。
    • UDP即是一种数据报套接字协议。

端口

  • 是一个逻辑概念。每一个服务器都运行在该主机的一个对外开放的端口上。
  • 一个主机上可以有多种服务器,也就是有多个端口。程序员可以在创建自己的服务器程序时使用其它端口(即除了系统默认的端口)。
  • 端口常以数字编号,作为客户可指定一个端口号,用这个端口号来连接相应的服务器以接收服务。
  • 如果把 IP 地址看作电话号码,则端口类似电话的分机号码

cmd > netstat –na 可以查看本机的端口使用情况。

Socket

Socket是网络上运行的程序之间双向通信链路的最后终结点

image-20220403124721918

IP与端口的组合得出一个套接字,可以完全分辨Internet上运行的程序

Socket通信—TCP

  • 一对一的Socket C/S通信
    • TCP是一种可靠的、基于连接的网络协议,在Internet上大都采用TCP/IP协议进行互联。网络上的两个进程采用C/S模式进行通信。
    • 当两台主机准备进行交谈时,都必须建立一个Socket,其中一方作为服务器打开一个Socket并侦听来自网络的连接请求,另一个作为客户,它向网络上的服务器发送请求,通过Socket与服务器传递信息,要建立连接,只需指定主机的IP地址和端口即可。

Socket 工作模式

image-20220403124838800

使用TCP协议的Socket编程

  1. 概述

    • Java提供Socket和ServerSocket类作为标准的TCP套接字编程技术,通过它们实现主机与主机之间(应用程序间)的对话。位于:java.net包中。
    • 使用Socket进行C/S程序设计的一般连接过程:
      1. Server端Listen(监听)某个端口是否有连接请求,
      2. Client端向Server端发出Connect(连接)请求,
      3. Server端向Client端发回Accept(接受)消息。
      4. 连接建立后,Server端和Client端都可以通过套接字类提供的一些方法与对方通信。
  2. Socket类的构造方法:

    public Socket(String host, int port)
    public Socket(InetAddress address, int port)
    public Socket(String host, int port, InetAddress localAddr, int localPort)
    
    //在指定的机器上的指定端口上运行  
    

    上述方法都将抛出例外IOException,程序中需要捕获处理。

  3. Socket的常见方法:

    //Socket的输入/输出流管理;抛出例外IOException。
    public InputStream getInputStream()
    public void shutdownInput() 
    public OutputStream getOutputStream() 
    public void shutdownOutput()
    //关闭Socket
    public void close() throws IOException
    //设置/获取Socket数据:
    public InetAddress getInetAddress()   返回此套接字链接的地址对象
    public InetAddress getLocalAddress()  返回此套接字本地的地址对象
    public int getPort()    返回此套接字链接地址的端口
    

    上述方法都将抛出例外SocketException,程序中需要捕获处理。

  4. ServerSocket类

    构造方法:

    public ServerSocket(int port)
    public ServerSocket(int port, int backlog)    //支持指定数目的连接
    public ServerSocket(int port, int backlog, InetAddress bindAddr)  //在指定的机器上运行  
    

    主要方法

    public Socket accept():等待客户端的连接
    public void close():关闭Socket
    

    设置/获取Socket数据的方法

    public InetAddress  getInetAddress()
    public int getLocalPort(), ...  
    
  5. Socket通信程序基本结构都一样,包括以下四个基本步骤:

    1. 在客户方和服务器方创建Socket/ServerSocket实例。
    2. 打开连接到Socket的输入/输出流。
    3. 利用输入/输出流,按照一定的协议对Socket进行读/写操作。
    4. 关闭输入/输出流和Socket。

    通常,程序员的主要工作是针对所要完成的功能在第3步进行编程,第1、2、4步对所有的通信程序来说几乎都是一样的。

image-20220403125523379

Socket

  • 创建Socket

    下面是一个典型的创建客户端Socket的代码:

    try
    {
        Socket socket=new Socket("127.0.0.1",1432); 
        //127.0.0.1是TCP/IP协议中默认的本机地址
    }catch(IOException e){
        System.out.println("Error:"+e);
    }
    
  • 创建服务器应用程序

    • 用于服务器的类和方法

      • 要创建服务器,你需要创建ServerSocket对象监听客户请求的特定端口。
      • 当它认出有效请求,服务器Socket获得客户创建的Socket对象。用这Socket产生位于服务器和客户机之间的通信。
    • ServerSocket类

      • ServerSocket类的构造函数带两个参数 :第一个是端口号码 ;第二个参数表示可连接的最大数。
      • ServerSocket类提供如下方法:
        • 监听连接
        • 返回地址和本机端口
        • 返回表示Socket的串
    • 创建服务器
      构造函数的代码给出如下:

      try {
          server = new ServerSocket(1432);
          //创建一个ServerSocket在端口1432监听客户请求
      } catch (Exception e) {
          System.out.println("can not listen to:" + e);
          //出错,打印出错信息
      } 
      System.out.println("Server started…");
      this.start(); //启动线程
      
    • 监听客户请求

      ServerSocket对象通过accept()方法来监听客户的请求,如果接收到客户的请求,则产生一个Socket对象,并继续执行后面的代码;如果没有接收到客户请求,则产生一个错误信息。

      Socket socket = null;
      try {
          socket = server.accept();
          //使用accept()阻塞等待客户请求,有客户
          //请求到来则产生一个Socket对象,并继续执行
      } catch (Exception e) {
          System.out.println("can not listen to:" + e);
          //出错,打印出错信息
      }
      
    • 服务器的输入和输出流
      服务器端用到了以下的输入和输出流:

      • BufferedReader对象用来表示从客户端输入到服务器的流
      • PrintWriter对象表示服务器端输出到客户端的流
      • BufferedReader对象表示从系统标准输入设备输入到服务器端的流。

      ``java
      BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
      //由Socket对象得到输入流,并构造相应的BufferedReader对象
      PrintWriter os = new PrintWriter(socket.getOutputStream());
      //由Socket对象得到输出流,并构造PrintWriter对象
      BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
      //由系统标准输入设备构造BufferedReader对象
      {
      new Server();
      }

      
      
    • 完整的服务器程序

      • 服务器程序创建了一个BufferedReader对象(is)和一个PrintStream对象(os)。is使用getInputStream()方法检索客户端的输入;os使用getOutputStream()方法使得服务器可以把输出写到客户端。这样一来,就开始了双向通信。
      • 当客户连接到服务器时,服务器在标准输出上打印从客户端读入的字符串,然后由BufferedReader对象sin的readline()方法从标准输入读入一字符串,并发送到客户端程序。当有一方输入”bye”时,通信结束。
      • 到了结束的时候,服务器中断循环。关闭客户套接字。关闭套接字很重要,如果让连接开着,很快就将耗尽服务器的内存。
    • ObjectInputStream类

      • ObjectInputStream类中包含从持久的存储介质中读出类的对象的功能。持久的存储介质可以是磁盘或套接字。

      • 这由ObjectInputStream类的readObject()方法来完成。readObject()方法的语法如下:

        FileInputStream fp=new FileInputStream(“data.txt”);
        ObjectInputStream istream = new ObjectInputStream(fp);
        Date date = (Date)istream.readObject(); 
        
  • 一般步骤

    1. 选择创建网络服务器应用程序所需的类和方法。
    2. 确定用户定义的类,它们的目的,方法。
    3. 确定用户定义的类中的数据类型和变量。
    4. 确定服务器的IP地址和端口号。
    5. 确定查询参数。
    6. 确定要处理的异常。
    7. 确定异常的出错信息。

数据报通讯(UDP)

概述

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

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

    • 用于表示通信数据的数据报类:DatagramPacket

    • 用于进行端到端通信的类:DatagramSocket

    • 用于广播通信的类:MulticastSocket。

image-20220403131547914

基于UDP通信的基本模式

  • 将数据打包,发往目的地
  • 接受别人发来的数据包,察看数据包内容
发送数据包
  1. 用DatagramPacket类将数据打包,即创建数据包对象。

    DatagramPacket(byte data[], int length, InetAddtress address,int port)
    Address: 目标主机地址    Port:目标主机端口
    

    如:

    byte data[]=“近来好吗”.getByte();
    InetAddress address=inetAddress.getByname(www.sina.com.cn);
    DatagramPacket data_pack=new DatagramPacket(data,data.length,address,980);
    

    注: data_pack常用方法:

    • Public int getPort();
    • public InetAddress getAddress();
    • public byte[] getData();
  2. 用DatagramSocket类的构造方法DatagramSocket()创建一个对象,该对象负责发送数据包。例:

    • DatagramSocket mail_out=new DatagramSocket();
    • Mail_out.send(data_pack);
接收数据

DatagramSocket mail_in=new DatagramSocket(int port);
注意:mail_in的主要方法:

  • Receive(DatagramPacket pack) //接收数据包给pack, 例如:
  • DatagramPacket pack=new DatagramPacket(data,length)
  • Mail_in.receive(pack);

练习

练习1

  • 利用Socket类和ServerSocket类编写一个C/S程序,实现C/S通信。

  • 客户端向服务器端发送Time命令,服务器端接受到该字符串后将服务器端当前时间返回给客户端;客户端向服务器端发送Exit命令,服务器端向客户端返回“Bye”后退出。

  • 编写完整程序;一个服务器端程序,一个客户端程序。服务器端和客户端都需要打印出接受到的消息和发出的命令。

  • 下图为运行结果示例

    image-20220403132403985

    Server.java :

package com.csmode;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class Server {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket socket;
        DataOutputStream dataOutputStream;
        DataInputStream dataInputStream;
        System.out.println("Server Running ... ");
        String time;
        String goodbye = "Bye!";
        String error = "No such Command!";

        try {
            //在端口号4096创建 serverSocket
            serverSocket = new ServerSocket(4096);
        } catch (IOException e) {
            System.out.println("ServerSocket error");
            e.printStackTrace();
        }
        try {
            assert serverSocket != null;
            socket = serverSocket.accept();
            // 连接成功
            dataOutputStream = new DataOutputStream(socket.getOutputStream());
            dataOutputStream.writeUTF("服务器启动完毕\n创建客户连接\t\t");

            // 标记什么时候退出
            boolean flag = true;
            while(flag) {
                // 接收客户端发送过来的消息
                dataInputStream = new DataInputStream(socket.getInputStream());
                String str = dataInputStream.readUTF();

                System.out.print(str + " : ");
                if(str.equals("Time")) {
                    time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime());
                    dataOutputStream.writeUTF("服务器当前时间为:\t" + time);
                    System.out.println(time);
                } else if (str.equals("Exit")) {
                    dataOutputStream.writeUTF(goodbye);
                    flag = false;
                } else {
                    dataOutputStream.writeUTF(error);
                }
            }
        } catch (IOException e) {
            System.out.println("Socket data IO error");
            e.printStackTrace();
        }
    }

}

Client.java:

package com.csmode;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost", 4096);
            // 创建 Socket 管道
            DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
            DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());

            String string = dataInputStream.readUTF();
            System.out.println(string);
            
            while(true) {
                // 输入命令
                Scanner input = new Scanner(System.in);
                String str = input.next();
                dataOutputStream.writeUTF(str);
                // 在服务器端传来的 数据
                string = dataInputStream.readUTF();
                System.out.println(string);
                if(str.equals("Exit")) {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

先启动Server.java, 再启动Client.java

结果如下:

客户端:

image-20220403182012843

服务器端:

image-20220403182026783

练习2

  • 编写一数据报通信程序,实现简单的聊天功能。

  • “聊天内容”和“输入文本”分别为当前聊天的历史信息和当前要传送出去的聊天文本。“确定”、“清空”、“退出”三个按钮分别实现发送当前聊天文本、清空当前聊天文本和退出系统的功能。

  • 界面可参考如下格式

    image-20220403132507303

PID.java

package com.chattingroom;

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;

public class PID {
    // 获取 PID
    public static int getProcessID() {
        RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
        return Integer.parseInt(runtimeMXBean.getName().split("@")[0]);
    }
}

chat.java

package com.chattingroom;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.TextArea;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;


public class chat extends JFrame implements ActionListener, Runnable, KeyListener {
    private TextArea textArea;
    private JTextField sendText;
    private JTextField ip;
    private JTextField port;
    private JButton buttonServer;
    private JButton buttonClient;
    private JButton send;
    private JButton exit;
    private JButton clear;
    private Socket socket;

    public void keyReleased(KeyEvent f) {

    }


    // 监听键盘输入
    public void keyPressed(KeyEvent f) {
        if (f.getKeyCode() == KeyEvent.VK_ENTER) {
            PrintWriter printWriter = null;
            try {
                printWriter = new PrintWriter(socket.getOutputStream());
            } catch (IOException e) {
                e.printStackTrace();
            }
            String string = sendText.getText();
            if (string == null)
                return;

            int pid = PID.getProcessID();
            String User = ip.getText() + " : " + port.getText();
            // 自己发的消息
            textArea.append(User + " : " + pid + " says: " + "\n" + string + "\n");

            // 对面发来的消息
            String string3 = User + " : " + pid + " says: " + "\n" + string;


            assert printWriter != null;
            printWriter.println(string3);
            printWriter.flush();
            sendText.setText("");
        }
    }

    public void keyTyped(KeyEvent f) {

    }


    // 轮询
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while (true) {
                String string = br.readLine();
                if (string == null)
                    break;
                textArea.append(string + "\n");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == buttonServer) {
            server();
        }

        if (e.getSource() == buttonClient) {
            client();
        }

        if (e.getSource() == send) {
            doSend();
        }

        if (e.getSource() == exit) {
            doExit();
        }

        if (e.getSource() == clear) {
            doClear();
        }
    }


    // 作为服务器启动
    public void server() {
        try {
            ServerSocket server = new ServerSocket(Integer.parseInt(port.getText()));
            socket = server.accept();
            textArea.append("连接服务器成功!\n");
            new Thread(this).start();
        } catch (Exception e) {
            textArea.append("服务器启动失败!\n");
        }
    }


    // 作为客户端启动
    public void client() {
        try {
            socket = new Socket(ip.getText(), Integer.parseInt(port.getText()));
            textArea.append("连接服务器成功!\n");
            new Thread(this).start();
        } catch (Exception e) {
            textArea.append("连接失败!\n");
        }
    }


    // 发送消息
    public void doSend() {
        try {
            PrintWriter printWriter = new PrintWriter(socket.getOutputStream());
            String string = sendText.getText();
            if (string == null)
                return;

            int pid = PID.getProcessID();
            String User = ip.getText() + " : " + port.getText();
            // 自己发的消息
            textArea.append(User + " : " + pid + " says: " + "\n" + string + "\n");

            // 对面发来的消息
            String string3 = User + " : " + pid + " says: " + "\n" + string;

            printWriter.println(string3);
            printWriter.flush();
            sendText.setText("");
        } catch (Exception e) {
            textArea.append("发送失败!\n");
        }
    }


    // 清除聊天
    public void doClear() {
        textArea.setText("");
    }


    // 退出程序
    public void doExit() {
        System.exit(0);
    }


    // 连接键
    public void addClient(JPanel panel) {
        buttonClient = new JButton("连接");
        buttonClient.setForeground(new Color(250, 131, 46));
        buttonClient.setFont(new Font("宋体", Font.BOLD, 14));
        panel.add(buttonClient);
    }


    // 监听键
    public void addServer(JPanel panel) {
        buttonServer = new JButton("监听");
        buttonServer.setForeground(new Color(250, 131, 46));
        buttonServer.setFont(new Font("宋体", Font.BOLD, 14));
        panel.add(buttonServer);
    }


    // 清除键
    public void addClear(JPanel panel) {
        clear = new JButton("清除");
        clear.setForeground(new Color(250, 131, 46));
        clear.setFont(new Font("宋体", Font.BOLD, 14));
        panel.add(clear);
    }


    // 退出键
    public void addExit(JPanel panel) {
        exit = new JButton("退出");
        exit.setForeground(new Color(250, 131, 46));
        exit.setFont(new Font("宋体", Font.BOLD, 14));
        panel.add(exit);
    }


    // 设置 JPanel
    public void setPanel(JPanel panel) {
        panel.setLayout(new BorderLayout());
        panel.add(ip, BorderLayout.WEST);
        panel.setBackground(new Color(245, 161, 102));
        sendText = new JTextField("");
        panel.add(sendText, BorderLayout.CENTER);
        send = new JButton("发送");
        panel.add(send, BorderLayout.EAST);
    }


    // 设置按键监听
    public void setListen() {
        clear.addActionListener(this);
        exit.addActionListener(this);
        buttonServer.addActionListener(this);
        buttonClient.addActionListener(this);
        send.addActionListener(this);
        sendText.addKeyListener(this);
    }


    public JPanel setJPanel1(Container container) {
        JPanel panel = new JPanel();
        container.add(panel, BorderLayout.NORTH);
        panel.setBackground(new Color(231, 162, 112));
        textArea = new TextArea();
        container.add(textArea, BorderLayout.CENTER);
        return panel;
    }


    public JPanel setJPanel2(Container container) {
        JPanel panel = new JPanel();
        container.add(panel, BorderLayout.SOUTH);
        panel.setBackground(new Color(250, 180, 30));
        textArea.setForeground(new Color(250, 131, 46));
        return panel;
    }


    public void addButton(JPanel panel){
        addServer(panel);
        addClient(panel);
        addClear(panel);
        addExit(panel);
    }


    // 聊天界面
    public chat(String IPAddress, String Port) {
        this.setTitle("UDP聊天程序");
        this.setBounds(200, 200, 500, 500);
        Container container = this.getContentPane();

        JPanel panel1 = setJPanel1(container);
        JPanel panel2 = setJPanel2(container);

        ip = new JTextField(IPAddress, 15);
        port = new JTextField(Port, 4);

        addButton(panel1);

        setPanel(panel2);
        setListen();

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

Run.java

package com.chattingroom;

public class Run {
    public static void main(String[] args) {
        new chat("127.0.0.1", "8192").setVisible(true);
    }
}

目录结构如下:

$ tree chattingroom 
chattingroom
├── PID.java
├── Run.java
└── chat.java

开启两个程序运行,效果如下:

image-20220403183637735

左端输入123,右端输入456,结果如下:

image-20220403183717239

TCP 文件传输

Java对网络编程的支持

java.net包中的主要的类和可能产生的例外包括:

  • 面向应用层的类:
    • URL
    • URLConnection
  • 面向传输层/IP层的类:
    • TCP协议相关类:
      • Socket
      • ServerSocket
    • UDP协议相关类:
      • DatagramPacket
      • DatagramSocket
      • MulticastSocket
  • 表示IP 地址的类:
    • InetAddress
  • 可能产生的异常:
    • BindException
    • ConnectException
    • MalformedURLException
    • NoRouteToHostException
    • ProtocolException
    • SocketException
    • UnknownHostException
    • UnknownServiceException

练习

  • 利用Socket类和ServerSocket类,编写一个C/S程序,实现网络文件传输。

  • 客户端向服务器端发送请求,服务器端当接受到客户端的请求之后,先向其传输文件名,当客户端接受完毕之后,向客户端传输文件

  • 客户端连上服务器后接收传输文件,并进行改名(文件名可自行定义)存在本地。

  • 编写完整程序;一个服务器端程序,一个客户端程序。服务器端和客户端都需要打印出交互过程。

  • 下图为运行结果示例

    • 服务器端运行结果:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V42P3IHL-1648993394649)(https://cdn.jsdelivr.net/gh/Misaka-9982-coder/img_hosting/img/image-20220403132839233.png)]

    • 客服端运行结果:

      image-20220403132858447

Server.java

package com.Transmission;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server implements Runnable {
    private static final int Port = 4096;
    private static final int TTL = 20;
    private final Socket socket;
    String fileName = "test.txt";

    public Server(Socket socket) {
        super();
        this.socket = socket;
    }

    public static void server() {
        try {
            ServerSocket serverSocket = new ServerSocket(Port);
            int i = 0;
            do {
                i ++ ;
                Socket socket = serverSocket.accept();
                System.out.println("服务器的线程1,启动,与客户端" + i + "连接成功");

                new Thread(new Server(socket)).start();
            } while (i <= TTL);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        OutputStream outputStream = null;
        FileInputStream fileInputStream = null;
        try {
            outputStream = socket.getOutputStream();

            System.out.println("要传输的文件为: " + fileName);

            outputStream.write(fileName.getBytes());
            System.out.println("开始传输文件");
            fileInputStream = new FileInputStream(fileName);
            int data;
            while (-1 != (data = fileInputStream.read())) {
                outputStream.write(data);
            }
            System.out.println("文件传输结束");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileInputStream != null)
                    fileInputStream.close();
                if (outputStream != null)
                    outputStream.close();
                this.socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Server.server();
    }
}

Client.java

package com.Transmission;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;

public class Client {
    private static final String SERVERIP = "localhost";
    private static final int Port = 4096;
    private static final int clientPort = 8192;

    public static void main(String[] args) {

        byte[] buffer = new byte[2048];

        Socket socket = new Socket();
        try {
            socket.connect(new InetSocketAddress(SERVERIP, Port), clientPort);
            System.out.println("与服务器连接成功");

            InputStream inputStream = socket.getInputStream();

            int read = inputStream.read(buffer);
            String fileName = new String(buffer, 0, read);
            String newName = "test1.txt";

            FileOutputStream fileOutputStream = new FileOutputStream(newName);
            int data;
            while ((data = inputStream.read()) != -1) {
                fileOutputStream.write(data);
            }

            System.out.println("接收到的文件为:" + fileName);
            System.out.println("保存为为:" + newName);

            inputStream.close();
            socket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

结果:

客户端:

image-20220403214116542

服务器端:

image-20220403214138631

现在文件结构如下:

$ tree . 
.
├── Network.iml
├── out
│   └── production
│       └── Network
│           └── com
│               ├── Transmission
│               │   ├── Client.class
│               │   └── Server.class
│               ├── chattingroom
│               │   ├── PID.class
│               │   ├── Run.class
│               │   └── chat.class
│               ├── csmode
│               │   ├── Client.class
│               │   └── Server.class
│               └── ipaddress
│                   ├── CSDNAddress.class
│                   ├── IPAddress.class
│                   └── SZUurl.class
├── src
│   └── com
│       ├── Transmission
│       │   ├── Client.java
│       │   └── Server.java
│       ├── chattingroom
│       │   ├── PID.java
│       │   ├── Run.java
│       │   └── chat.java
│       ├── csmode
│       │   ├── Client.java
│       │   └── Server.java
│       └── ipaddress
│           ├── CSDNAddress.java
│           ├── IPAddress.java
│           └── SZUurl.java
├── szu.html
├── test.txt
└── test1.txt

14 directories, 24 files

Pythond 的Socket 编程

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值