java学习中级篇之网络编程

TCP/IP网络模型
网络通信协议有很多种,目前应用最广泛的是TCP/IP协议(Transmission Control Protocal/Internet Protoal传输控制协议/英特网互联协议),它是一个包括TCP协议和IP协议,UDP(User Datagram Protocol)协议和其它一些协议的协议组,在学习具体协议之前首先了解一下TCP/IP协议组的层次结构。在这里插入图片描述
链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。

IP地址和端口号
要想使网络中的计算机能够进行通信,必须为每台计算机指定一个标识号,通过这个标识号来指定接受数据的计算机或者发送数据的计算机。
TCP/IP协议中,这个标识号就是IP地址,它可以唯一标识一台计算机,目前,IP地址广泛使用的版本是IPv4,它是由4个字节大小的二进制数来表示,如:00001010000000000000000000000001。由于二进制形式表示的IP地址非常不便记忆和处理,因此通常会将IP地址写成十进制的形式,每个字节用一个十进制数字(0-255)表示,数字间用符号“.”分开,如 “192.168.1.100”。

随着计算机网络规模的不断扩大,对IP地址的需求也越来越多,IPV4这种用4个字节表示的IP地址面临枯竭,因此IPv6 便应运而生了,IPv6使用16个字节表示IP地址,它所拥有的地址容量约是IPv4的8×1028倍,达到2128个(算上全零的),这样就解决了网络地址资源数量不够的问题。

本机的回环ip,相当于汉语中的"我"
127.0.0.1这个ip就表示当前主机的ip

端口号的作用:
通过IP地址可以连接到指定计算机,但如果想访问目标计算机中的某个应用程序,还需要指定端口号。
端口号是操作系统自动分配的,是逻辑端口
在计算机中,不同的应用程序是通过端口号区分的。端口号是用两个字节(16位的二进制数)表示的,它的取值范围是065535**,其中,**01023之间的端口号用于一些知名的网络服务和应用,用户的普通应用程序需要使用1024以上的端口号,从而避免端口号被另外一个应用或服务所占用。

冒号后的5000代表端口号

在这里插入图片描述
端口号和IP地址的作用示意图
注:每个端口号只能代表一个应用程序
在这里插入图片描述

当我们用http://www.baidu.com来访问的时候,这个网址也相当于一个IP地址,其实网址后跟着一个默认端口号80
所以完整网址因该是http://www.baidu.com:80
通俗来讲,一个网址也相当于一个主机名

使用java编码获取本机ip地址
使用java.net.InetAddress该类的getLocalHost 静态方法获取本地主机的ip地址

public class InetAdress {
	public static void main(String[] args) throws UnknownHostException {
		function();
	}
	public static void function() throws UnknownHostException
	{
		InetAddress inet = InetAddress.getLocalHost();
		System.out.println(inet);
	}
}

使用splite将字符串分割,获得分别的主机名和ip地址

public class InetAdress {
	public static void main(String[] args) throws UnknownHostException {
		function();
	}
	public static void function() throws UnknownHostException
	{
		InetAddress inet = InetAddress.getLocalHost();
		String host = inet.toString();
		String[] splite = host.split("/");
		for(String i :splite)
		{
			System.out.println(i);
		}
	}
}

这个分割主机和ip地址的活,sun公司已经帮你做好了
非静态方法
getHostAddress()
getHostName()

public class InetAdress {
	public static void main(String[] args) throws UnknownHostException {
		function();
	}
	public static void function() throws UnknownHostException
	{
		InetAddress inet = InetAddress.getLocalHost();
		System.out.println(inet.getHostAddress());
		System.out.println(inet.getHostName());
	}
}

总结一下:以下四种方法
1:可以通过别人的主机名来获得InetAdress类对象
2:获取本机的InetAdress类对象
3:根据InetAdress类对象获取主机名
4:根据InetAdress类对象获取主机IP地址
在这里插入图片描述
运输层的两个重要协议: TCP UDP

UDP协议

UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。

TCP协议
TCP协议是面向连接的通信协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”第一次握手,客户端向服务器端发出连接请求,等待服务器确认,第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求,第三次握手,客户端再次向服务器端发送确认信息,确认连接。

UDP通信代码实现

两个重要的类
DatagramPacket()和DatagramSocket()
DatagramPacket()负责打包数据
DatagramSocket()负责运输数据

发送方

  • 实现步骤:
  • 创建DatagramPacket(),封装数据,IP地址和端口号
  • 创建DatagramSocket()对象
  • 调用DatagramSocket()对象的send方法,发送封装后的数据
  • 调用DatagramSocket()对象可以发送也可以接收
  • 关闭资源
