【了解Java网络编程】实现TCP网络通信

活动地址:CSDN21天学习挑战赛

目录

一、网络编程

1.1 网络通信结构

1.2 网络通信协议

1.3 协议分类

 1.4 网络编程三要素

 二、TCP通信程序

2.1 概述

2.2 Socket 类

2.3 ServerSocket类

2.4 TCP通信流程

2.5 TCP通信示例

2.6 文件上传的优化分析


一、网络编程

1.1 网络通信结构

        1)C/S结构 :即 Client/Server 结构,是指客户端和服务器结构。常见有QQ、腾讯视频等软件。

        2)B/S结构 :即 Browser/Server 结构,是指浏览器和服务器结构。常见有谷歌、火狐浏览器等。

        总结:无论哪种架构,都要在联网状态下可运行。网络编程,就是在一定的协议下,实现两台计算机的通信的程序。

1.2 网络通信协议

        TCP/IP协议: 传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。

  •         定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。
  •         采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。

        TCP/IP四层概念模型如下: 

应用层 HTTP、FTP、TFTP、SMTP、DNS等
传输层 TCP、UDP
网络层 ICMP、IGMP、IP、ARP等
数据链路层 由底层网络定义的协议
物理层

         具体协议如下:

                HTTP: 超文本传输协议(Hyper Text Transfer Protocol)

                FTP:    文件传输协议(File Transfer Protocol)

                TFTP:  简单网络传输协议(Trivial File Transfer Protocol)

                SMTP:  简单电子邮件传输协议(Simple Mail Transfer Protocol)

                DNS:    域名系统(Domain Name System)

                TCP:    传输控制协议(Transmission Control Protocol)

                UDP:    用户数据报协议(User Datagram Protocol)

                ICMP:    Internet控制报文协议(Internet Control Message Protocol)

                IGMP:    Internet组管理协议(Internet Group Management Protocol)

                IP:        网络互连协议(Internet Protocol)

                ARP:    地址解析协议(Address Resolution Protocol)

1.3 协议分类

        java.net 包中提供了两种常见的网络协议的支持:

                1)TCP协议面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
                三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
                        第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
                        第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
                        第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。

                2)UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中,例如视频会议、QQ聊天等。

 1.4 网络编程三要素

  •         IP地址:可以唯一标识网络中的设备
  •         端口号:可以唯一标识设备中的进程(应用程序)
  •         协议:双方通信的规则,上述介绍的协议都属于网络通信协议

1)IP地址

                查看本机的IP地址:可在 命令行窗口 输入:

 ipconfig

                 检查网络是否连通,可在 命令行窗口 输入:

ping  IP地址

如:

 2)端口号

        不同的进程有不同的端口号,用以区分软件,规定端口号为用两个字节表示的整数,它的取值范围是0~65535

  •                 公共端口:0~1023,如:HTTP:80;

                                                                HTTPS:443;

                                                                FTP:21;用于一些知名的网络服务和应用。

  •                 程序注册端口:1024~49151,用以分配给用户和普通的应用程序。

                                                        如:Tomcat:8080;MySQL:3306;Oracle:1521。

  •                 动态及私有端口:49152~65535

                 查看本机的所有端口,在 命令行窗口 输入

netstat -ano

                 查看特定的端口(如:查看端口号为5900的端口)

netstat -ano | findstr "5900"

 二、TCP通信程序

2.1 概述

        1)两端通信时步骤
                ①服务端程序,需要事先启动,等待客户端的连接。
                ② 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
        2)在Java中,提供了两个类用于实现TCP通信程序:
                ①客户端: java.net.Socket 类表示。创建 Socket 对象,向服务端发出连接请求,服务端响应请求,两者建
立连接开始通信。
                ②服务端: java.net.ServerSocket 类表示。创建 ServerSocket 对象,相当于开启一个服务,并等待客户端
的连接。

2.2 Socket 类

        Socket 类:实现了客户端套接字,套接字指的是两台设备之间通讯的端点。

        构造方法public Socket(String host, int port) :创建套接字对象(地址为host)并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址(一旦使用回送地址发送数据,立即返回,不进行任何网络传输)。

        举例:

