【Java】socket实现电脑间传输文件

背景

MateBook Ubuntu18.04
MacBook MacOS HighSierra 10.13.6
两台电脑有的时候会需要文件传输,选择TCP协议下的Socket编程实现
印象中很简单且上学的时候实现过在实验室的两台机器间测试TCP和UDP的性能。现在实现一下,也来复习一下当时忽略的很多知识点

基础知识

网络通讯要素:协议,IP,端口

TCP协议

two types of switch operation for networks: circuit switching, packet switching

  • circuit swithing:固定链接,包括建立链路,传输数据,断开链接【电话系统】
  • packet switching:数据以包传输,通过路由管理链路【网络】

two types of service that networks can provide: connectionless and connection-oriented

  • connectionless:无连接服务,网络尽力传输每一个数据包,不保证丢失,按序,不可靠连接,发快递,UDP协议
  • connection-oriented:面向连接服务,发送和接收两端有通讯,接电话,TCP协议

协议选择TCP/IP可靠协议,多次握手建立连接,connection-oriented

IP Address

设备终端在网络中的唯一标识符

  • LAN,局域网,常用,typically under single admin domain。局域网之间Internet连接
  • 子网通过路由器相互连接,router。routing refers to selecting path from source to destination across multiple subnets
  • 四类IP地址, 192.168.1.1是C类保留IP,用于路由器设置。192.168.x.x通常是局域网,内网,每个接入的设备会分配一个内网IP
  • 内网之间传输用内网IP即可,所有的内网设备有一个共同的外网IP,是运营商分配的网关的IP。外网设备间通讯用外网IP。我这里基本两台电脑能保证在同一个局域网范围内,用内网IP就行
  • IP地址的分配不是固定的,很可能是动态分配的,因此在每次链接通讯的时候要确认当前两台计算机的IP地址

端口

程序间通过IP+PORT来通讯
use operating system calls to bind to port,系统调用开放端口
packets have source and destination ports,包在链接两端的端口间建立传输
两台终端分别可以使用不同的端口,双方确定就行
一个端口在运行时只能启用一种服务,否则端口被占用

Java实现

服务端【接收】

  1. 创建ServerSocket对象,用通讯端口
  2. 监听ServerSocket对象,建立连接后,得到Socket对象
  3. 创建输入流对象读取通道内数据【字节流】
  4. 创建输出流对象,将通道内数据写入文件
  5. 传输完成后创建输出流对象,向客户端发送反馈
  6. 关闭流对象资源
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

//This class serves as the Socket Sever accepting files from MacBook

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //create server socket instance, listening to local port 8888,specified for client-server connection
        ServerSocket ss = new ServerSocket(8888);
        //create socket instance from method accept, blocked method until binding occurs
        Socket s = ss.accept();

        //create byte input stream from socket
        BufferedInputStream bis = new BufferedInputStream(s.getInputStream());
        //create byte output stream to local destination, specify file name and directory for every transfer
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("/home/aries/Downloads/1.jpg"));

        //read data from socket stream and write to file stream
        byte[] bys = new byte[1024];
        int len = 0;
        while ((len = bis.read(bys)) != -1) {
            bos.write(bys, 0, len);
            bos.flush();
        }

        //notify for successful transfer
        OutputStream os = s.getOutputStream();
        os.write("MateBook已接收".getBytes());

        //resource release
        bos.close();
        //release server socket if necessary
        s.close();
    }
}

客户端【发送】

  1. 创建Socket对象,与服务器链接
  2. 创建输入流对象,从文件中读数据【字节流】
  3. 创建输出流对象,写入Socket通道
  4. 通知通道完成传输
  5. 接收服务端反馈
  6. 关闭流对象资源,关闭客户端
import java.io.*;
import java.net.Socket;

//This class serves as the Client socket sending files to MacBook
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //create client socket instance by hostIP and hostPort
        Socket s = new Socket("192.168.1.131", 8888);

        //create byte input stream instance by file input stream, specify the directory and file name each time sending to Mac
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("/home/aries/Documents/Test.txt"));
        //create byte output stream instance by client socket
        BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream());

        //read data from file stream and write to socket stream
        byte[] bys = new byte[1024];
        int len = 0;
        while ((len = bis.read(bys)) != -1) {
            bos.write(bys, 0, len);
            bos.flush();
        }

        //Disables the output stream for this socket
        s.shutdownOutput();

        //accept feedback from server
        InputStream is = s.getInputStream();
        byte[] feedback = new byte[1024];
        int length = is.read(feedback);
        String server_ack = new String(feedback, 0, length);
        System.out.println(server_ack);

        bis.close();
        s.close();
    }
}

思考和注意

服务端监听客户端,当客户端运行时建立链接,所以服务端应早于客户端开启。

假设服务器在公网,而内网有不同设备向公网发送消息。此时内网设备的外网IP相同,内网IP不同。与服务器间的连接由公网IP实现。此时对公网服务器来说,两台不同的物理设备传输数据和同一台没啥区别。但可以用多线程避免传输等待问题。

假设客户端在公网,而内网有不同的服务器设备接收消息。此时服务器的公网IP相同,内网IP不同。如果开启了一个服务器,我猜另一个服务器开启时会报错,我没有试过是什么错误,但很可能是端口占用错误,或服务器已开启等。因为和上面相同的思路,应该将两台内网服务器看做一个整体,当端口开启时,如果重复开启端口则会报错。可以给两台服务器开启不同端口,但这样又不符合实际的逻辑,因为对于客户端,它可能不知道服务器是多个。所以可以用反向代理管理同IP内的多个服务器实现分布式,如果当服务器处于不同局域网时,应该也有分布式管理架构去管理服务器集群。

程序比较简单但是很多知识都忘了,多想想会觉得挺有意思的。总结就是,万物皆可分布式,这种思想在网络编程中还是很常见的。

扫描二维码关注公众号,回复: 11017423 查看本文章
发布了9 篇原创文章 · 获赞 0 · 访问量 859

猜你喜欢

转载自blog.csdn.net/weixin_43138930/article/details/105491829