public class udpsend {
	public static void main(String[] args) throws IOException {
		//创建DatagramPacket()对象,封装数据,IP地址和端口号
		byte[] data = "你好UDP".getBytes();
		//创建InetAddress对象,封装自己的IP地址
		//但是封装的IP地址是目标地址
		InetAddress inet = InetAddress.getByName("10.7.63.160");
		//最后一个参数是端口号 ,6000端口
		DatagramPacket np = new DatagramPacket(data, data.length, inet,6000);
		//套接字socket定义:绑定了Ip地址和端口号的网络对象
		DatagramSocket ds = new DatagramSocket();
		ds.send(np);
		//关闭资源
		ds.close();
	}
}

接收方
/*
1。创建DatagramSocket(),绑定端口号,与发送端口保持一致
2。创建字节数组,接收发来的消息
3。创建DatagramPacket()对象
4。调用DatagramSocket()的receive()
receive(DatagramPacket dp)
作用是将接收的字节数组的数据放在dp包中
5。拆包
得到
发送方的IP地址
DatagramPacket对象中的方法getAddress()
返回值是InetAddress类的对象
接收到的字节个数
DatagramPacket对象中的方法getLength()
发送方的端口号
DatagramPacket对象中的方法.getPort()
该端口号一般是操作系统自动指派的,一般没什么意义
6。关闭资源

*/

public class udpreceive {
	public static void main(String[] args) throws IOException {
		//创建数据包传输对象ds,绑定端口号
		DatagramSocket ds = new DatagramSocket(6000);
		//创建字节数组
		byte[] data = new byte[1024];
		//创建数据包对象,传递字节数组
		DatagramPacket dp = new DatagramPacket(data, data.length);
		//调用ds对象的receive方法来接收数据
		ds.receive(dp);
		int port = dp.getPort();
		InetAddress address = dp.getAddress();
		String ip_address  = address.getHostAddress();
		int length = dp.getLength();
		//通过String类将data对象字符串化
		System.out.println(new String(data,0,length)+"ip地址"+ip_address+"端口号"+port);
		ds.close();
	}
}

将其改为聊天小程序

先打开接收端,再打开发送端
在这里插入图片描述
最后一个小三角图标是选择控制台的

发送端

public class udpreceive {
	public static void main(String[] args) throws IOException {
		DatagramSocket ds = new DatagramSocket(6000);
		byte[] data = new byte[1024];
		//不将数组放入循环,不然太浪费了
		//我之前想会不会数组的数据没有清除,导致输出错误
		//下面的new String(data,0,length),会输出指定长度的字符串
		while(true)
		{
			DatagramPacket dp = new DatagramPacket(data, data.length);
			ds.receive(dp);
			int length = dp.getLength();
			System.out.println(new String(data,0,length));
		}
	}
}

接收端

public class udpreceive {
	public static void main(String[] args) throws IOException {
		DatagramSocket ds = new DatagramSocket(6000);
		byte[] data = new byte[1024];
		while(true)
		{
			DatagramPacket dp = new DatagramPacket(data, data.length);
			ds.receive(dp);
			int length = dp.getLength();
			System.out.println(new String(data,0,length));
		}
	}
}

TCP通信

  • TCP通信同UDP通信一样,都能实现两台计算机之间的通信,通信的两端都需要创建socket对象
  • 区别在于,UDP中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据。
  • TCP通信是严格区分客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信服务器端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户端的连接。
  • 在JDK中提供了两个类用于实现TCP程序,一个是ServerSocket类,用于表示服务器端,一个是Socket类,用于表示客户端
  • 通信时,首先创建代表服务器端的ServerSocket对象,该对象相当于开启一个服务,并等待客户端的连接,然后创建代表客户端的Socket对象向服务器端发出连接请求,服务器端响应请求,两者建立连接开始通信。
    在这里插入图片描述
  • 客户端和服务器交换一次数据需要四次IO流,大体过程是客户端发送abc,服务器接收abc,服务器发送"你好",客户端接收"你好"。

在这里插入图片描述

客户端程序:

  • 实现tcp客户端 使用java.net.Socket类

  • 构造方法:
    Socket(String host, int port)
    传递服务器的IP和端口号
    构造方法只要运行,就开始连接服务器,若连接失败,则会抛出异常
    和服务器实现数据交换

  • OutputStream getOutputStream() 返回套接字的输出流
    作用:将数据流输出,输出到服务器

  • InputStream getInputStream() 返回套接字的输入流
    作用:从服务器读取数据

  • 这两个流对象是客户端和服务器在建立链接时就已经自动生成的
    我们只需在建立的socket对象中得到这输入输出流对象即可,只能用这两个对象进行客户机和服务器之间的交换

