Java socket编程详解,TCP&UDP实现

用一张图来认识一下TCP和UDP

TCP点对点的传输方式,保证了数据的可达性;UDP只管发送数据,至于服务端能否接收到数据,不在它的保证范围之内。

下面,我们进入正题。

一、网络架构模型

TCP/IP协议分为分层架构:物理层、数据链路层、网络层、传输层、应用层。

应用层:能被用户感知到的一层,如浏览器的http、https协议,远程连接工具的ftp、ftps协议等。

传输层:两台计算机之间的交互数据传输,就在这一层完成,传输层为上层协议提供端到端的可靠和透明的服务。TCP和UDP就是在这一层,是应用层协议的基础。进程的端口号,就是在这一层。

网络层:也就是IP层,两台计算机之间进行通信,先通过IP找到目标计算机,再根据端口号定位到具体的进程,然后进行数据传输。IP协议是internet 的基础。

二、socket编程

socket本质是编程语言的API,在Java中就是具体的类,socket 类对TCP/IP和UDP/IP协议进行了封装,提供一个可以供程序员做开发使用的接口。

TCP和UDP的区别

TCP:

  1. 基于连接,也就是两台计算机进行传输数据之前,必然先进行连接,也就是TCP中的三次握手,建立连接
  2. TCP连接,传输数据没有大小限制,一旦建立连接,可以按照约定的数据格式,传输所需数据。
  3. TCP是一个可靠的协议,它会确保接受双方能够正确完整的接收到对方传输的数据(基于连接,保证了可靠性)。

UDP:

  1. 基于数据包来发送数据,数据包中包括了目标的IP、端口以及所要发送的数据。
  2. 传输数据大小受到了限制,每个数据包都必须小于64k。
  3. UDP是一个不可靠的协议;双方数据传输不需要建立连接,发送方封装好数据后,只管发送,不管接受方能不能接收到数据。

TCP 的三次握手

第一次握手:客户端向服务端发送sny包,包括标记sny=1和数据seq=x,并进入sny_send状态。

第二次握手:服务端接收到客户端的TCP报文,确认客户端发送的数据seq=x没有问题,向客户端返回TCP报文,包括标记sny=1,确认ack=x+1,数据seq=y,并进入sny_recv状态。

第三次握手:客户端接收到服务端返回的TCP报文,确认服务端发送的数据seq=y没有问题,向服务端发送TCP报文,包括标记sny=1,确认ack=y+1, seq=x+1,并进入established状态。

服务端接收到客户端的TCP报文,进入established状态,双方可以进行数据传输。

三、TCP协议实现

socket实现的TCP协议客户端: TcpClient.java

import java.io.*;
import java.net.UnknownHostException;
import java.util.Scanner;
public class TcpClient {
	private final static String SERVER_IP = "127.0.0.1";
	private final static int SERVER_PORT = 10888;
	public static void main(String[] args) throws Exception {
		TcpClient tcpClient = new TcpClient();
		Scanner in = new Scanner(System.in);
		while(true) {
			String msg = in.next();
			if("exit".equals(msg)) {
				break;
			}
			tcpClient.startTcpClient(SERVER_IP, SERVER_PORT, msg);
		}
	}
	public void startTcpClient(String ip, int port, String msg) throws Exception {
		//创建客户端 socket,并连接服务端IP:port
		Socket socket = new Socket(ip, port);
		//打开socket数据输出流
		OutputStream outputStream = socket.getOutputStream();
		PrintWriter printWriter = new PrintWriter(outputStream);
		//将数据 写入输出流
		printWriter.write(msg);
		//刷新缓冲区,输出缓冲区的数据;如果不刷新,服务端接收到数据
		printWriter.flush();
		//关闭socket客户端的输出流
		//当socket的写操作打开后,会一直阻塞,所以,每次写完数据,需要关闭socket的写操作
		socket.shutdownOutput();
		
		/************* 客户端接受服务器返回的数据 ***************/
		
		//打开socket输入流
		InputStream inputStream = socket.getInputStream();
		//字节流转字符流,并指定编码
		InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"UTF-8");
		//字符流封装成缓冲流
		BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
		
		StringBuffer stringBuffer = new StringBuffer();
		String len = null;
		while((len = bufferedReader.readLine()) != null) {
			stringBuffer.append(len);
		}	
		System.out.println("接收到客户端返回的数据:" + stringBuffer.toString());
		//关闭流
		bufferedReader.close();
		inputStreamReader.close();
		inputStream.close();
		printWriter.close();
		outputStream.close();		
	}
}

socket实现的TCP协议服务端:TcpServer.java

import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer {
    public static void main(String[] args) throws Exception {
        TcpServer tcpServer = new TcpServer();
        tcpServer.startServer(10888);
    }
    public void startServer(int port) throws Exception {
        //创建一个socket服务端,监听port 端口
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("TCP服务器启动,并监听端口:" + port);

        //创建一个连接客户端的socket
        Socket socket = null;
        //创建一个循环,web项目以监听实现
        int count = 0;
        while(true){
            //监听客户端,等待客户端连接
            socket = serverSocket.accept();
            System.out.println("第" + count + "个客户端请求开始处理!");
            //创建一个线程,专门处理当前客户端的请求,以便继续处理其他客户端请求,避免阻塞
            TcpServerThread tcpServerThread = new TcpServerThread(socket);
            Thread thread = new Thread(tcpServerThread);
            thread.start();
            count++;
        }
    }
}

