TCP协议简单介绍

  1. TCP是TCP协议中非常重要和常用的通信协议,可以实现可靠的网络通信

     特点:
     需要创建连接 需要三次握手
     底层建立的连接流 数据包以流的方式传递 没有传输数据量大小的限制
     传输过程中 可以保证数据一定不会丢 也不会多 也可以保证 顺序的一致
     
     速度比较慢在可靠性要求比较高 速度要求比较低 的场景下优先使用
    

2. Java中实现TCP
在TCP通信中,通信的过程需要两端的参与,其中发起请求的端称之为客户端 被动等待请求的端称之为服务器端

在这里插入图片描述
案例:实现TPC通信
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;

		/**
		 * TCP通信的客户端
		 */
		public class Demo01Client {
			public static void main(String[] args) throws Exception {
				//1.创建Socket
				Socket socket = new Socket();
				//2.连接服务器
				socket.connect(new InetSocketAddress("127.0.0.1", 44444));
				//3.从客户端发送消息给服务端
				OutputStream out = socket.getOutputStream();
				//4.向服务端发送数据
				String msg = "hello world~ ";
				out.write(msg.getBytes());
				out.flush();
				//5.从服务端接受数据
				InputStream in = socket.getInputStream();
				byte [] data = new byte[1024];
				int len = in.read(data);
				String msg2 = new String(data,0,len);
				System.out.println(msg2);
				//6.关闭套接字
				socket.close();
			}
		}
		
		
		import java.io.InputStream;
		import java.io.OutputStream;
		import java.net.InetSocketAddress;
		import java.net.ServerSocket;
		import java.net.Socket;
		
		/**
		 * TCP通信的服务端
		 */
		public class Demo01Server {
			public static void main(String[] args) throws Exception {
				//1.创建服务端
				ServerSocket ss = new ServerSocket();
				//2.绑定指定端口
				ss.bind(new InetSocketAddress(44444));
				//3.等待客户端连接
				Socket socket = ss.accept();
				//4.接受客户端的数据
				InputStream in = socket.getInputStream();
				byte [] data = new byte[1024];
				int len = in.read(data);
				String str = new String(data,0,len);
				System.out.println(str);
				//5.向客户端返回数据
				String msg = "hello net~";
				OutputStream out = socket.getOutputStream();
				out.write(msg.getBytes());
				out.flush();
				//6.关闭套接字
				socket.close();
				ss.close();
			}
		}

3. 在tcp网络通信中应用多线程技术实现一个服务端为多个客户端提供服务
在这里插入图片描述
案例:实现文件上传服务器 并且 通过多线程技术来实现同时处理多个客户端的效果
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;

		/**
		 * TCP案例:通过TCP实现文件上传 - 服务端代码
		 */
		
		public class Demo02UploadServer {
			public static void main(String[] args) throws Exception {
				//1.创建服务端
				ServerSocket ss = new ServerSocket();
				ss.bind(new InetSocketAddress(44444));
				System.out.println("###千度网盘开始运行了###");
				//2.不停接受客户端连接,一旦连接成功,交给线程处理
				while(true){
					Socket socket = ss.accept();
					new Thread(new UploadRunnable(socket)).start();
				}
			}
		}
		
		class UploadRunnable implements Runnable{
			private Socket socket = null;
			public UploadRunnable(Socket socket) {
				this.socket  = socket;
			}
			
			@Override
			public void run() {
				OutputStream out = null;
				try {
					//1.获取socket输入流
					InputStream in = socket.getInputStream();
					//2.创建文件输出流指向输出位置
					String path = "upload/"+UUID.randomUUID().toString()+".data";
					out = new FileOutputStream(path);
					//3.对接流
					byte [] data = new byte[1024];
					int i = 0;
					while((i = in.read(data))!=-1){
						out.write(data,0,i);
					}
					System.out.println("接收到了来自["+socket.getInetAddress().getHostAddress()+"]的上传文件["+path+"]");
				} catch (IOException e) {
					e.printStackTrace();
				} finally {
					//4.关闭资源
					if(out!=null){
						try {
							out.close();
						} catch (IOException e) {
							e.printStackTrace();
						} finally {
							out = null;
						}
					}
					if(socket != null){
						try {
							socket.close();
						} catch (IOException e) {
							e.printStackTrace();
						} finally {
							socket = null;
						}
					}
				}
			}
		}
		

		
		import java.io.File;
		import java.io.FileInputStream;
		import java.io.FileNotFoundException;
		import java.io.IOException;
		import java.io.InputStream;
		import java.io.OutputStream;
		import java.net.InetSocketAddress;
		import java.net.Socket;
		import java.util.Scanner;
		
		/**
		 * TCP案例:通过TCP实现文件上传 - 客户端代码
		 */
		public class Demo02UploadClient {
			public static void main(String[] args) {
				Scanner scanner = null;
				InputStream in = null;
				Socket socket = null;
				try {
					//1.要求用户输入文件路径
					scanner = new Scanner(System.in);
					System.out.println("--请输入要上传的文件的路径:");
					String path = scanner.nextLine();
					File file = new File(path);
					//2.只有文件存在 且 是一个文件才上传
					if(file.exists() && file.isFile()){
						//2.创建连接文件的输入流
						in = new FileInputStream(file);
						//3.创建TCP客户端对象
						socket = new Socket();
						//4.连接TCP服务端
						socket.connect(new InetSocketAddress("127.0.0.1",44444));
						//5.获取到TCP服务端的输出流
						OutputStream out = socket.getOutputStream();
						//6.对接流 发送文件数据给服务端
						byte [] data = new byte[1024];
						int i = 0;
						while((i = in.read(data))!=-1){
							out.write(data,0,i);
						}
					}else{
						throw new RuntimeException("文件不存在 或者是一个文件夹~~");
					}
				} catch (Exception e) {
					e.printStackTrace();
				} finally{
					//7.关闭扫描器 关闭文件输入流 关闭套接字
					if(scanner != null){
						scanner.close();
					}
					if(in != null){
						try {
							in.close();
						} catch (IOException e) {
							e.printStackTrace();
						} finally {
							in = null;
						}
					}
					if(socket!=null){
						try {
							socket.close();
						} catch (IOException e) {
							e.printStackTrace();
						} finally {
							socket = null;
						}
					}
				}
			}
		}

