网络初始2:网络编程--基于UDP和TCP实现回显器

发布于:2025-02-21 ⋅ 阅读:(19) ⋅ 点赞:(0)

基础概念

1.发送端与接受端

在通过网络传输信息时,会有两个进程,接收端和发送端。

发送端:数据的发送方进程,即网络通信中的源主机。

接收端:数据的接收方进程,即网路通信中的目的主机。

2.Socet套接字

Socket套接字,是由系统提供用于网络通信的技术。基于Socket套接字的网络程序开发就是网络编程。

Socket主要分为流套接字和数据报套接字,还有原始套接字(应用很少,不介绍)

流套接字:使用传输层协议Tcp,Tcp特点:1.有连接。2.面向字节流。3.可靠传输。4.有接受缓冲区,也有发送缓冲区。5.传输信息大小不限。6.全双工。

数据报套接字:使用传输层协议Udp,Udp特点:1.无连接。2.面向数据报。3.不可靠传输。4.有接受缓冲区,无发送缓冲区。5.一次最多传输64KB。6.全双工。

学习网络编程,本质上就是学习传输层提供给应用层的API,通过API来实现客户端和服务器。

UDP socketAPI 的使用

由于Udp是面向数据报的,所以核心的类都是和数据报有关的。

1.DategramSocket

分为两个版本,1.DategramSocket(),创建一个Udp数据报套接字的Socket,绑定到主机上任意一个空闲端口,一般用于客户端。2.DategramSocket(int port) :用于指定绑定的端口, 通过port传入,一般用于服务端。

2.send(DategramPacket p) 和 receive(DategramPacket p)

send:将数据打包为DategramPacket类型后发送出去,不会阻塞等待。

receive:从另一方接受DategramPacket类型的数据,如果还没收到会阻塞等待。

3.DategramPacket

由于UDP面向数据报,所以每次发送和接受都是一个DategramPacket类型的数据报,DategramPacket是UDP发送和接收的基本单位。在Udp中,传入的参数是1.byte[]数组(可以通过调用String.Byte()来转化);2.byte[]数组的长度,也可以通过String.Byte().length来传入;3.端口号,如果是客户端还需要传入服务器的IP地址。这个需要注意二者的不同。

实例:基于UDP实现一个简单的回显器

一.客户端

1.首先创建客户端要连接的服务器的端口号和IP地址,并在构造函数中将它们传入

package netWork;

import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;

public class UDPEchoClient {
private int Serverport = 0;
private String ServerIp = "";
DatagramSocket socket = null;

    public UDPEchoClient(int port, String Ip) throws SocketException {
        Serverport = port;
        ServerIp = Ip;
        this.socket = new DatagramSocket();
    }

}

2.创建start方法,通过Scannner函数读取用户传入的信息。

public void start(){
    System.out.println("客户端启动!");
    Scanner in = new Scanner(System.in);

}

3.创建一个while循环,将用户传入的信息进行打包为DategramPacket类型,然后发送给服务器。由于我们传入的IP号是String类型,所以需要通过InetAddres.getByName()方法来转换一下。

while (true){
        System.out.println("请输入:");
        String request = in.next();
        DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(ServerIp),Serverport);
        socket.send(requestPacket);
    }

同时注意一下异常的抛出,这里由于涉及到了IO的输入与输出,异常抛出是在所难免的。

4.发送完数据后,我们还需要创建一个DategramPacket类型的数据报来接受服务器传来的数据,同时将传来的数据转换为String类型并打印。这里我创建了一个大小为4096的byte[]数组来接收。

public void start() throws IOException {
    System.out.println("客户端启动!");
    Scanner in = new Scanner(System.in);
    while (true){
        System.out.println("请输入:");
        String request = in.next();
        DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(ServerIp),Serverport);
        socket.send(requestPacket);
        DatagramPacket responesPacket = new DatagramPacket(new byte[4096],4096);
        socket.receive(responesPacket);
        String respones = new String(responesPacket.getData(),0,responesPacket.getLength());
        System.out.println(respones);
    }
}

5.到这里差不多就写完了,我们还需要创建主类,调用stat方法就行了。(127.0.0.1为本机IP地址,由于实例是一个机器同时扮演服务器和客户端,所以用本机地址,而9999为随意指定的服务器端口号,只要是空闲的,不是知名端口号即可(>1024))

public static void main(String[] args) throws IOException {
        UDPEchoClient udpEchoClient = new UDPEchoClient(9999,"127.0.0.1");
        udpEchoClient.start();
}

整体代码

package netWork;

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

public class UDPEchoClient {
private int Serverport = 0;
private String ServerIp = "";
DatagramSocket socket = null;