public class tcpclient {
	public static void main(String[] args) throws IOException{
		// 创建socket对象,连接服务器
		Socket so = new Socket("10.7.63.160", 8888);
		//通过socket对象获取输出流
		OutputStream outs = so.getOutputStream();
		outs.write("服务器".getBytes());
		
		//读取服务器发回的数字,使用socket套接字中的对象输入流
		InputStream in = so.getInputStream();
		//新建字节数组
		byte[] data = new byte[1024];
		int length = in.read(data);
		System.out.println(new String(data,0,length));
		so.close();
	}
}

服务器端程序

  • 实现tcp服务器

  • 表示服务器的类java.net.ServerSocket

  • 构造方法:
    ServerSocket(int port)

  • 必须要获得客户端的套接字对象socket,这样才能知道自己和哪个客户端连接了
    Socket accept()

  • 服务器端本身没有流功能,他的功能要靠客户端的socket来获取

public class tcpserver {
	public static void main(String[] args) throws IOException {
		ServerSocket ser = new ServerSocket(8888);
		//调用服务器套接字对象中的accept(),方法获得客户端套接字对象
		Socket socket = ser.accept();
		//靠客户端的套接字对象获取字节输出流,读的是客户端发来的数据
		InputStream in = socket.getInputStream();
		//将输入流获取的数据存放在获取的数组中
		byte[] data = new byte[1024];
		// int read()方法返回读入缓冲区的总字节数
		int len = in.read(data);
		System.out.println(new String(data,0,len));
		
		OutputStream out = socket.getOutputStream();
		out.write("收到,谢谢".getBytes());
		
		socket.close();
		ser.close();
	}
}

文件上传案例

  • 从客户端上传一个图片,上传到服务器中

  • 服务器将图片保存到一个文件夹中

  • 客户端收到服务器的上传成功反馈
    在这里插入图片描述
    客户端代码

  • 实现tcp图片上传服务器

  • 实现步骤:

     	1:使用socket套接字链接服务器
     	2:使用socket获取字节输出流,写图片
     	3:使用自己的流对象,获取图片数据源
     		FileInputStream
     	4: 读取图片,使用字节输出流,将图片写到服务器,采用字节数组进行缓冲
     	5:使用socket套接字获取字节输入流,读取服务器返回的上传成功信息
     	6:关闭资源
    
  • 重大发现:
    文件输入流(FileInputStream)和文件输出流(FileOutputStream)的输入和输出是相对于缓冲数组
    文件输入流(FileInputStream)向缓冲数组中写入
    文件输出流(FileOutputStream)从缓冲数组中读出
    输入,就像相当于来了,来了就得读
    输出,就相当于走了,走了就得写

public class tcpclient {
	public static void main(String[] args) throws IOException {
		Socket socket = new Socket("10.7.63.160",8000);
		OutputStream out = socket.getOutputStream();
		String filename = "F:\\照片\\test.jpg";
		FileInputStream fis  = new FileInputStream(filename);
		int length = 0;
		byte[] pic = new byte[1024];
		while((length = fis.read(pic))!=-1)
		{
			out.write(pic, 0, length);
		}
		InputStream in = socket.getInputStream();
		in.read(pic);
		System.out.println(new String(pic,0,pic.length));
		fis.close();
		socket.close();
	}
}

服务器端代码

  • tcp图片上传服务器

     1:serversocket套接字对象,端口8000
     2:accept对象获取客户端的连接对象
     3:socket对象获得字节输入流
     4:  客户端连接对象获取字节输入流,读取客户端发送的文件
     5:创建file对象,绑定上传文件夹
     	判断文件夹是否存在,不存在,则创建文件夹
     6:创建字节输出流,数据目的是file对象所在文件夹
     7:字节流读取图片,写入文件夹中
     8:给客户端发送成功消息
     9:关闭资源
    
public class tcpserver {
	public static void main(String[] args) throws IOException {
		ServerSocket ser = new ServerSocket(8000);
		Socket socket = ser.accept();
		InputStream in = socket.getInputStream();
		String file = "E:\\迅雷下载";
		File upload = new File(file);
		if(!upload.exists())
		{
			upload.mkdirs();
		}
		FileOutputStream fos = new FileOutputStream(upload+"\\1.jpg");
		byte[] data = new byte[1024];
		int length = 0;
		while((length = in.read(data))!=-1)
		{
			fos.write(data,0,length);
		}
		OutputStream out = socket.getOutputStream();
		data = "图片已经上传完毕".getBytes();
		out.write(data,0,data.length);
		fos.close();
		socket.close();
		ser.close();
	}
}

但是运行后有缺陷
现象是客户端和服务器均不停止运行,且客户端没有显示上传成功字样

原因:
客户端代码段