4. TCP通信中的粘包问题
a. 粘包问题概述
TCP通信中 如果连续传输多段数据 ,TCP在传输的过程中,会根据需要自动的拆分包合并包,造成数据边界信息丢失了,在接收端收到数据后无法判断数据的边界在哪里,这样的问题就称之为TCP通信中的粘包问题。
粘包问题的本质在于TCP协议是传输层的协议,而数据边界判断的问题本质上是会话层的问题,TCP协议并没有给予解决方案。
而socket编程是给予网络层 和 传输层的编程,没有会话层的功能提供,所以在socket编程中粘包问题需要程序开发人员自己想办法解决
在这里插入图片描述
b. 粘包问题解决方案
i. 只传输固定长度的数据
能解决粘包问题,但是程序的灵活性非常低,只能在每次传输的数据长度都一致的情况下使用,应用的场景比较少
ii. 约定分隔符
能解决粘包问题,但是如果数据本身包含分隔符,则需要进行转义。转义的过程比较麻烦,浪费时间,代码写起来也比较复杂
iii. 使用协议
在通信双发原定数据传输的格式 发送方严格按照格式发送 接收方严格按照格式接收 从而根据格式本身判断数据的边界

			协议又分为公有协议 和 私有协议
			使用公有协议:
				利用会话层 传输层 应用层 的公有协议的规则 来传输数据 判断边界
				在全世界范围内通用 但协议相对复杂
			使用私有协议:
				自己来约定传输双方使用的格式 从而来判断边界
				协议可以根据需要定制 但只在有限的小范围内有效