Socket client = new Socket("127.0.0.1", 6666);

        常用成员方法

                public InputStream getInputStream() : 返回此套接字的输入流

                public OutputStream getOutputStream() : 返回此套接字的输出流。

                public void close() :关闭此套接字

                public void shutdownOutput() : 禁用此套接字的输出流。

2.3 ServerSocket类

        ServerSocket 类:实现了服务器套接字,该对象等待通过网络的请求。

        构造方法public ServerSocket(int port) :在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上。

        举例:

ServerSocket server = new ServerSocket(6666);

        常用成员方法

                public Socket accept() :侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法
会一直阻塞直到建立连接。

2.4 TCP通信流程

        1. 【服务端】启动,创建ServerSocket对象,等待连接。
        2. 【客户端】启动,创建Socket对象,请求连接。
        3. 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
        4. 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
        5. 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。
        到此,客户端向服务端发送数据成功。

        自此,服务端向客户端回写数据。
        6. 【服务端】Socket对象,获取OutputStream,向客户端回写数据。
        7. 【客户端】Scoket对象,获取InputStream,解析回写数据。
        8. 【客户端】释放资源,断开连接。

2.5 TCP通信示例

【举例1】客户端向服务器发送数据    “欢迎学习Java”

客户端的实现:TCPClientDemo1.java

package Net;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
//实现TCP发生消息

//客户端
public class TCPClientDemo1 {

