17.网络通信

Java网络编程基础:TCP与UDP协议详解
本文介绍了网络通信的基础,包括TCP/IP协议、网络协议、局域网与互联网、端口和套接字的概念。详细讲解了TCP和UDP协议的区别,TCP提供可靠的数据传输,适合HTTP、FTP等应用,而UDP则适用于对速度要求高但能容忍数据丢失的场景。此外,文章还阐述了Java中网络编程涉及的InetAddress、ServerSocket和DatagramSocket类的使用方法,以及TCP和UDP程序设计的基本步骤。

17.网络通信

网络应用程序可以让不同计算机之间交换数据。编写网络应用程序,首先必须明确网络应用程序索要使用的网络协议,TCP/IP协议是网络应用程序的首选。

17.1网络程序设计基础

Java已经将网络程序所需要的东西封装成不同的类。

17.1.1局域网与英特网

为了实现两台计算机之间的通信,必须要用一个网络连接服务器和客户机。服务器是指提供信息的计算机或者程序。客户机是指请求信息的计算机或程序。

因特网,由无数的LAN(Local Area Network,局域网)和WAN(Wide Area Network,WAN)组成的。

17.1.2网络协议

网络协议规定了计算机之间连接的物理、机械(网线与网卡的连接规定)、电气(有效的电平范围)等特征和计算机之间的相互寻址规则、数据发送冲突的解决、长的数据如何分段传送与接受等。常用网络协议如下:

1.IP协议

IP是Internet Protecol 的简称,是一种网络协议。Internet网络采用的协议是TCP/IP协议,全称是Transmission Control Protocol?Internet Protocol。

在Internet网络上,每台主机用为其分配的Internet地址代表自己,即IP地址。目前IP地址用4个字节,32位2进制数表示,称为IPv4,目前人们正在实验用16个自己表示IP地址,即IPv6。

TCP/IP协议是一种层次结构,共分为4层,分别是应用层、传输层、互联网层和网络层。

2.TCP与UDP协议

在TCP/IP协议栈中,有两个重要的高级协议,分别是传输控制协议(Transmission Control Protocol,TCP)和用户数据报协议(User Datagram Protocol,UDP)。

1.TCP

TCP协议提供两台计算机之间可靠的数据传送。确保数据能够准确送达,而且抵达的数据排列顺序和发送顺序相同。HTTP、FTP和Telnet等都需要使用可靠的通信频道, 所以都是基于TCP实现的。

2.UDP

UDP是无连接通信协议。不保证可靠的数据传输。能够想若干个目标发送数据,接受来自若干个源的数据。UDP是以独立发送数据包的方式进行。UDP协议适用于一些对数据准确性要求不高的场合,如网络聊天室、在线影片等。

值得注意的是,一些防火墙和路由器会设置成不允许UDP数据包传输。因此,遇到UDP连接方面的问题时,应首先确定所在网络是否允许UDP协议。

17.1.3端口和套接字

1.端口

通常一台计算机只有单一的连接到网络的物理接口(Physical Connection),所有数据都通过此连接对内、对外送达特定的计算机,即端口。端口并非是一个物理装置,而是通过软件实现的一个连接装置。

端口被规定为一个在0~65535之间的整数,其中0~1023之间的端口用于一些知名的网络服务和应用。

客户端会通过不同的端口确定连接到服务器的哪项服务上,如HTTP服务一般使用80端口,FTP使用21端口。

2.套接字

套接字(Socket)用于连接应用程序和端口。也是一个虚拟的连接装置。

Java将套接字抽象化为类,程序设计者只需创建Socket类对象,即可使用套接字。

17.2TCP程序设计基础

TCP网络程序设计是指利用Socket类编写通信程序。服务器端和客户端交互过程如下所示:

1.服务器程序创建一个ServerSocket(服务器端套接字),调用accept()方法等待客户机来连接

2.客户端程序创建一个Socket,请求与服务器建立连接

3.服务器接受客户机的连接请求,同时创建一个新的Socket与客户端建立连接,服务器继续等待新的请求

17.2.1 InetAddress类

