Java基础——网络编程(一)

网络编程

  • 网络模型
    • OSI参考模型
    • TCP/IP参考模型
  • 网络通讯要素
    • IP地址
    • 端口号
    • 传输协议

在一个依赖网络运行的程序,比如说QQ聊天软件,
1,我要给张三发一条消息,我先要在网络上找到张三所用的这台主机,也就是找到他所在的IP地址。
2,数据要发送到对方的应用程序上,为了标识这些应用程序,
所以给这些网络应用程序的用数字进行了标识
为了方便称呼这个数字,叫做端口,这样的端口是逻辑端口
3,为了让我发过去的数据张三所在的机器能能够处理,所以我俩必须先定义一个通信规则,这个通讯规则称为协议
国际组织定义了通用协议TCP/IP

本地回环地址127.0.0.1
是任何一台计算机都有的自身的IP地址。

配置端口的数字范围是在0~65515
系统程序的端口一般为0~1024

记住几个端口:
web服务:80
Tomcat服务器:8080
MySQL:3306

这里写图片描述

网络通讯要素

  • IP地址:InetAddress
    • 网络中设备的标识
    • 不易记忆,可用主机名
    • 本地回环地址:127.0.0.1 主机名:localhost
  • 端口号
    • 用于标识进程的逻辑地址,不同进程的标识
    • 有效端口:065535,其中01024系统使用或保留端口
  • 传输协议
    • 通讯的规则
    • 常见协议:TCP,UDP

示例获取电脑主机的名称和地址

在java的net包中封装了IP地址的类的对象,InetAddress

//获取电脑的主机IP地址和名称
import java.net.*;
public class IPDemo {
	public static void main(String[] args) throws Exception
	{
		//获取本地主机的IP对象
		InetAddress i= InetAddress.getLocalHost();
		
		System.out.println(i.toString());

		//单独的获取本主机的IP地址
		System.out.println("Address = "+i.getHostAddress());
		//单独的获取本地主机的主机名称
		System.out.println("name = "+i.getHostName());
	}
}

运行结果:

这里写图片描述

以上是获取本地主机的主机名和IP地址,也可以获取指定主机的主机名和IP地址的


//获取指定主机IP地址和名称
import java.net.*;
public class IPDemo {
	public static void main(String[] args) throws Exception
	{
		//获取指定主机的IP对象
		InetAddress i= InetAddress.getByName("www.baidu.com");//这里获取的是百度的主机IP
		//单独的获取指定主机的IP地址
		System.out.println("Address = "+i.getHostAddress());
		//单独的获取指定主机的主机名称
		System.out.println("name = "+i.getHostName());
	}
}

运行结果为:

这里写图片描述

TCP/UDP

  • UDP
    • 将数据及源和目的封装成数据包中,不需要建立连接
    • 每个数据包的大小限制在64k内
    • 因无连接,是不可靠协议
    • 不需要建立连接,速度快
  • TCP
    • 建立连接,形成传输数据的通道
    • 在连接中进行大数据量传输
    • 通过三次握手完成连接,是可靠协议
    • 必须建立连接,效率会稍低。

Socket

  • Socket
    • Socket就是为网络服务提供的一种机制
    • 通信的两端都有Socket
    • 网路通信其实就是Socket间的通信
    • 数据在两个Socket间通过IO传输

那么现在创建一个UDP传输的演示效果:

传输需要先建立服务,也就是Socket服务

//UDP发送端
import java.net.*;
/*
思路:
1,建立udpsocket服务。
2,提供数据,并将数据封装到数据包中
3,通过socket服务的发送功能,将数据包发出去
4,关闭资源
*/
public class UDPSend {
	public static void main(String[] args) throws Exception
	{
		//先建立一个数据传输服务,并且指定发送端的端口号
		DatagramSocket ds = new DatagramSocket(12340);

		//建立一个数据包,要传数据先将数据打包,,,将数据指定到一个数组,数组长度,要发送的地址,端口号
		byte [] buf ="接收端你好".getBytes();
		DatagramPacket dp = 
			new DatagramPacket(buf,buf.length,InetAddress.getByName("127.0.0.1"),10000);

		//使用传输服务将数据发送出去
		ds.send(dp);

		//关闭资源
		ds.close();
	}
}

