0


Java网络编程基础

一、Socket套接字

1,概念

Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基 于Socket套接字的网络程序开发就是网络编程。

2,分类

流套接字:使用传输层TCP协议

TCP,即Transmission Control Protocol(传输控制协议),传输层协议。

以下为TCP的特点

    有连接

    可靠传输

    面向字节流

    有接收缓冲区,也有发送缓冲区

    大小不限

数据报套接字:使用传输层UDP协议

UDP,即User Datagram Protocol(用户数据报协议),传输层协议。

以下为UDP的特点

    无连接

    不可靠传输

    面向数据报

    有接收缓冲区,无发送缓冲区

    大小受限:一次最多传输64k

二、Java数据报套接字通信模型

三、Java流套接字通信模型

四、UDP数据报套接字编程

1,DatagramSocket API

DatagramSocket是UDP Socket,用于发送和接收UDP数据报。

DatagramSocket方法
方法签名方法说明void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻 塞等待)void send(DatagramPacket p)从此套接字发送数据报包(不会阻塞等待,直接发送)void close()关闭此数据报套接字
DatagramSocket构造方法
方法签名方法说明DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口 (一般用于客户端)DatagramSocket(int port)创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用 于服务端)

2,DatagramPacket API

DatagramPacket是UDP Socket发送和接收的数据

DatagramPacket方法
方法签名方法说明InetAddress getAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取 接收端主机IP地址int getPort()从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获 取接收端主机端口号byte[] getData()获取数据报中的数据
DatagramPacket构造方法
方法签名方法说明DatagramPacket(byte[] buf, int length)构造一个DatagramPacket以用来接收数据报,接收的数据保存在 字节数组(第一个参数buf)中,接收指定长度(第二个参数 length)DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)构造一个DatagramPacket以用来发送数据报,发送的数据为字节 数组(第一个参数buf)中,从0到指定长度(第二个参数 length)。address指定目的主机的IP和端口号

3,示例代码

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

public class UdpEchoServer {

    //对于一个服务器来说,核心分成两部
    //进入初始化操作(实例化Socket对象)
    //进入主循环,接受请求
    //读取响应
    //根据请求计算响应
    //把响应写回客户端
    private DatagramSocket socket = null;
    public UdpEchoServer(int port) throws SocketException {
        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()).trim();
            //根据请求计算响应
            String response = process(request);
            //把响应写回客户端,响应数据就是response,需要包装成一个对象
            DatagramPacket datagramPacket = new DatagramPacket(response.getBytes(),response.getBytes().length
            ,requestPacket.getSocketAddress());
            socket.send(requestPacket);
            System.out.printf("[%s:%d] req: %s; resp: %s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }

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

    public static void main(String[] args) throws IOException {
        UdpEchoServer udpEchoServer = new UdpEchoServer(9090);
        udpEchoServer.start();
    }

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

public class UdpEchoClient {

    //1,读客户端的数据
    //2,构造请求发送给客户端
    //3,从服务器读取响应
    //4,把响应写回客户端

    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;

    //需要在启动客户端的时候来指定那个服务器
    public UdpEchoClient(String serverIp,int serverPort) throws SocketException {
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        socket = new DatagramSocket();
    }
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (true){
            //读取用户数据
            System.out.println("->");
            String request = scanner.nextLine();
            if (request.equals("exit")){
                break;
            }
            //构造请求发送给服务器
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
                    request.getBytes().length, InetAddress.getByName(serverIp),serverPort);
            socket.send(requestPacket);
            //从服务器读取响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(),0,responsePacket.getLength()).trim();
            //显示数据
            System.out.println(response);
        }
    }

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

五、TCP流套接字编程

1,ServerSocket API

ServerSocket是创建TCP服务端Socket的API

ServerSocket构造方法
方法签名方法说明ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口
ServerSocket方法
方法签名方法说明Socket accept()开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket 对象,并基于该Socket建立与客户端的连接,否则阻塞等待void close()关闭此套接字

2,Socket API

Socket是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端 Socket。不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

Socket构造方法
方法签名方法说明Socket(String host, int port)Socket(String host, int port)
Socket方法
方法签名方法说明InetAddress getInetAddress()返回套接字所连接的地址InputStream getInputStream()返回此套接字的输入流OutputStream getOutputStream()返回此套接字的输出流

3,示例代码

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

public class TcpEchoServer {
    //1,初始化服务器
    //2,进入主循环
    //先从内核中获取到一个TCP连接
    //处理这个TCP连接
    //读取请求并解析
    //根据请求计算响应
    //把响应写回客户端