while((length = fis.read(pic))!=-1)
		{
			out.write(pic, 0, length);
		}

服务器代码段

while((length = in.read(data))!=-1)
		{
			fos.write(data,0,length);
		}

客户端的fis.read(pic)读的是文件,可以遇到fis.read(pic)==-1的时候
服务器的in.read(data))读的是服务器,永远也读不到-1,所以循环不会结束,还在等
可是客户端数据已经发完了,但是服务器还在等

解决方法
Socket类中的shutdownOutput()方法

public class tcpclient {
	public static void main(String[] args) throws IOException {
		Socket socket = new Socket("10.7.63.160",8000);
		OutputStream out = socket.getOutputStream();
		String filename = "F:\\照片\\test.jpg";
		FileInputStream fis  = new FileInputStream(filename);
		int length = 0;
		byte[] pic = new byte[1024];
		while((length = fis.read(pic))!=-1)
		{
			out.write(pic, 0, length);
		}
		//给服务器发送终止序列,也就是告诉服务器,我数据发完了,你别等了
		socket.shutdownOutput();
		InputStream in = socket.getInputStream();
		int len = in.read(pic);
		System.out.println(new String(pic,0,len));
		fis.close();
		socket.close();
	}
}

为了解决同名文件被覆盖问题

public class tcpserver {
	public static void main(String[] args) throws IOException {
		ServerSocket ser = new ServerSocket(8000);
		Socket socket = ser.accept();
		InputStream in = socket.getInputStream();
		String file = "E:\\下载";
		File upload = new File(file);
		if(!upload.exists())
		{
			upload.mkdirs();
		}
		String filename = "sun"+System.currentTimeMillis()+new Random().nextInt(999)+".jpg";
		FileOutputStream fos = new FileOutputStream(upload+File.separator+filename);
		byte[] data = new byte[1024];
		int length = 0;
		while((length = in.read(data))!=-1)
		{
			fos.write(data,0,length);
		}
		OutputStream out = socket.getOutputStream();
		out.write("图片已经上传完毕".getBytes());
		fos.close();
		socket.close();
		ser.close();
	}
}
  • System.currentTimeMillis()获取时间原点到现在的毫秒数
  • new Random().nextInt(999)获取[0,999)之间的随机数
  • File.separator 代替文件路径中的分号
  • 与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符,即 separatorChar
  • 在 Microsoft Windows 系统上,它为 ‘\’
String filename = "sun"+System.currentTimeMillis()+new Random().nextInt(999)+".jpg";
		FileOutputStream fos = new FileOutputStream(upload+File.separator+filename);

实现多个客户端同时上传图片
用到了多线程的思想
就是有一个客户端向服务器上传数据,服务器就开一个线程,将上传的图片保存。
所以要更改服务器端的代码
服务器线程代码

public class server {

	public static void main(String[] args) throws IOException {
		ServerSocket ser = new ServerSocket(8000);
		while(true)
		{
		Socket socket = ser.accept();
		//s是接口类实现对象
		serverthread s = new serverthread(socket);
		new Thread(s).start();
		}
	}
}

多线程服务器封装

public class serverthread implements Runnable{
	private Socket socket;
	public serverthread(Socket socket)
	{
		this.socket = socket;
	}
	@Override
	public void run() {
		try{
		InputStream in = socket.getInputStream();
		String file = "E:\\下载";
		File upload = new File(file);
		if(!upload.exists())
		{
			upload.mkdirs();
		}
		String filename = "sun"+System.currentTimeMillis()+new Random().nextInt(999)+".jpg";
		FileOutputStream fos = new FileOutputStream(upload+File.separator+filename);
		byte[] data = new byte[1024];
		int length = 0;
		while((length = in.read(data))!=-1)
		{
			fos.write(data,0,length);
		}
		OutputStream out = socket.getOutputStream();
		out.write("图片已经上传完毕".getBytes());
		fos.close();
		socket.close();
		}catch(Exception e) {e.printStackTrace();}
	}

}
public class tcpclient {
	public static void main(String[] args) throws IOException {
		Socket socket = new Socket("10.7.63.160",8000);
		OutputStream out = socket.getOutputStream();
		String filename = "F:\\照片\\test.jpg";
		FileInputStream fis  = new FileInputStream(filename);
		int length = 0;
		byte[] pic = new byte[1024];
		while((length = fis.read(pic))!=-1)
		{
			out.write(pic, 0, length);
		}
		//给服务器发送终止序列,也就是告诉服务器,我数据发完了,你别等了
		socket.shutdownOutput();
		InputStream in = socket.getInputStream();
		int len = in.read(pic);
		System.out.println(new String(pic,0,len));
		fis.close();
		socket.close();
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值