1. 网络编程基础
什么叫网络编程?
直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯,这就叫网络编程
但在交互过程中,会有以下两个问题:
- 如何准确地定位网络上一台或多台主机:定位主机上的特定的应用(IP + 端口)
- 找到主机后,如何可靠、高效地进行数据传输(网络通信协议)
当然,以上两个问题都已经有了相应的解决方案了。
IP
IP:唯一的标识 Internet 上的计算机
端口
端口被规定为一个16位的整数:0~65535。在同一主机中,不同的进程有不同的端口。
端口被分为:
- 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP 占用 80 端口、FTP 占用 21 端口)
- 注册端口:1024~49151。分配给用户进程或应用程序(如:Tomcat 占用 8080 端口、Mysql 占用 3306 端口)
- 动态/私有端口:49152~65535
所以,Socket = IP + 端口
网络通信协议
网络协议太复杂,所以,将通信协议进行分层。由上至下为:应用层、传输层(TCP、UDP)、网络层(IP)、数据链路层、物理层
TPC 特点:
- 使用 TCP 协议前,必须先建立 TCP 连接,形成传输数据通道
- 传输前,采用“三次握手”方式,点对点通信,是可靠传输
- TCP 协议进行通信的两个应用进程:客户端、服务端
- 在连接中,可进行大数据量的传输
- 传输完毕,需要释放已建立的连接(效率低)
UDP 特点:
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据包的大小限制在 64k 内
- 发送不管对方是否准备好,接收方收到也不确认,不可靠传输
- 可以广播发送
- 发送数据结束时,无需释放资源(开销小、速度快)
2. 网络编程示例
2.1 TCP 协议
场景一:客户端通过 TCP 协议向服务端发送数据,服务端接收后,将收到的数据打印到控制台中,并指明来自于哪个客户端
public class TCPServer {
public static void main(String[] args) throws Exception{
// 指明需要监听的端口
ServerSocket ss = new ServerSocket(1234);
// 监听来自客户端的 Socket 请求
Socket socket = ss.accept();
// 获取 Socket 对象的输入流
InputStream is = socket.getInputStream();
// 如果客户端传来的是汉字(三个字节编码),使用字节流读取可能会存在乱码
/*byte[] bytes = new byte[1024];
int len = 0;
while ((len = is.read(bytes)) != -1) {
// 当把一个汉字劈成两半,再还原为字符串时就会乱码
System.out.print(new String(bytes, 0 ,len));
}*/
// 创建一个字节数据输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len = 0;
// 读取输入流中的数据
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0 ,len);
}
System.out.println(baos.toString());
System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据");
baos.close();
is.close();
socket.close();
ss.close();
}
}
public class TCPClient {
public static void main(String[] args) throws Exception{
// 创建 Socket 对象,指明服务器的 ip 和端口
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 1234);
// 通过 Socket 对象获取它的输出流
OutputStream os = socket.getOutputStream();
String data = "您好,我是客户端 GG";
// 向服务器发送数据
os.write(data.getBytes());
os.close();
socket.close();
}
}
场景二:客户端将文件发送给服务端,服务端将文件保存起来
public class TCPServer2 {
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(1234);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream(new File("2.jpg"));
byte[] bytes = new byte[1024];
int len = 0;
while ((len = is.read(bytes)) != -1) {
fos.write(bytes, 0 ,len);
}
fos.close();
is.close();
socket.close();
ss.close();
}
}
public class TCPClient2 {
public static void main(String[] args) throws Exception{
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 1234);
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream(new File("1.jpg"));
byte[] bytes = new byte[1024];
int len = 0;
while ((len = fis.read(bytes)) != -1) {
os.write(bytes, 0, len);
}
fis.close();
os.close();
socket.close();
}
}
场景三:客户端将文件发送给服务端,服务端将文件保存起来。并且,服务端需要返回“发送成功”给客户端,然后关闭相应连接
public class TCPServer3 {
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(1234);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream(new File("2.jpg"));
byte[] bytes = new byte[1024];
int len = 0;
while ((len = is.read(bytes)) != -1) {
fos.write(bytes, 0 ,len);
}
// 服务端给予客户端反馈
OutputStream os = socket.getOutputStream();
String data = "发送成功";
os.write(data.getBytes());
fos.close();
is.close();
os.close();
socket.close();
ss.close();
}
}
public class TCPClient3 {
public static void main(String[] args) throws Exception{
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 1234);
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream(new File("1.jpg"));
byte[] bytes = new byte[1024];
int len = 0;
// read() 方法是一个阻塞方法,没有明确指出传输完毕,就会一直阻塞
while ((len = fis.read(bytes)) != -1) {
os.write(bytes, 0, len);
}
// 给出明确指示(传输完毕)
socket.shutdownOutput();
// 客户端接收服务端传送过来的数据
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[20];
int length = 0;
while ((length = is.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
System.out.println(baos.toString());
fis.close();
os.close();
baos.close();
socket.close();
}
}
2.2 UDP 协议
public class UDPReceiver {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(1234);
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(), 0, packet.getLength()));
socket.close();
}
}
public class UDPSender {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket();
// 封装一个数据包(所有信息都在数据包中)
String msg = "我是 UDP 发送方式的数据";
byte[] data = msg.getBytes();
DatagramPacket packet = new DatagramPacket(data, 0, data.length, InetAddress.getByName("127.0.0.1"), 1234);
socket.send(packet);
socket.close();
}
}