java.net包中的InetAddress类是与IP地址相关的类,利用该类可以获取IP地址、主机地址等信息。常用方法如下:

方法 返回值 功能说明
getByName(String host) InetAddress 获取与Host相对应的InetAddress对象
getHostAddress() String 获取InetAddress对象所包含的IP地址
getHostName() String 获取此IP地址的主机名
getLocalHost() InetAddress 返回本地主机的InetAddress对象

举例如下:

import java.net.*;
public class Address {
    public static void main(String[] args) {
        try {

            InetAddress ip = InetAddress.getLocalHost();
            String LocalName = ip.getHostName();
            String LocalIp = ip.getHostAddress();
            System.out.println("本机名" + LocalName);
            System.out.println("本机IP地址" + LocalIp);
            /*InetAddress ip1;
            ip1 = InetAddress.getByName("");
            //在参数中输入其他主机的主机名,可以获取其他主机的IP地址
            LocalIp = ip1.getHostAddress();
            System.out.println("该主机的IP地址为"+LocalIp);*/
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}

InetAddress类的方法可能抛出UnknownHsotException异常,所以必须进行异常处理,这个异常在主机不存在或者网络连接错误时发生。

17.2.2ServerSocket类

java.net包中的ServerSocket类用于表示服务器套接字,服务器套接字通过指定的端口来等待连接的套接字。

服务器套接字一次可以与一个套接字连接。当多台客户机同时发出请求,服务器套接字会将客户机存于队列,从中取出一个套接字,进行连接。

如果请求连接数大于队列最大容纳数,则多出来的连接请求被拒绝。队列默认大小是50。

SerberSocket类的构造方法都抛出IOException异常,分别有以下几种形式:

1.ServerSocket():创建非绑定服务器套接字

2.ServerSocket(int prot):创建绑定到特定端口的服务器套接字

3.ServerSocket(int port,int backlog):利用指定的backlog创建服务器套接字,并绑定到指定的本地端口号

4.ServerSocket(int port,int backlog,InetAddress bindAddress):使用指定的端口、监听backlog和要绑定到的本地IP地址创建服务器。该构造方法适用于计算机上有多块网卡和多个IP地址的情况,用于可以明确规定ServerSocket在哪块网卡或IP地址上等待客户的连接请求。

ServerSocket类常用方法如下所示:

方法 返回值 功能说明
accept() Socket 等待客户机的连接。若连接,创建一个套接字
isBound() boolean 判断ServerSocket的绑定状态
getInetAddress() InetAddress 返回此服务器套接字的本地地址
isClosed() boolean 返回服务器套接字的关闭状态
close() void 关闭服务器套接字
bind(SocketAddress endpoint) void 将ServerSocket绑定到特定地址(IP地址和端口号)
getInetAddress() int 返回服务器套接字等待的端口号

调用ServerSocket类的accept()方法会返回一个和客户端Socket对象相连接的Socket对象。此时服务器端Socket对象和客户端Socket对象通过输入输出流互相连接。(服务器端的Socket对象使用getOutputStream()方法获得的输出流将指向客户端Socket对象使用getInputStream()方法获得的输入流;服务器端的Socket对象使用getInputStream()方法获得的输入流将指向客户端Socket对象使用getOutputStream()方法获得的输出流)

accept()方法会阻塞线程的继续执行,直到接到客户端的呼叫。如果客户端没有呼叫服务器,那么System.out.println(”连接中“)语句就不会执行。但如果没有收到客户请求,accept()方法没有发生阻塞,肯定是程序处理问题,通常是使用了一个还在被其他程序占用的端口号。ServerSocket绑定没有成功。

17.2.3TCP网络程序

单向通信只要求客户端向服务端发送消息,而不需要服务端向客户端发生信息。要实现单向通信,只需要客户机套接字和服务器套接字连接成功后,客户机通过输出流发送数据,服务器通过输入流接受数据即可,举例如下:

以下是一个TCP服务器端程序,在getserver()方法中建立服务器套接字,带哦用getClientMessage()方法获取客户端信息:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class MyTCP {
    private BufferedReader reader;
    private ServerSocket server;
    private Socket socket;

    void getserver() {
        try {
            server = new ServerSocket(18998);
            System.out.println("服务器套接字已经创建成功");
            while (true) {
                System.out.println("等待客户机连接");
                socket = server.accept();
                reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                System.out.println("");
                getClientMessage();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void getClientMessage() {
        try {
            while (true) {
                System.out.println("客户机" + reader.readLine());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            if (reader != null) {
                reader.close();
            }
            if (socket != null) {
                socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        MyTCP tcp = new MyTCP();
        tcp.getserver();
    }
}

以下是客户端程序,实现将用户在文本框中输入的信息发送至服务器端,并将文本框中输入的信息显示在客户端文本域中。

import javax.swing.*;
import javax.swing.border.BevelBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.PrintWriter;
import java.net.Socket;

public class MyClient extends JFrame {
    private PrintWriter writer;
    Socket socket;
    private JTextArea ta=new JTextArea();
    private JTextField tf=new JTextField();
    Container cc;
    public MyClient(String title){
        super(title);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        cc=this.getContentPane();
        final JScrollPane scrollPane=new JScrollPane();
        scrollPane.setBorder(new BevelBorder(BevelBorder.RAISED));
        getContentPane().add(scrollPane,BorderLayout.CENTER);
        scrollPane.setViewportView(ta);
        cc.add(tf,"South");
        tf.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                writer.println(tf.getText());
                ta.append(tf.getText());
                ta.setSelectionEnd(ta.getText().length());
                tf.setText("");
            }
        });
    }
    private void connect(){
        ta.append("尝试连接");
        try{
            socket =new Socket("127.0.0.1",18998);
            writer =new PrintWriter(socket.getOutputStream(),true);
            ta.append("完成连接\n");
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        MyClient client=new MyClient("向服务器发送数据");
        client.setSize(200,200);
        client.setVisible(true);
        client.connect();
    }
}

对于端口被占用的情况,可以在windows命令行中使用命令netstat -an查看。

17.3UDP程序设计基础

相比TCP,基于UDP(用户数据报协议)的信息传递更快,但是不提供可靠的交付。

用户无法知道数据能否正确到达主机,也不能确定到达的目的地的顺序是否和发送的顺序相同。

对于需要较快地传输信息,并且能忍受小的错误,可以考虑采用UDP传输。

以下是总结的UDP程序的步骤。

发送数据包:

(1)使用DatagramSocket()创建一个数据包套接字。

(2)使用DatagramPakcet(byte[] buf,int offset,int length,InetAddress address,int port)创建要发送的数据包。

(3)使用DatagramSocket类的send()方法发送数据包。

接受数据包:

(1)使用DatagramSocket(int port)创建数据包套接字,绑到指定的端口。

(2)使用DategramPacket(byte[] buf,int length)创建字节数组来接受数据包。

(3)使用DatagramPacket类的receiver()方法来接受UDP包。

值得注意的是,在没有可接受的数据时,receive()方法将阻塞,一道有数据传来,receive()方法接受该数据并返回。如果没有数据时receive()方法没有阻塞,多半是使用了一个被其它程序占用的端口号。

17.3.1DatagramPacket类

java.net包的DatagramPacket类用来表示数据包。构造函数如下:

Datagrampacket(byte[] buf,int length)

DatagramPacket(byte[] buf,int length,InetAddress address,int port)

第一种构造函数指定了数据包内存空间和大小。

第二种构造函数指定了数据包内存空间和大小,还指定了数据包的目标地址和端口。发送数据时必须指定接收方的Socket地址和端口号,因此使用第二种构造方法可以创建发送数据的DatagramPacket对象。

17.3.2DatagramSocket类

java.net包中的DatagramSocket类用于表示发送和接受数据包的套接字,构造函数如下:

DatagramSocket()

DatagramSocket(int port)

DatagramSocket(int port,InetAddress addr)

第一种构造方法创建DatagramSocket对象,构造数据包套接字并将其绑定到本地主机上任何可用的端口。

第二种构造方法创建DatagramSocket对象,创建数据包套接字并将其绑定到本地主机上指定端口。

第三种构造方法创建DatagramSocket对象,创建数据包套接字并将其绑定到指定的本地地址。该构造方法适用于有多块网卡和多个IP地址的情况。

发送程序中,通常使用第一种构造方法,不指定端口号,由系统自动分配。

接收程序必须指定一个端口号,通常使用第二种构造方法。

17.3.3UDP网络程序

以下是一个广播数据包程序。主机不断重复播出节目预报,可以保证加入到同一组的主机随时可以接收到广播消息。接收者将正在接受的信息放在一个文本域中,并将接受的全部信息放在另外一个文本域中。

广播主机程序不断向外播出信息,代码如下:

import java.net.*;
public class Weather extends Thread {
    String weather="节目预报:八点有大型晚会,请收听";
    int port=9898;
    InetAddress iaddress=null;
    MulticastSocket socket=null;
    Weather(){
        try{
            //实例化InetAddress,指定地址
            iaddress=InetAddress.getByName("224.225.10.0");
            socket=new MulticastSocket(port);
            socket.setTimeToLive(1);
            socket.joinGroup(iaddress);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
public void run(){
        while(true){
            DatagramPacket packet=null;
            byte data[]=weather.getBytes();
            //将数据打包
            packet=new DatagramPacket(data,data.length,iaddress,port);
            System.out.println(new String(data));
            try{
                socket.send(packet);
                sleep(3000);
            }catch(Exception e){
                e.printStackTrace();
            }
        }
}

    public static void main(String[] args) {
        Weather w=new Weather();
        w.start();
    }
}

接受广播程序:单击”开始接受”按钮,系统开始接受主机播出的信息;单击”停止接受”按钮,系统会停止接受广播主机播出的信息。

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

public class Receive extends JFrame implements Runnable, ActionListener {
    int port;
    InetAddress group = null;
    MulticastSocket socket = null;
    JButton ince = new JButton("开始接收");
    JButton stop = new JButton("停止接受");
    JTextArea inceAr = new JTextArea(10, 10);
    JTextArea inced = new JTextArea(10, 10);
    Thread thread;
    boolean b = false;

    public Receive() {
        super("广播数据报");
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        thread = new Thread(this);
        ince.addActionListener(this);
        stop.addActionListener(this);
        inceAr.setForeground(Color.blue);
        JPanel north = new JPanel();
        north.add(ince);
        north.add(stop);
        add(north,BorderLayout.NORTH);
        JPanel center=new JPanel();
        center.setLayout(new GridLayout(1,2));
        center.add(inceAr);
        center.add(inced);
        add(center,BorderLayout.CENTER);
        validate();
        port=9898;
        try{
            group=InetAddress.getByName("224.255.10.0");
            socket=new MulticastSocket(port);
            socket.joinGroup(group);
        }catch(Exception e){
            e.printStackTrace();
        }
        setBounds(100,50,360,380);
        setVisible(true);
    }
    public void run(){
        while(true){
            byte data[]=new byte[1024];
            DatagramPacket packet=null;
            packet=new DatagramPacket(data,data.length,group,port);
            try{
                socket.receive(packet);
                String message=new String(packet.getData(),0,packet.getLength());
                inceAr.setText("正在接受的内容:\n"+message);
                inced.append(message+"\n");
            }catch(Exception e){
                e.printStackTrace();
            }
            if(b==true){
                break;
            }
        }
    }
    public void actionPerformed(ActionEvent e){
        if(e.getSource()==ince){
            ince.setBackground(Color.red);
            stop.setBackground(Color.yellow);
            if(!(thread.isAlive())){
                thread=new Thread(this);
            }
            thread.start();
            b=false;
        }
        if(e.getSource()==stop){
            ince.setBackground(Color.yellow);
            stop.setBackground(Color.red);
            b=true;
        }
    }

    public static void main(String[] args) {
        Receive rec=new Receive();
        rec.setSize(460,200);
    }

}

要广播或者接受广播的主机地址必须加入到一个组内,地址范围为224.0.0.0~224.255.255.255,这类地址并不代表某个特定主机的位置。加入到同一组的主机可以在某个端口上广播信息,也可以在某个端口上接受信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值