服务端处理客户端请求的线程: TcpServerThread.java

import java.io.*;
import java.net.Socket;
public class TcpServerThread implements Runnable {
    private Socket socket;
    public TcpServerThread(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        PrintWriter printWriter = null;
        OutputStream outputStream = null;

        try {
            //获取socket 字节输入流
            inputStream = socket.getInputStream();
            //字节流转字符流,指定编码
            inputStreamReader = new InputStreamReader(inputStream,"utf-8");
            //字符流封装成缓冲字符流
            bufferedReader = new BufferedReader(inputStreamReader);
            StringBuffer stringBuffer = new StringBuffer();
            String len = null;
            while((len = bufferedReader.readLine()) != null){
                stringBuffer.append(len);
            }
            System.out.println("客户端的地址:" + socket.getInetAddress() + ",客户端的端口:" + socket.getPort() + "接收到客户端的数据为:" + stringBuffer.toString());
            /***************   服务端返回数据给客户端  ***************/
            outputStream = socket.getOutputStream();
            printWriter = new PrintWriter(outputStream);
            printWriter.write("客户端你好,我接受到了你的数据:" + stringBuffer.toString());
            //刷新缓冲区,输出缓冲区的数据,如果不刷新,客户端接受不到数据
            printWriter.flush();
            //当socket的写操作打开后,会一直阻塞,所以,每次写完数据,需要关闭socket的写操作
            socket.shutdownOutput();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                printWriter.close();
                outputStream.close();
                bufferedReader.close();
                inputStreamReader.close();
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

先启动服务端,再启动客户端,演示动画:
在这里插入图片描述

四、UDP协议的实现

UDP客户端:UdpClient.java

import java.net.*;
import java.util.Scanner;
public class UdpClient {
	public final static String SERVER_IP = "127.0.0.1";
	public final static int SERVER_PORT = 10999;
    public final static int BYTE_LENGTH = 1024;
	public static void main(String[] args) throws Exception {
		UdpClient udpClient = new UdpClient();
		Scanner in = new Scanner(System.in);
		while(true) {
			String msg = in.next();
			if("exit".equals(msg)) {
				break;
			}
			udpClient.startUdpClient(SERVER_IP, SERVER_PORT, msg);
		}
	}
	
	public void startUdpClient(String ip, int port, String msg) throws Exception {
		//创建一个UDP的客户端
		DatagramSocket datagramSocket = new DatagramSocket();
		//发送给服务端的数据
		byte[] bytes = msg.getBytes("UTF-8");
		//封装数据包,包括数据、服务端IP、服务端端口
		DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, InetAddress.getByName(ip), port);
		//发送数据
		datagramSocket.send(datagramPacket);
		/*********** 接收服务端发来的消息, 也可以像服务端开启新的线程 ************/
		//用来接受服务端发来的数据
		byte[] receiveData = new byte[BYTE_LENGTH];
		//封装数据报,用来接收数据
		DatagramPacket receiveDataPacket = new DatagramPacket(receiveData, receiveData.length);
		//接受数据,阻塞,直到有数据发来
		datagramSocket.receive(receiveDataPacket);
		String serverSendData = new String(receiveDataPacket.getData(),0,receiveDataPacket.getLength(),"UTF-8");
		System.out.println("服务端返回的数据:" + serverSendData + ",服务端IP:" + receiveDataPacket.getAddress() + ",服务端端口:" + receiveDataPacket.getPort());
	}
}

UDP服务端:UdpServer.java

import java.net.*;
public class UdpServer {
    public final static int SERVER_PORT = 10999;
    public final static int BYTE_LENGTH = 1024;
    public static void main(String[] args) throws Exception {
        UdpServer udpServer = new UdpServer();
        udpServer.startUdpServer(SERVER_PORT);
    }
    public void startUdpServer(int port) throws Exception {
        // 创建UDP服务端,监听port端口
        DatagramSocket datagramSocket = new DatagramSocket(port);
        //创建一个字节数组,用来接受客户端发来的数据
        byte[] bytes = new byte[BYTE_LENGTH];
        //创建数据报,用来接收数据
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
        System.out.println("开启UDP服务端,并监听端口:" + port);
        //创建一个循环,接收和处理客户端发送的数据
        while (true){
            //接收客户端发送的数据,阻塞,直到有客户端发送数据
            datagramSocket.receive(datagramPacket);
            //获取接收到的数据
            String receiveData = new String(datagramPacket.getData(),0,datagramPacket.getLength(),"UTF-8");
            //获取客户端IP
            InetAddress clientIP = datagramPacket.getAddress();
            //获取客户端端口号
            int clientPort = datagramPacket.getPort();
            System.out.println("接收到客户端发送的数据:" + receiveData + ",客户端IP:" + clientIP + ",客户端端口:" + clientPort);
            //服务端返回给客户端的数据
            byte[] sendData = ("客户端你好!"+System.currentTimeMillis()).getBytes("UTF-8");
            //将数据、客户端IP、客户端端口封装进数据报DatagramPacket里面
            DatagramPacket sendDataPacket = new DatagramPacket(sendData, sendData.length, clientIP, clientPort);
            //发送数据
            datagramSocket.send(sendDataPacket);
        }
    }
}

先启动服务端,再启动客户端,演示动画:
在这里插入图片描述


版权声明:本文为weixin_42045540原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。