    private ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            //先从内核中获取一个TCP连接
            Socket clientSocket = serverSocket.accept();
            //处理这个连接
            processConnection(clientSocket);
        }
    }

    private void processConnection(Socket clientSocket) {

        System.out.printf("[%s:%d]客户端上线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        //通过clientSocket来与客户端交互
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
            //此处建立一个连接过程,处理多个请求和响应
            while (true){
                //读取请求响应
                String request = bufferedReader.readLine();
                //根据请求计算响应
                String response = process(request);
                //把响应写回客户端
                bufferedWriter.write(response + "\n");
                bufferedWriter.flush();
                System.out.printf("[%s:%d] req: %s; resp: %s\n",clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),request,response);
            }

        } catch (IOException e) {
            e.printStackTrace();
            System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress().toString(),
                    clientSocket.getPort());
        }

    }

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

    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
        tcpEchoServer.start();
    }
    
}
import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class TcpClientServer {
    //启动客户端
    //进入主循环
    //读取用户内容
    //构造一个请求发送给服务器
    //读取服务器的响应的数据
    //把响应写到界面
    private Socket socket = null;

    public TcpClientServer(String serverIp,int serverPort) throws IOException {
        //实例化Socket,建立TCP连接
        socket = new Socket(serverIp,serverPort);
    }

    public void start(){

        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))){

            while (true){
                //读取用户输入内容
                System.out.println("->");
                String request = scanner.nextLine();
                if (request.equals("exit")){
                    break;
                }
                //构造请求并发送
                bufferedWriter.write(request + "\n");
                bufferedWriter.flush();
                //读取响应数据
                String response = bufferedReader.readLine();
                //把响应写到界面
                System.out.println(response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpClientServer tcpClientServer = new TcpClientServer("127.0.0.1",9090);
        tcpClientServer.start();
    }
    
}

getOutputStream得到一个流对象,进一步封装成一个BufferedWriter

代码调用BufferedWriter.write方法的时候,先把数据放在缓冲区,此时wrtite操作并没有往内核中写socket文件中的数据。

调用flush方法,把内存缓冲区中的内容写入Socket文件中

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpThreadEchoServer {
    private ServerSocket serverSocket = null;

    public TcpThreadEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            Socket clientSocket = serverSocket.accept();
            Thread t = new Thread(){
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            };
            t.start();
        }
    }

    public void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
             BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
            while (true) {
                // 1. 读取请求并解析
                String request = bufferedReader.readLine();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 把响应写回到客户端
                bufferedWriter.write(response + "\n");
                bufferedWriter.flush();

                System.out.printf("[%s:%d] req: %s; resp: %s\n", clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(), request, response);
            }
        } catch (IOException e) {
            // e.printStackTrace();
            System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress().toString(),
                    clientSocket.getPort());
        }
    }

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

    public static void main(String[] args) throws IOException {
        TcpThreadEchoServer server = new TcpThreadEchoServer(9090);
        server.start();
    }
}

主线程专门负责accept,其他线程负责和客户端沟通

 public void start() throws IOException {
        System.out.println("服务器启动");
        //先创建一个线程池实例
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true){
            //针对这个连接,单独创建一个线程池来负责
            Socket clientSocket = serverSocket.accept();
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }

虽然使用多线程解决了BUG,但还有很多问题,每次来一个客户端,都要分配一个线程对于一个服务器来说,随时可能会来大量的客户端,随时也会有大量的客户端断开连接,服务器需要频繁的创建和销毁线程,所以可以用线程池

标签: 网络 java tcp/ip

本文转载自: https://blog.csdn.net/qq_50156012/article/details/123261275
版权归原作者 /少司命 所有, 如有侵权,请联系我们删除。

“Java网络编程基础”的评论:

还没有评论