    public UDPEchoClient(int port, String Ip) throws SocketException {
        Serverport = port;
        ServerIp = Ip;
        this.socket = new DatagramSocket();
    }
public void start() throws IOException {
    System.out.println("客户端启动!");
    Scanner in = new Scanner(System.in);
    while (true){
        System.out.println("请输入:");
        String request = in.next();
        DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(ServerIp),Serverport);
        socket.send(requestPacket);
        DatagramPacket responesPacket = new DatagramPacket(new byte[4096],4096);
        socket.receive(responesPacket);
        String respones = new String(responesPacket.getData(),0,responesPacket.getLength());
        System.out.println(respones);
    }
}
public static void main(String[] args) throws IOException {
        UDPEchoClient udpEchoClient = new UDPEchoClient(9999,"127.0.0.1");
        udpEchoClient.start();
}
}

二、服务端

1.与客户端类似,先创建DatagramSocket类型变量,再通过构造函数来绑定端口号。

public class UDPEchoServer {
    DatagramSocket socket = null;

    public UDPEchoServer(int port) throws SocketException {
        this.socket = new DatagramSocket(port);
    }
}

2.创建start方法,不过与客户端不同的是服务器是先接收,处理后再发送出去,其他的与客户端大同小异。

 public void start() throws IOException {
        System.out.println("服务器启动!");
        while(true){
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            String spones = process(request);
            DatagramPacket sponesPacket = new DatagramPacket(spones.getBytes(),spones.getBytes().length,requestPacket.getSocketAddress());
            socket.send(sponesPacket);
        }
    }

3.同时为了显示出数据的发送信息,我在最后加了一句打印信息的代码。而process方法则就是返回原字符串,印证了回显器的功能。

public void start() throws IOException {
        System.out.println("服务器启动!");
        while(true){
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            String spones = process(request);
            DatagramPacket sponesPacket = new DatagramPacket(spones.getBytes(),spones.getBytes().length,requestPacket.getSocketAddress());
            socket.send(sponesPacket);
            System.out.printf("[%s : %d],req: %s,spon: %s\n",requestPacket.getAddress(),requestPacket.getPort(),request,spones);
        }
    }

    private String process(String request) {
        return request;
    }

4.最后创建主类,调用方法即可。

package netWork;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UDPEchoServer {
    DatagramSocket socket = null;

    public UDPEchoServer(int port) throws SocketException {
        this.socket = new DatagramSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while(true){
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            String spones = process(request);
            DatagramPacket sponesPacket = new DatagramPacket(spones.getBytes(),spones.getBytes().length,requestPacket.getSocketAddress());
            socket.send(sponesPacket);
            System.out.printf("[%s : %d],req: %s,spon: %s\n",requestPacket.getAddress(),requestPacket.getPort(),request,spones);
        }
    }

    private String process(String request) {
        return request;
    }
public static void main(String[] args) throws IOException {
        UDPEchoServer udpEchoServer = new UDPEchoServer(9999);
        udpEchoServer.start();
}
}

效果:

TCP socketAPI 的使用

1.ServerSocket 

Socket类,对应到网卡,但是只可以给服务器使用。

2.Socket

也是对应到网卡,既可以给服务器使用,又可以给客户端使用。

在开发中二者我们都要用到。

3.accept()方法

Socket类中一个重要的方法,由于TCP是有连接的,所以我们需要accept来确认连接。如果没有客户端连接过来,accept也会进入阻塞状态。

实例:同样基于TCP实现一个回显器

一、服务端

1.同UDP服务器一样,先绑定端口号。

ServerSocket socket = null;

    public TcpServer(int port) throws IOException {
        socket = new ServerSocket(port);
    }

2.再使用Socket来创建一个变量,用来在建立连接后与客户端的通信,同时为了能与多个客户端同时通信,而且避免较大的线程开销,我们使用线程池来为每个客户端进程创建一个线程。

 public void start() throws IOException {
        System.out.println("服务器启动!");
        ExecutorService pool = Executors.newCachedThreadPool();
        while(true){
            Socket clientSocket = socket.accept();
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    processConnectin(clientSocket);
                }
            });
        }
    }

3.实现processConnection方法:首先打印连接到的客户机的信息,并提醒其上线了,由于TCP是面向字节流的,所以我创建了InputStream和OutputStream对象来接收和传输数据。这里在try括号里创建,好处是try-catch的结尾自动调用close方法,避免了占用过多的文件描述符。