//UDP传输接收端
import java.net.*;
/*
思路:
1,定义udpsocket服务,通常会监听一个端口,其实就是给这个接收网络应用程序定义数字标识
	方便于明确哪些数据过来该应用程序可以处理
2,定义一个数据包,因为要存储接收到的字节数据
	因为数据包对象中有更多功能可以提取字节数据中的不同数据信息

3,通过socket服务的receive方法将受到的数据存入已经定义好的数据包中
4,通过数据包对象的特有功能,将这些不同的数据取出,打印在控制台上
5,关闭资源
*/
public class UDPRec {
	public static void main(String[] args) throws Exception
	{
		//同样先创建一个数据传输服务
		DatagramSocket ds = new DatagramSocket(10000);

		//再定义一个数据包,用来接收数据时存放发过来的数据包,里面有操作数据包中数据的方法
		byte[] buf = new byte[1024];
		DatagramPacket dp = new DatagramPacket(buf,buf.length);

		//接收数据
		ds.receive(dp);
		
		//获取数据中的信息
		String Address = dp.getAddress().getHostAddress();
		int  port  = dp.getPort();
		String text = new String(dp.getData(),0,dp.getLength());
		
		//将数据打印出来
		System.out.println(Address+".."+text+".."+port);
		
		//关闭资源
		ds.close();
	}
}

运行结果:

这里写图片描述

因为UDP传输是面向无连接的,所以如果先运行发送端的话,数据就会丢失,
应该先打开接收端,没有数据收到的话,接收端会处于阻塞状态,一直到接收到数据。

练习二:使用键盘获取数据作为发送的数据

//UDP发送端2,使用键盘录入的方式发送数据
import java.net.*;
import java.io.*;
public class UDPSend2 {
	public static void main(String[] args) throws Exception
	{
		DatagramSocket ds = new DatagramSocket(12341);

		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
		String line = null;
		while((line = bufr.readLine())!=null)
		{
			//如果读到886就跳出循环,关闭资源
			if("886".equals(line))
				break;
			//读到的数据装进一个数组中
			byte[] buf = line.getBytes();
			//然后将数组中的数据装进包里
			DatagramPacket dp = 
				new DatagramPacket(buf,buf.length,InetAddress.getByName("127.0.0.1"),10001);
			ds.send(dp);
		}
		ds.close();
		bufr.close();
	}
}

//想要实现像对讲机一样,接收端常开的,发送端一发送数据,这里就能接收到
import java.net.*;
public class UDPRec2 {
	public static void main(String[] args) throws Exception
	{
		//创建好一个端口,常开着,只要过来数据,直接读就可以了,
		//不用将服务器再重新开启一编,所以这里讲服务器定义在外边
		DatagramSocket ds = new DatagramSocket(10001);
		while (true)
		{
			byte[] buf = new byte[1024];
			DatagramPacket dp = new DatagramPacket(buf,buf.length);

			ds.receive(dp);
		
		
			String Address = dp.getAddress().getHostAddress();
			int  port  = dp.getPort();
			String text = new String(dp.getData(),0,dp.getLength());
		
			System.out.println(Address+".."+text+".."+port);
		}
		
	}
}

运行结果:

这里写图片描述

练习:将发送端和接收端用两个线程来操作,也就是说在同一个窗口中实现发送和接收消息。