	public static void main(String[] args) {
		
		Socket socket = null;
		OutputStream os = null;
		try {
			
			// 1.要知道服务器的地址,端口号
			InetAddress serverIP = InetAddress.getByName("127.0.0.1");
			int port = 999;
			
			//2.创建一个socket连接,确定连接到端口
			socket = new Socket(serverIP,port);
			
			//3.发送消息IO流
			os = socket.getOutputStream();
			
            //写数据
			os.write("欢迎学习Java".getBytes());
			
		} catch (Exception e) {
			
			e.printStackTrace();
		}finally {
			if(os != null) {
				try {
                    //关闭资源
					os.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

	}

}

 服务端的实现:TCPServerDemo1.java

package Net;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

//服务端
public class TCPServerDemo1 {

	public static void main(String[] args) {
		
		ServerSocket serverSocket = null;
		Socket socket =null;
		InputStream is = null;
		ByteArrayOutputStream baos = null;

		try {
			// 1.要有一个服务器地址(这里设置为localhost/9999)
            //创建ServerSocket对象,绑定端口,开始等待连接
			serverSocket = new ServerSocket(999);
			//2.accept方法 等待客户端连接过来,返回socket对象
			socket = serverSocket.accept();
			//3.读取客户端的消息(输入流)
			is = socket.getInputStream();
			
			
			//管道流读取数据
			baos = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];   //创建字节数组
			int len;
			while((len=is.read(buffer)) != -1) {
				baos.write(buffer,0,len);//打印数据流信息
			}
			
			System.out.println(baos.toByteArray());

			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			//关闭资源(先定义的对象后关闭)
			if(baos != null) {
				try {
					baos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if(is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
			if(socket != null) {
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
			if(serverSocket != null) {
				try {
					serverSocket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

	}

}

【举例2】客户端向服务器传输文件(以图片为例)

文件上传的流程:

        1. 【客户端】输入流,从硬盘读取文件数据到程序中。
        2. 【客户端】输出流,写出文件数据到服务端。
        3. 【服务端】输入流,读取文件数据到服务端程序。
        4. 【服务端】输出流,写出文件数据到服务器硬盘中。

客户端的实现:TCPClientDemo2.java

package Net;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
//实现TCP发送文件

public class TCPClientDemo2 {

	public static void main(String[] args) throws Exception {
		// 1.创建一个Socket连接
		Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),888);
		//2.创建一个输出流
		OutputStream os = socket.getOutputStream();
		//3.文件流:读取文件
        //注意!!!根据自己存储图片的文件路径作为输入!!!

		FileInputStream fis = new FileInputStream(new File("C:\\Users\\eclipse-workspace\\QQ图片.png"));

		//4.写出文件
		byte[] buffer = new byte[1024];
		int len;
		while((len=fis.read(buffer)) != -1) {
			os.write(buffer,0,len);
		}
		//通知服务器,我已经结束了
		socket.shutdownOutput();//我已经传输完了
		
		//确定服务器接收完毕,才能断开连接
		InputStream inputStream = socket.getInputStream();
		//String byte[]
		ByteArrayOutputStream baos = new ByteArrayOutputStream();

		byte[] buffer2 = new byte[2014];
		int len2;
		while((len2=fis.read(buffer2)) != -1) {
			baos.write(buffer2,0,len2);
		}
		
		//5.关闭资源
		baos.close();
		inputStream.close();
		fis.close();
		os.close();
		socket.close();
	}

}

  服务端的实现:TCPServerDemo2.java

package Net;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServerDemo2 {

	public static void main(String[] args) throws Exception {
		// 1.创建服务端ServerSocket
		ServerSocket serverSocket = new ServerSocket(888);
		//2.监听客户端的连接
		Socket socket = serverSocket.accept();  //阻塞式监听,会一直等待客户端连接
		//3.获取输入流
		InputStream is = socket.getInputStream();
		
		//4.文件输出
		FileOutputStream fos = new FileOutputStream(new File("receive.png"));//保存在本项目工作空间
		
        //读写数据
		byte[] buffer = new byte[1024];
		int len;
		while((len=is.read(buffer)) != -1) {
			fos.write(buffer,0,len);
		}
		
		//通知客户端,接收完毕
		OutputStream os = socket.getOutputStream();
		os.write("我接受完毕了,你可以断开了".getBytes());
		
		//关闭资源
		fos.close();
		is.close();
		socket.close();
		serverSocket.close();
	}

}

        运行后在工作路径下保存了“receive.png”

2.6 文件上传的优化分析

1)文件名称写死的问题

        在服务端,保存文件的名称如果写死,那么最终导致服务器硬盘,只会保留一个文件,使用系统时间优
化,保证文件名称唯一
,代码如下:

//4.文件输出
FileOutputStream fos = new FileOutputStream(new File("receive"+System.currentTimeMillis()+".png"));

2) 循环接收的问题

        在服务端,保存一个文件就关闭了,之后的用户无法再上传,这是不符合实际的,使用循环改进,可以不断
的接收不同用户的文件,代码如下:

// 每次接收新的连接,创建一个Socket
while(true){
    Socket accept = serverSocket.accept();
    ......
}

3)效率问题

        在服务端,接收大文件时,可能耗费几秒钟的时间,此时不能接收其他用户上传,所以,使用多线程技术
化,代码如下:

while(true){
    Socket accept = serverSocket.accept();
    // accept 交给子线程处理.
    new Thread(() ‐> {
       ......  
        InputStream bis = accept.getInputStream();
       ......  
    }).start();
}

        综上,优化后的服务端的实现:

package Net;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServerDemo2_better {

	public static void main(String[] args) throws Exception {
		// 1.创建服务
		ServerSocket serverSocket = new ServerSocket(888);
		//2.循环监听客户端的连接
		while(true) {
			Socket socket = serverSocket.accept();  //阻塞式监听,会一直等待客户端连接
			
			//socket对象交给子线程处理,进行读写操作 Runnable接口中,只有一个run方法,使用lambda表达式简化格式
			new Thread(()-> {
				try {

					//3.获取输入流
					InputStream is = socket.getInputStream();
					
					//4.文件输出
					FileOutputStream fos = new FileOutputStream(new File("receive"+System.currentTimeMillis()+".png"));
					
					byte[] buffer = new byte[1024];
					int len;
					while((len=is.read(buffer)) != -1) {
						fos.write(buffer,0,len);
					}
					
					//通知客户端,接收完毕
					OutputStream os = socket.getOutputStream();
					os.write("我接受完毕了,你可以断开了".getBytes());
					
					//关闭资源
					fos.close();
					is.close();
					socket.close();
					serverSocket.close();
					
				}catch(IOException e) {
					e.printStackTrace();
				}
			}).start();

		}
	}
}

猜你喜欢

转载自blog.csdn.net/m0_46427461/article/details/126423347