案例:改造文件上传案例 通过自定义协议解决粘包问题 实现传输文件同时传输文件名
在这里插入图片描述
协议格式: [文件名长度]\r\n[文件名][文件内容长度]\r\n[文件内容]

		import java.io.File;
		import java.io.FileInputStream;
		import java.io.IOException;
		import java.io.InputStream;
		import java.io.OutputStream;
		import java.net.InetSocketAddress;
		import java.net.Socket;
		import java.util.Scanner;
		
		/**
		 * TCP案例:通过TCP实现文件上传 - 客户端代码
		 */
		public class Demo02UploadClient {
			public static void main(String[] args) {
				Scanner scanner = null;
				InputStream in = null;
				Socket socket = null;
				try {
					//1.要求用户输入文件路径
					scanner = new Scanner(System.in);
					System.out.println("--请输入要上传的文件的路径:");
					String path = scanner.nextLine();
					File file = new File(path);
					//2.只有文件存在 且 是一个文件才上传
					if(file.exists() && file.isFile()){
						//2.创建连接文件的输入流
						in = new FileInputStream(file);
						//3.创建TCP客户端对象
						socket = new Socket();
						//4.连接TCP服务端
						socket.connect(new InetSocketAddress("127.0.0.1",44444));
						//5.获取到TCP服务端的输出流
						OutputStream out = socket.getOutputStream();
						//6.1向服务器发送[文件名字节长度\r\n]
						out.write((file.getName().getBytes().length+"\r\n").getBytes());
						//6.2向服务器发送[文件名字节]
						out.write(file.getName().getBytes());
						//6.3向服务器发送[文件内容字节长度\r\n]
						out.write((file.length()+"\r\n").getBytes());
						//6.4向服务器发送[文件内容字节]
						byte [] data = new byte[1024];
						int i = 0;
						while((i = in.read(data))!=-1){
							out.write(data,0,i);
						}
					}else{
						throw new RuntimeException("文件不存在 或者是一个文件夹~~");
					}
				} catch (Exception e) {
					e.printStackTrace();
				} finally{
					//7.关闭扫描器 关闭文件输入流 关闭套接字
					if(scanner != null){
						scanner.close();
					}
					if(in != null){
						try {
							in.close();
						} catch (IOException e) {
							e.printStackTrace();
						} finally {
							in = null;
						}
					}
					if(socket!=null){
						try {
							socket.close();
						} catch (IOException e) {
							e.printStackTrace();
						} finally {
							socket = null;
						}
					}
				}
			}
		}

		
		import java.io.FileOutputStream;
		import java.io.IOException;
		import java.io.InputStream;
		import java.io.OutputStream;
		import java.net.InetSocketAddress;
		import java.net.ServerSocket;
		import java.net.Socket;
		
		/**
		 * TCP案例:通过TCP实现文件上传 - 服务端代码
		 */
		
		public class Demo02UploadServer {
			public static void main(String[] args) throws Exception {
				//1.创建服务端
				ServerSocket ss = new ServerSocket();
				ss.bind(new InetSocketAddress(44444));
				System.out.println("###千度网盘开始运行了###");
				//2.不停接受客户端连接,一旦连接成功,交给线程处理
				while(true){
					Socket socket = ss.accept();
					new Thread(new UploadRunnable(socket)).start();
				}
			}
		}
		
		class UploadRunnable implements Runnable{
			private Socket socket = null;
			public UploadRunnable(Socket socket) {
				this.socket  = socket;
			}
			
			/**
			 * 通过私有协议传输数据 协议的格式为 [文件名长度\r\n文件名 文件长度\r\n文件内容]
			 */
			@Override
			public void run() {
				OutputStream out = null;
				try {
					//1.获取socket输入流
					InputStream in = socket.getInputStream();
					//2.获取文件名 - 读到第一个回车换行之前 截取出文件名的长度 接着读取这个长度的字节 就是文件名
					//--读取数据 直到遇到第一个回车换行
					//----每次从流中读取一个字节 转成字符串 拼到line上 只要line还不是\r\n结尾 就重复这个过程
					String line = "";
					byte [] tmp = new byte[1];
					while(!line.endsWith("\r\n")){
						in.read(tmp);
						line += new String(tmp);
					}
					//----读取到了 文件名长度\r\n 截掉\r\n 转成int 就是文件名的长度
					int len = Integer.parseInt(line.substring(0, line.length()-2));
					//----从流中接着读 len个字节 就是文件名
					byte [] data = new byte[len];
					in.read(data);
					String fname = new String(data);
					
					//3.读取文件内容  - 读到下一个回车换行之前 截取出文件内容的长度 接着读取这个长度的字节 就是文件内容
					String line2 = "";
					byte [] tmp2 = new byte[1];
					while(!line2.endsWith("\r\n")){
						in.read(tmp2);
						line2 += new String(tmp2);
					}
					//----读取到了 文件长度\r\n 截掉\r\n 转成int 就是文件的长度
					int len2 = Integer.parseInt(line2.substring(0, line2.length()-2));
					//4.从流中读取 文件长度个字节 就是文件内容 输出到文件中
					byte data2 [] = new byte[len2];
					in.read(data2);
					
					//5.创建文件输出流指向输出位置,将数据写出到流中
					String path = "upload/"+fname;
					out = new FileOutputStream(path);
					out.write(data2);
					System.out.println("接收到了来自["+socket.getInetAddress().getHostAddress()+"]的上传文件["+path+"]");
				} catch (IOException e) {
					e.printStackTrace();
				} finally {
					//6.关闭资源
					if(out!=null){
						try {
							out.close();
						} catch (IOException e) {
							e.printStackTrace();
						} finally {
							out = null;
						}
					}
					if(socket != null){
						try {
							socket.close();
						} catch (IOException e) {
							e.printStackTrace();
						} finally {
							socket = null;
						}
					}
				}
			}
		}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值