//想写一个聊天软件,在同一个窗口中同时实现发送数据和接收数据
//这就用到了多线程,一条线程负责发送,一条线程负责接收消息
//因为两个线程执行的指令不一样,所以要封装两个run方法
//因为read方法和键盘录入都是阻塞式方法,只要我不操作,两个线程都会在等待,
//当收到数据的时候,接收端就先输出数据
//当遇到键盘录入的时候,就输出我自己键盘录入的消息
import java.io.*;
import java.net.*;
class Send implements Runnable
{
	//因为发送数据需要用到服务端,所以这里直接给一个服务端
	private DatagramSocket ds;
	Send(DatagramSocket ds)
	{
		this.ds = ds;
	}
	public void run()
	{
		try
		{
			BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
			String line = null;
			while((line = bufr.readLine())!=null)
			{
				if("886".equals(line))
					break;
				byte[] buf = line.getBytes();
				DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getLocalHost(),10002);
				ds.send(dp);
			}
			bufr.close();
			ds.close();
		}
		catch (Exception e)
		{
			throw new RuntimeException("发送端异常");
		}
	}
}
class Rece implements Runnable
{
	private DatagramSocket ds;
	Rece(DatagramSocket ds)
	{
		this.ds = ds;
	}
	public void run()
	{
		try
		{
			while (true)
			{
				byte[] buf = new byte[1024];
				DatagramPacket dp = new DatagramPacket(buf,buf.length);
				ds.receive(dp);
				String ip = dp.getAddress().getHostAddress();
				String data = new String(dp.getData(),0,dp.getLength());
				System.out.println(ip+"..."+data);
			}
		}
		catch (Exception e)
		{
			throw new RuntimeException("接收端异常");
		}
	}
}
public class ChatDemo {
	public static void main(String[] args) throws Exception
	{
		DatagramSocket sendDs = new DatagramSocket();
		DatagramSocket receDs = new DatagramSocket(10002);
		new Thread(new Send(sendDs)).start();
		new Thread(new Rece(receDs)).start();
	}
}

运行结果:

这里写图片描述

TCP传输

  • Socket和ServerSocket
  • 建立客户端和服务器端
  • 建立连接后,通过Socket中的IO流进行数据的传输
  • 关闭Socket
  • 同样客户端与服务器端是两个独立的程序

TCP分客户端和服务端
客户端对应的对象是Socket
服务器端对应的对象是ServerSocket

使用TCP传输,从客户端写一段数据,然后由服务端读取到数据,然后打印在控制台上:

//TCP客户端
import java.io.*;
import java.net.*;
public class TCPClient {
	public static void main(String[] args) throws Exception
	{
		//先创建客户端对象,指定服务端地址和端口号
		Socket s = new Socket("127.0.0.1",10003);
		//从客户端对象中可以获取到对应的输出流
		OutputStream out = s.getOutputStream();

		//使用输出流往出写数据
		byte[] buf = "tcp wo lai le".getBytes();
		out.write(buf);
		s.close();
	}
}

//TCP 服务端

import java.io.*;
import java.net.*;
public class TCPServer {
	public static void main(String[] args) throws Exception
	{
		//创建服务端对象
		ServerSocket ss = new ServerSocket(10003);
		//在服务端中获取到客户端对象
		Socket s = ss.accept();
		//再从客户端对象中获取到读取流,这样可以保证服务端发给本客户端的消息不会发给其他的客户端
		InputStream in = s.getInputStream();
		//开始读取数据
		byte [] buf = new byte[1024];
		int len = in.read(buf);
		String data = new String(buf,0,len);
		System.out.println(data);
		//然后关闭资源
		ss.close();
		s.close();
	}
}

运行结果:

这里写图片描述

想要实现TCP传输之间的客户端和服务器端的互访。
客户端给服务器端发送消息之后,服务器端给一个回应,说我收到了:

//实现TCP传输的互访,也就是说客户端给服务端发送一个数据,服务端爷要给客户端发送一条数据
import java.io.*;
import java.net.*;
public class TCPClient2 {
	public static void main(String[] args) throws Exception
	{
		//服务端先写出去一段数据
		Socket s = new Socket("192.168.0.104",10004);
		System.out.println(s.getInetAddress().getHostAddress()+",,,,,conect");
		OutputStream out = s.getOutputStream();
		out.write("服务端,你好".getBytes());
		//写完之后再操作读取数据
		InputStream in = s.getInputStream();
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		System.out.println(new String(buf,0,len));
		//关闭资源
		s.close();
	}
}

//接收到信息之后,给客户端写出去一条消息
import java.io.*;
import java.net.*;
public class TCPServer2 {
	public static void main(String[] args) throws Exception
	{
		//先读取客户端发来的信息
		ServerSocket ss = new ServerSocket(10004);
		Socket s = ss.accept();
		InputStream in = s.getInputStream();
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		System.out.println(new String(buf,0,len));
		//读取完了之后开始写给客户端消息
		OutputStream out = s.getOutputStream();
		out.write("收到,你也好".getBytes());
		//可选操作,关闭资源
		ss.close();
		s.close();
	}
}

运行结果为:

这里写图片描述