private void processConnectin(Socket clientSocket) {
        System.out.printf("[%s : %d]客户机上线了!\n",clientSocket.getInetAddress(),clientSocket.getPort());
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){
            while(true){
                Scanner in = new Scanner(inputStream);
                if(!in.hasNext()) {
                    System.out.printf("[%s : %d]客户机已经下线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                String request = in.next();
                String sponse = process(request);
                
            }
        }catch (IOException e){
            throw new RuntimeException(e);
        }
    }

4.对于要发送信息的处理,我们可以使用OutputStream的方法,但是更好的方法是使用PrintWrite来封装一下outputstream,一方面是好处理换行,另一方面是因为TCP有发送缓存,如果我发送的信息太少的话,它是会先存到缓存中而不去发送,而PrintWrite的flush方法可以强制将缓冲区中的数据立即写入到关联的输出流中。同时不要忘记关闭clientsocket。

 private void processConnectin(Socket clientSocket) {
        System.out.printf("[%s : %d]客户机上线了!\n",clientSocket.getInetAddress(),clientSocket.getPort());
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){
            while(true){
                Scanner in = new Scanner(inputStream);
                if(!in.hasNext()) {
                    System.out.printf("[%s : %d]客户机已经下线!\n",clientSocket.getInetAddress(),
                            clientSocket.getPort());
                    break;
                }
                String request = in.next();
                String sponse = process(request);
                PrintWriter writer = new PrintWriter(outputStream);
                writer.println(sponse);
                writer.flush();
                System.out.printf("[%s : %d]req: %s,spon: %s\n",clientSocket.getInetAddress(),
                        clientSocket.getPort(),request,sponse);
            }
        }catch (IOException e){
            throw new RuntimeException(e);
        }finally {
            try{
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

5.最后创建主类,调用方法即可。

package Tcp;

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;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class TcpServer {
    ServerSocket socket = null;

    public TcpServer(int port) throws IOException {
        socket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动!");
        ExecutorService pool = Executors.newCachedThreadPool();
        while(true){
            Socket clientSocket = socket.accept();
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    processConnectin(clientSocket);
                }
            });
        }
    }
    private void processConnectin(Socket clientSocket) {
        System.out.printf("[%s : %d]客户机上线了!\n",clientSocket.getInetAddress(),clientSocket.getPort());
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){
            while(true){
                Scanner in = new Scanner(inputStream);
                if(!in.hasNext()) {
                    System.out.printf("[%s : %d]客户机已经下线!\n",clientSocket.getInetAddress(),
                            clientSocket.getPort());
                    break;
                }
                String request = in.next();
                String sponse = process(request);
                PrintWriter writer = new PrintWriter(outputStream);
                writer.println(sponse);
                writer.flush();
                System.out.printf("[%s : %d]req: %s,spon: %s\n",clientSocket.getInetAddress(),
                        clientSocket.getPort(),request,sponse);
            }
        }catch (IOException e){
            throw new RuntimeException(e);
        }finally {
            try{
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        TcpServer tcpServer = new TcpServer(9998);
        tcpServer.start();
    }
}

二、客户端

1.首先创建Socket和构造函数,值得注意的是TCP是有连接的,所以Socket可以记住ip和port,不需要在类中创建这些变量。

private Socket socket = null;

    public TcpClient(int port,String ip) throws IOException {
        this.socket = new Socket(ip,port);
    }

2.start方法:首先创建InputStream和OutputStream,然后创建用于发送和接收的Scanner(因为TCP是面向字节流的,所以要用Scanner来接收数据),最后用PrintWrite来对发送的数据进行处理即可。

 public void start(){
        System.out.println("客户端启动!"); 
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){
            Scanner sendFile = new Scanner(System.in);
            Scanner receiveFile = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);
            while(true){
                System.out.println("请输入:");
                String request = sendFile.next();
                writer.println(request);
                writer.flush();
                String spones = receiveFile.next();
                System.out.println(spones);
            }

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

3.创建主类,调用方法。

package Tcp;

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 TcpClient {
private Socket socket = null;

    public TcpClient(int port,String ip) throws IOException {
        this.socket = new Socket(ip,port);
    }
    public void start(){
        System.out.println("客户端启动!");
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){
            Scanner sendFile = new Scanner(System.in);
            Scanner receiveFile = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);
            while(true){
                System.out.println("请输入:");
                String request = sendFile.next();
                writer.println(request);
                writer.flush();
                String spones = receiveFile.next();
                System.out.println(spones);
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) throws IOException {
        TcpClient tcpClient = new TcpClient(9998,"127.0.0.1");
        tcpClient.start();
    }
}

测试效果:


网站公告

今日签到

点亮在社区的每一天
去签到