Java SE核心API(14) —— TCP通信和UDP通信

一、TCP通信

1.1 Socket原理
1.1.1 Socket简介

  socket通常称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。
  应用程序通常通过“套接字”向网络发出请求或者应答网络请求。Socket和ServerSocket类位于java.net包中。ServerSocket用于服务端,Socket是建立网络连接时使用的,在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。

1.1.2 获取本地地址和端口号

  java.net.Socket为套接字类,其提供了很多方法,其中我们可以通过Socket获取本地的地址以及端口号。
  - int getLocalPort(),该方法用于获取本地使用的端口号。
  - InetAddress getLocalAddress(),该方法用于获取套接字绑定的本地地址。
  使用InetAddress获取本地的地址方法:
  - String getCanonicalHostName(),获取此IP地址的完全限定域名。
  - String getHostAddress(),返回IP地址字符串(以文本表现形式)。

1.1.3 获取远端地址和端口号

  通过Socket获取远端的地址以及端口号。
  - int getPort(),该方法用于获取远端使用的端口号。
  - InetAddress getInetAddress(),该方法用于获取套接字绑定的远端地址。

1.1.4 获取网络输入流和网络输出流

  通过Socket获取输入流与输出流,这两个方法是使用Socket通讯的关键方法。
  - InputStream getInputStream(),该方法用于返回此套接字的输入流。
  - OutputStream getOutputStream(),该方法用于返回此套接字的输出流。

1.1.4 close方法

  当使用Socket进行通讯完毕后,要关闭Socket以释放系统资源。
  - close(),关闭此套接字。当关闭了该套接字后也会同时关闭由此获取的输入流与输出流。


1.2 Socket通信模型
1.2.1 Server端ServerSocket监听

  java.net.ServerSocket是运行于服务端应用程序中。通常创建ServerSocket需要指定服务端端口号,之后监听Socket的连接:

 //创建ServerSocket并申请服务端口8088
 ServerSocket server = new ServerSocket(8088);
 //方法会产生阻塞,直到某个Socket连接,并返回请求连接的Socket
 Socket socket = server.accept();
1.2.2 Client端Socket连接

  当服务端创建ServerSocket并通过accept()方法侦听后,我们就可以通过在客户端应用程序中创建Socket来向服务端发起连接。
  需要注意的是,创建Socket的同时就发起连接,若连接异常会抛出异常。

//参数1:服务端的IP地址,参数2:服务端的服务端口
Socket socket = new Socket("localhost",8088);
1.2.3 Server端多线程模型

  因为需要处理多客户端,所以服务端要周期性循环调用accept方法,但该方法会产生阻塞,所以与某个客户端的交互就需要使用多线程来并发处理。

  下面是一个使用TCP通信实现的聊天室:

package chat;
/**
 * 聊天室服务端
 * @author Administrator
 *
 */

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Server {
    /*
     * 运行在服务端的ServerSocket,主要负责:
     * 1、向系统申请服务端口
     *      客户端就是通过这个端口与之连接的
     * 2、监听申请的服务端口,当一个客户端通过该端口尝试建立连接时,
     * ServerSocket会在服务端创建一个Socket与客户端建立连接。
     */
    private ServerSocket server;
    //保存所有客户端输出流的集合
    private List<PrintWriter> allOut;

    /*
     * 用来初始化服务端
     */
    public Server() throws Exception{
        /*
         * 初始化的同时申请服务端口
         */
        server = new ServerSocket(8088);

        allOut = new ArrayList<PrintWriter>();
    }

    /*
     * 将给定的输出流存入共享集合
     */
    private synchronized void addOut(PrintWriter out) {
        allOut.add(out);
    }

    /*
     * 将给定的输出流从共享集合中删除
     */
    private synchronized void removeOut(PrintWriter out) {
        allOut.remove(out);
    }

    /*
     * 将给定的消息发送给所有客户端
     */
    private synchronized void sendMessage(String message) {
        for (PrintWriter out:allOut) {
            out.println(message);
        }
    }

    /*
     * 服务端开始工作的方法
     */
    public void start() {
        try {
            /*
             * ServerSocket的accept方法是一个阻塞方法,作用是
             * 监听服务端口,直到一个客户端连接。并创建一个Socket,
             * 使用该Socket即可与刚连接的客户端进行交互。
             */
            while (true) {
                System.out.println("等待客户端连接...");
                Socket socket = server.accept();
                System.out.println("一个客户端连接了!");

                /*
                 * 启动一个线程,来完成与该客户端的交互
                 */
                ClientHandler handler = new ClientHandler(socket);
                Thread t = new Thread(handler);
                t.start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            Server server = new Server();
            server.start();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("服务端启动失败!");
        }
    }

    /*
     * 该线程负责处理一个客户端的交互
     */
    class ClientHandler implements Runnable{
        private Socket socket;//该线程处理的客户端的socket
        private String host;//客户端的地址信息
        private String nickname;//该用户的昵称

        public ClientHandler(Socket socket) {
            this.socket = socket;

            /*
             * 通过Socket可以获取远端计算机的地址信息
             */
            InetAddress address = socket.getInetAddress();
            //获取IP地址
            host = address.getHostAddress();
        }

        public void run() {
            PrintWriter pw = null;
            try {
                //System.out.println(host+"上线了!");
                /*
                 * socket提供的方法
                 * InputStream getInputStream()
                 * 该方法可以获取一个输入流,从该流读取的数据就是从远端计算机
                 * 发送来的。
                 */
                InputStream in = socket.getInputStream();               
                InputStreamReader isr = new InputStreamReader(in,"UTF-8");              
                BufferedReader br = new BufferedReader(isr);

                //首先读取一行字符串为昵称
                nickname = br.readLine();
                sendMessage(nickname+"上线了!");

                /*
                 * 通过Socket创建输出流用于将消息发送给客户端
                 */
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
                pw = new PrintWriter(osw,true);

                /*
                 * 将该客户端的输出流存入到共享集合中
                 */
                addOut(pw);

                String message = null;
                /*
                 * br.readLine()在读取客户端发送过来的消息时,
                 * 由于客户端断线,而其操作系统的不同,这里读取
                 * 后的结果不同:
                 * 当windows的客户端断开时,br.readLine()会抛出异常。
                 * 当Linux的客户端断开时,br.readLine()会返回null。
                 */
                while ((message = br.readLine()) != null) {
                    //System.out.println(host+":"+message);
                    //pw.println(host+":"+message);
                    //广播消息
                    sendMessage(nickname+":"+message);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                /*
                 * 处理当前客户端断开后的逻辑
                 */
                //将该客户端的输出流从共享集合中删除
                removeOut(pw);

                //System.out.println(host+"下线了!");
                sendMessage(nickname+"下线了!");
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
package chat;
/**
 * 聊天室客户端
 * @author Administrator
 *
 */

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    /*
     * java.net.Socket
     * 封装了TCP协议,使用它就可以基于TCP协议进行网络通讯
     * Socket是运行在客户端的
     */
    private Socket socket;

    /*
     * 构造方法,用来初始化客户端
     * 实例化Socket的时候需要传入两个参数:
     * 1、服务端地址:通过IP地址可以找到服务端的计算机
     * 2、服务端端口:通过端口可以找到服务端计算机上的服务端应用程序
     * 实例化Socket的过程就是连接的过程,若远程计算机没有响应会抛出异常。
     */
    public Client() throws Exception{
        System.out.println("正在连接服务端...");
        socket = new Socket("localhost", 8088);
        System.out.println("已与服务端建立连接!");
    }

    /*
     * 启动客户端的方法
     */
    public void start() {
        try {
            Scanner scanner = new Scanner(System.in);

            /*
             * 先要求用户输入一个昵称
             */
            String nickName = null;
            while (true) {
                System.out.println("请输入用户名:");
                nickName = scanner.nextLine();
                if (nickName.length() > 0) {
                    break;
                }
                System.out.println("输入有误!");
            }
            System.out.println("欢迎你"+nickName+"!开始聊天吧!");

            /*
             * socket提供的方法:
             * OutputStream getOutputStream()
             * 获取一个字节输出流,通过该流写出的数据会被发送至
             * 远端计算机。
             */
            OutputStream out = socket.getOutputStream();            
            OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");          
            PrintWriter pw = new PrintWriter(osw,true);

            /*
             * 先将昵称发送给服务端
             */
            pw.println(nickName);

            /*
             * 启动读取服务端发送过来消息的线程
             */
            ServerHandler handler = new ServerHandler();
            Thread t = new Thread(handler);
            t.start();

            while (true) {
                /*
                 * 将字符串发送至服务端
                 */
                pw.println(scanner.nextLine());
            }

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

    public static void main(String[] args) {
        try {
            Client client = new Client();
            client.start();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("客户端启动失败!");
        }
    }

    /*
     * 该线程用来读取服务端发送过来的消息
     * 并输出到客户端控制台显示。
     */
    class ServerHandler implements Runnable{
        public void run() {
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in,"UTF-8");
                BufferedReader br = new BufferedReader(isr);

                String message = null;
                while ((message = br.readLine()) != null) {
                    System.out.println(message);
                }
            } catch (Exception e) {

            }
        }
    }
}

二、UDP通信

2.1 DatagramPacket
2.1.1 构建接收包

  - DatagramPacket(byte[] buf,int length),将数据包中length长的数据装进buf数组。
  - DatagramPacket(byte[] buf,int offset,int length),将数据包中从offset开始,length长的数据装进buf数组。

2.1.2 构建发送包

  -DatagramPacket(byte[] buf,int length,InetAddress clientAddress,int clientPort),从buf数组中,取出length长的数据创建数据包对象,目标是clientAddress地址,clientPort端口,通常用来发送数据给客户端。
  -DatagramPacket(byte[] buf,int offset,int length,InetAddress clientAddress,int clientPort),从buf数组中,取出offset开始的、length长的数据创建数据包对象,目标是clientAddress地址,clientPort端口,通常用来发送数据给客户端。


2.2 DatagramSocket
2.2.1 服务端接收

  - DatagramSocket(int port),创建实例,并固定监听port端口的报文,通常用于服务端。
  - receive(DatagramPacket d),接收数据报文到d中,receive方法产生“阻塞”。

2.2.2 客户端发送

  无参的构造方法DatagramSocket(),通常用于客户端编程,它并没有特定监听的端口,仅仅使用一个临时的。程序会让操作系统分配一个可用的端口。
  - send(DatagramPacket dp),该方法用于发送报文dp到目的地。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值