赞
踩
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; } } } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。