那么接下来要设计一个客户端给服务端发送字符数据,然后由服务端换成该数据的大写,再发送给客户端,由客户端读取出来,然后打印在控制台上:

//设计一个客户端给服务端发送字符数据,然后由服务端换成该数据的大写,
//再发送给客户端,由客户端读取出来,然后打印在控制台上:

import java.io.*;
import java.net.*;
class TransClient {
	public static void main(String[] args) throws Exception
	{
		Socket s = new Socket("192.168.0.104",10005);
		//获取键盘录入数据的流
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

		//获取Socket输出流,用来给服务端写数据的
		BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

		//获取到Socket输入流,用来接收服务端的数据的
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		
		//开始读取数据
		String line = null;
		while((line = bufr.readLine())!=null)
		{
			if("over".equals(line))
				break;
			//将键盘读取到的数据,给服务器端发过去
			bufOut.write(line);//因为write方法是不带结束符号写入的,而没有结束符的话,
								//服务器端的readLine方法认为该行数据没有结束,所以一直在等,而不会读取出来数据
			bufOut.newLine();	//所以这里自己加一个回车符
			bufOut.flush();		//调用Buffered缓冲区的方法写数据是写在了缓冲区中,而没有写进流里边去
								//所以这里要记得刷新一下

			//每发送一次数据就接收回来一个数据,所以这里先来读取一下
			String str = bufIn.readLine();
			System.out.println("Server:...."+str);
		}
		s.close();
		bufr.close();

	}
}

class TransServer
{
	public static void main(String[] args)  throws Exception
	{
		ServerSocket ss = new ServerSocket(10005);
		Socket s = ss.accept();
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"......conect");
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		String line = null;
		while((line = bufIn.readLine())!=null)
		{
			//这里写数据和在客户端写数据的原理是一致的
			bufOut.write(line.toUpperCase());
			bufOut.newLine();
			bufOut.flush();
		}
		s.close();
		ss.close();
	}
}

运行结果:

这里写图片描述

运行时发现,当输入over让客户端退出的时候,服务器端也自动退出了:
这是因为,Socket中有一个数字,当关闭Socket的时候,会自动返回一个-1,而当服务器端收到这个-1 的时候,就会自动关闭了。

在写代码中还发现,每次使用Socket的方法写入数据的时候都会写入数据,然后加一个换行,然后刷新一次,由此想到了,PrintWriter,参数传true的话,使用println方法可以自动进行换行和刷新。

所以可以修改代码如下:

//设计一个客户端给服务端发送字符数据,然后由服务端换成该数据的大写,
//再发送给客户端,由客户端读取出来,然后打印在控制台上:

import java.io.*;
import java.net.*;
class TransClient {
	public static void main(String[] args) throws Exception
	{
		Socket s = new Socket("192.168.0.104",10005);
		//获取键盘录入数据的流
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

		//获取Socket输出流,用来给服务端写数据的
		PrintWriter bufOut = new PrintWriter(new OutputStreamWriter(s.getOutputStream()),true);

		//获取到Socket输入流,用来接收服务端的数据的
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		
		//开始读取数据
		String line = null;
		while((line = bufr.readLine())!=null)
		{
			if("over".equals(line))
				break;
			//将键盘读取到的数据,给服务器端发过去
			/*
			bufOut.write(line);//因为write方法是不带结束符号写入的,而没有结束符的话,
								//服务器端的readLine方法认为该行数据没有结束,所以一直在等,而不会读取出来数据
			bufOut.newLine();	//所以这里自己加一个回车符
			bufOut.flush();		//调用Buffered缓冲区的方法写数据是写在了缓冲区中,而没有写进流里边去
								//所以这里要记得刷新一下
			*/

			//换为下面这一句
			bufOut.println(line);

			//每发送一次数据就接收回来一个数据,所以这里先来读取一下
			String str = bufIn.readLine();
			System.out.println("Server:...."+str);
		}
		s.close();
		bufr.close();

	}
}

