用一张图来认识一下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:
- 基于连接,也就是两台计算机进行传输数据之前,必然先进行连接,也就是TCP中的三次握手,建立连接。
- TCP连接,传输数据没有大小限制,一旦建立连接,可以按照约定的数据格式,传输所需数据。
- TCP是一个可靠的协议,它会确保接受双方能够正确完整的接收到对方传输的数据(基于连接,保证了可靠性)。
UDP:
- 基于数据包来发送数据,数据包中包括了目标的IP、端口以及所要发送的数据。
- 传输数据大小受到了限制,每个数据包都必须小于64k。
- 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);
}
}
}
先启动服务端,再启动客户端,演示动画: