背景
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实现
服务端【接收】
- 创建ServerSocket对象,用通讯端口
- 监听ServerSocket对象,建立连接后,得到Socket对象
- 创建输入流对象读取通道内数据【字节流】
- 创建输出流对象,将通道内数据写入文件
- 传输完成后创建输出流对象,向客户端发送反馈
- 关闭流对象资源
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();
}
}
客户端【发送】
- 创建Socket对象,与服务器链接
- 创建输入流对象,从文件中读数据【字节流】
- 创建输出流对象,写入Socket通道
- 通知通道完成传输
- 接收服务端反馈
- 关闭流对象资源,关闭客户端
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内的多个服务器实现分布式,如果当服务器处于不同局域网时,应该也有分布式管理架构去管理服务器集群。
程序比较简单但是很多知识都忘了,多想想会觉得挺有意思的。总结就是,万物皆可分布式,这种思想在网络编程中还是很常见的。