class TransServer
{
	public static void main(String[] args)  throws Exception
	{
		ServerSocket ss = new ServerSocket(10005);
		Socket s = ss.accept();
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"......conect");
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		PrintWriter bufOut = new PrintWriter(new OutputStreamWriter(s.getOutputStream()),true);
		String line = null;
		while((line = bufIn.readLine())!=null)
		{
			//这里写数据和在客户端写数据的原理是一致的
			/*
			bufOut.write(line.toUpperCase());
			bufOut.newLine();
			bufOut.flush();
			*/

			bufOut.println(line.toUpperCase());
		}
		s.close();
		ss.close();
	}
}

功能一样可以实现的。

练习:用TCP传输上传一个文件到客户端:

//想要从客户端上传一个文件给服务器端,
//上传成功后服务端给客户端发送一个数据,表示数据接收成功
import java.io.*;
import java.net.*;
class TextClient {
	public static void main(String[] args)  throws Exception
	{
		Socket s = new Socket("192.168.0.104",10006);
		//源选中一个目标文件
		BufferedReader bufr = new BufferedReader(new FileReader("IPDemo.java"));
		//从Socket流中获取到输入输出流
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		PrintWriter bufOut = new PrintWriter(s.getOutputStream(),true);
		//开始读取文件中的数据,将数据写入到输出流中,发送给服务器端
		String line = null;
		while((line = bufr.readLine())!=null)
		{
			bufOut.println(line);
		}
		//发送完之后 ,客户端等着接收服务器端发回来的消息并打印出来
		String str = bufIn.readLine();
		System.out.println(str);
		s.close();
		bufr.close();
	}
}

class TextServer
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss = new ServerSocket(10006);
		//只要一连接上就打印一次IP
		Socket s = ss.accept();
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"......conect");
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		PrintWriter bufOut = new  PrintWriter(s.getOutputStream(),true);
		PrintWriter bufw = new PrintWriter(new FileWriter("server.txt"),true);
		//开始读取流中发送回来的数据
		String line = null;
		while((line = bufIn.readLine())!=null)
		{
			bufw.println(line);
		}
		//读完之后服务器端给客户端发送一个消息,上传成功
		bufOut.println("上传成功");
		ss.close();
		s.close();
		bufw.close();
	}
}

运行结果发现,文件可以顺利上传,但是上传成功之后,服务端写的数据没有发送回来,而且两边都一直处在等待状态:

这是为什么呢?

因为客户端发送完数据之后就在等着服务器端发送数据,等着读取
而服务器端并不知道客户端发送完数据了,还在那里一直等着读取呢,
两边都在等,所以服务器端的数据就没有发回来,客户端也读取不到了。

解决方法是:在客户端写完数据之后,加上一个标记,告诉服务器端,我的数据写完了,
这个标记就是:shutdownOutput

然后只要在代码中加一句,s.shutdownOutput:意思是我写完了。

程序就可以正常结束了:

//想要从客户端上传一个文件给服务器端,
//上传成功后服务端给客户端发送一个数据,表示数据接收成功
import java.io.*;
import java.net.*;
class TextClient {
	public static void main(String[] args)  throws Exception
	{
		Socket s = new Socket("192.168.0.104",10006);
		//源选中一个目标文件
		BufferedReader bufr = new BufferedReader(new FileReader("IPDemo.java"));
		//从Socket流中获取到输入输出流
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		PrintWriter bufOut = new PrintWriter(s.getOutputStream(),true);
		//开始读取文件中的数据,将数据写入到输出流中,发送给服务器端
		String line = null;
		while((line = bufr.readLine())!=null)
		{
			bufOut.println(line);
		}
		s.shutdownOutput();
		//发送完之后 ,客户端等着接收服务器端发回来的消息并打印出来
		String str = bufIn.readLine();
		System.out.println(str);
		s.close();
		bufr.close();
	}
}

class TextServer
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss = new ServerSocket(10006);
		//只要一连接上就打印一次IP
		Socket s = ss.accept();
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"......conect");
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
		PrintWriter bufOut = new  PrintWriter(s.getOutputStream(),true);
		PrintWriter bufw = new PrintWriter(new FileWriter("server.txt"),true);
		//开始读取流中发送回来的数据
		String line = null;
		while((line = bufIn.readLine())!=null)
		{
			bufw.println(line);
		}
		//读完之后服务器端给客户端发送一个消息,上传成功
		bufOut.println("上传成功");
		ss.close();
		s.close();
		bufw.close();
	}
}

运行结果:

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值