Java网络编程基础

1. 简单了解网络通信协议TCP/IP网络模型相关名词

应用层(HTTP,FTP,DNS等)
传输层(TCP,UDP)
网络层(IP,ICMP等)
链路层(驱动程序,接口等)
  1. 链路层:用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对网线、光纤提供的驱动。
  2. 网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或网络。
  3. 传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
  4. 应用层:主要负责应用程序的协议,如HTTP协议,或者FTP协议等。
  5. IP地址:在网络中,需要指定一台电脑的唯一标示号,用于网络中任意两台电脑之间的通信,通过这个IP号来确定另一台电脑,在TCP/IP协议中,这个标识号就是ip地址,常用的ip版本是IPV4,也就是4个字节长度的二进制数表示的,通常会写成十进制的形式,每个字节的二进制数转换为10进制数(范围是0-255),比如127.0.0.1。
  6. 端口号:通过ip地址可以确定网络中要进行通信的计算机,而通过端口,就可以确定在这台计算机上要与那个应用程序进行通信。端口号是用两个字节的二进制数表示,通常会写成十进制(范围是0-65535),自己编写的应用程序所占用的端口要使用1024之后的端口,因为小于1024的端口一般都被占用了。

2. Java中网络编程的编写

2.1 IP地址类——java.net.InetAddress

    1. 该类有两个直接子类 java.net.Inet4Address 和 java.net.Inet6Address 分别对应IPV4和IPV6,该类的对象无法通过构造器生成,只能通过静态方法来获取。

    2. 相关常用API如下:

  • public static InetAddress getByName(String host):通过一串IP地址字符串生成一个InetAddress对象
  • public static InetAddress getLocalHost():获取本机的InetAddress对象,里面包含了本机的ip地址和主机名
  • public static InetAddress getByAddress(byte[] addr):通过一串二进制码表示的ip地址来得到InetAddress
  • public String getHostAddress():获取当前InetAddress对象中的主机名+ip地址
  • public static InetAddress getByAddress(String host, byte[] addr):通过主机名+ip地址来获取对应的InetAddress对象
  • public static InetAddress getByName(String host):通过ip地址字符串来获取对应的InetAddress对象
  • public byte[] getAddress():获取当前InetAddress对象中IP地址的二进制字节码
import java.net.InetAddress;
import java.net.UnknownHostException;
public class IPAddress {
	public static void main(String[] args) {
		InetAddress inet;
		try {
			inet=InetAddress.getLocalHost();//获取本机的InetAddress对象
			System.out.println(inet.toString());//输出的就是主机名和ip地址
			String ip=inet.getHostAddress();//获取本机ip地址
			String name=inet.getHostName();//获取主机名
			System.out.println(ip+":"+name);

			//通过ip地址来获取对应的InetAddress对象
			byte[] addr={127,0,0,1};
			inet2=InetAddress.getByAddress(addr);
			System.out.println(inet2.toString());
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

2.2 TCP与UDP协议

    1. UDP协议:是无连接通信协议,即在数据传输时,发送端与接收端不建立逻辑连接。也就是说,发送方在发送数据时,不会确定是否有接收方存在或者是否接收到数据,而接收方也不会向发送方反馈是否接收到数据;依据这种特点,UDP协议一般在网络直播、社交软件(如QQ)等应用较多,其不保证数据完整性。另外,UDP协议限制传输数据大小为64kb以内,否则发生数据丢失时丢失的数据过多而造成不安全的问题。

    2. TCP协议:是面向连接的通信协议,即在传输数据前,发送端先要与接收到建立逻辑连接,然后再传输数据。TCP协议提供了发送端与接收端之间可靠无差错的数据传输。在TCP协议中,必须明确客户端与服务端,由客户端发出连接请求,每次连接的建立都需要进行“三次握手”。第一次握手,客户端向服务端发送连接请求;第二次握手,服务端向客户端发送一个响应,通知客户端已接收连接请求;第三次握手,客户端再次向服务端发送一个请求,表示确认连接信息,然后连接建立。由于其面向连接,所以可以保证数据传输的安全性,而且其支持大数据传输。

2.3 UDP通信的Java代码实现

    1. jdk中提供了两个类实现UDP协议网络程序:

  • java.net.DatagramPacket:数据打包类,用于将数据以UDP协议的格式打包,类似于“集装箱”的作用
  • java.net.DatagramSocket:数据发送和接受,以UDP协议的格式打包的数据将通过此类对象发送至接收端,或在接收端使用该对象接收数据,类似于“码头”的作用。

    2. 基本应用代码如下:实现一个UDP协议的发送端与接收端

/**
 * @ClassName:UDPServer
 * @Description:数据发送端
 * 先将数据打包在一个java.net.DatagramPacket对象中
 * 然后通过java.net.DatagramSocket对象发送至接收端
 */
public class UDPServer {
	
	public static void main(String[] args) {
		try {
			send();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	/**
	 * @throws UnknownHostException 
	 * @throws UnsupportedEncodingException 
	 * @throws SocketException 
	 * @Title:send
	 * @Description:发送数据
	 */
	public static void send() throws IOException{
		DatagramPacket packet;
		DatagramSocket socket;
		//1. 创建DatagramPacket对象,封装数据,并且指定接收端的IP地址和端口号
		byte[] data="hello".getBytes();
		byte[] address={127,0,0,1};
		InetAddress inet=InetAddress.getByAddress(address);
		packet=new DatagramPacket(data, data.length , inet, 8888);
		//2. 创建DatagramSocket对象,发送DatagramPacket对象数据包
		socket=new DatagramSocket(8880);//在本机申请一个端口号
		socket.send(packet);
		//3. 关闭资源
		socket.close();
	}
}
/**
 * @ClassName:UDPClient
 * @Description:数据接收端
 * 通过java.net.DatagramSocket对象接收到一个java.net.DatagramPacket对象
 * 解析java.net.DatagramPacket对象,获取其中的数据
 */
public class UDPClient {
	public static void main(String[] args) {
		try {
			get();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	/**
	 * @throws IOException 
	 * @Title:get
	 * @Description:接收数据
	 */
	public static void get() throws IOException{
		DatagramPacket packet;
		DatagramSocket socket;
		//1. 首先创建DatagramSocket对象,用于接收发送端传输的数据
		socket=new DatagramSocket(8888);//必须指定一个端口号,而且要与发送端发送的DatagramPacket对象的端口号一致
		//2. 创建字节数组,用于存储接收获取得数据
		byte[] data=new byte[1024];
		//3. 创建DatagramPacket对象,通过DatagramSocket的receive()方法接收数据
		packet=new DatagramPacket(data, data.length);
		//4. 接收数据,并且拆包分析
		socket.receive(packet);//该方法是一个线程阻塞方法,如果没有接收到数据,就会一直阻塞
		
		//拆包分析数据
		InetAddress inet=packet.getAddress();//获取数据发送端ip地址对象
		int length=packet.getLength();//获取数据字节数组真实长度
		
		System.out.println(new String(data,0,length));
		//5. 关闭资源
		socket.close();
	}
}

2.4 TCP通信的Java代码实现

    1. jdk中提供了两个类实现TCP协议网络程序:

  • java.net.ServerSocket,服务端接收数据与发送数据,创建该对象相当于建立一个服务器端,等待着客户端的连接请求
  • java.net.Socket,客户端接受与发送数据,创建该对象时,可以向指定ip与端口的服务器端发送连接请求,建立连接后即可开始通信

    2. 一个简单的TCP网络程序演示基本用法:

对于TCP协议中的客户端与服务端,它们之间做一次数据交互,需要4个字节流对象,客户端与服务端各一组输入输出字节流对象,分别用来向对方发出和接收数据。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * @ClassName:Server
 * @Description:服务端
 */
public class Server {
	public static void main(String[] args) throws IOException {
		//1. 指定服务端端口号,创建服务端
		ServerSocket server=new ServerSocket(8888);
		//2. 开启连接等待阻塞,直到有客户端建立连接,accept()方法才会解除阻塞退出,
		//   并且返回一个与客户端连接的Socket(套接字)对象
		Socket socket=server.accept();
		//3. 发送数据到客户端
		OutputStream out=socket.getOutputStream();
		out.write("hello,this is server".getBytes());
		//4. 从客户端接收数据
		InputStream in=socket.getInputStream();
		int length=socket.getReceiveBufferSize();//获取接收到的数据大小的真实字节长度
		byte[] b=new byte[length];
		in.read(b);
		System.out.println(new String(b));
		//6. 关闭资源,必须先关闭与客户端连接的套接字对象socket,然后关闭服务端server
		socket.close();
		server.close();
	}
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
/**
 * @ClassName:Client
 * @Description:客户端
 */
public class Client {
	public static void main(String[] args) throws UnknownHostException, IOException {
		//1. 创建一个Socket对象,用于与服务端建立连接和通信,构造方法中传入服务端ip地址和端口
		Socket socket=new Socket("127.0.0.1", 8888);
		//2. 通过Socket对象来获取一个字节输入流对象,用于获取服务端发来的数据
		InputStream in=socket.getInputStream();
		//3. 通过Socket对象来获取一个字节输出流对象,用于向服务端发送数据
		OutputStream out=socket.getOutputStream();
		//4. 接收从服务端发来的数据
		int length=socket.getReceiveBufferSize();//获取接收到的数据大小的真实字节长度
		byte[] b=new byte[length];
		in.read(b);
		System.out.println(new String(b));
		//5. 发送数据
		out.write("hello,this is client".getBytes());
		//6. 关闭资源
		socket.close();
	}
}

    3. TCP网络程序实现文件上传(以图片为例):稍微思考,可以首先传输完整的文件名,然后再传输文件的字节流,实际上与通过字节流实现文件复制的原理相同。

/**
 * @ClassName:UploadClient
 * @Description:图片上传客户端
 * 1. 建立连接
 * 2. 首先获取本地图片文件的输入流读取数据
 * 3. 通过Socket套接字对象获取输出到服务器的输出流
 * 4. 输出完整文件名到服务器
 * 5. 输出数据到服务端
 * 6. 通过Socket套接字对象获取输入流接收服务端返回的数据
 */
public class UploadClient {
	public static void main(String[] args) throws IOException {
		
		Socket socket = new Socket("127.0.0.1", 8888);
		
		String path="D:\\5a13818206778.jpg";
		FileInputStream fin=new FileInputStream(path);
		
		OutputStream out = socket.getOutputStream();
		byte[] fb=new byte[1024];
		int len=0;
		while((len=fin.read(fb))!=-1){
			out.write(fb,0,len);
		}
		//shutdownOutput()方法向服务端写终止序列,禁用当前套接字对象的OutputStream流
		//之前写入的数据会全部被传输至服务端,后面再使用该套接字的OutputStream流将会抛出IOException
		//使用该方法就表示当前所有要传输的数据都完成了,服务端在调用read方法读取数据时就不会进入等待阻塞
		//如果没有数据传来就会直接返回
		socket.shutdownOutput();
		int length = socket.getReceiveBufferSize();
		byte[] b = new byte[length];
		InputStream in = socket.getInputStream();
		in.read(b);
		System.out.println(new String(b));

		socket.close();
		fin.close();
		
	}
}
/**
 * @ClassName:UploadServer
 * @Description:图片上传服务端
 * 1. 与客户端建立连接
 * 2. 获取数据流
 * 3. 存储在指定文件中
 * 4. 向客户端发送响应,成功接收文件
 */
public class UploadServer {
	public static void main(String[] args) throws IOException {
		ServerSocket server=new ServerSocket(8888);
		Socket socket=server.accept();
		
		InputStream in=socket.getInputStream();
		//创建文件夹
		File dir=new File("D:\\develop");
		if(!dir.exists()){
			dir.mkdirs();
		}
		FileOutputStream fout=new FileOutputStream(dir+"\\upload.jpg");
		int length=0;
		byte[] b=new byte[1024];
		while((length=in.read(b))!=-1){
			fout.write(b,0,length);
		}

		OutputStream out=socket.getOutputStream();
		out.write("success".getBytes());
		socket.close();
		server.close();
	}
}

    4. TCP网络程序加多线程实现一个简单的群聊应用:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class Client {
	private Socket socket;
	public Client()throws Exception {
		socket=new Socket("localhost", 8088);
	}
	public void start()throws IOException{
		OutputStream out=socket.getOutputStream();
		OutputStreamWriter osw=new OutputStreamWriter(out, "UTF-8");
		PrintWriter pw=new PrintWriter(osw,true);
		Scanner scan=new Scanner(System.in);
		String str=null;
		
		ServerHandler handle=new ServerHandler();
		Thread t=new Thread(handle);
		t.start();
		while(true){
			str=scan.nextLine();
			pw.println(str);
		}
	}
	public static void main(String[] args) {
		try{
			Client client=new Client();
			client.start();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	private class ServerHandler implements Runnable{
		public void run() {
			try{
				InputStream in=socket.getInputStream();
				InputStreamReader isr=new InputStreamReader(in,"UTF-8");
				BufferedReader br=new BufferedReader(isr);
				String message=null;
				while((message=br.readLine())!=null){
					System.out.println(message);
				}
			}catch(Exception e){	
			}
		}
		
	}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Server {
	/*
	 * java.net.ServerSocket
	 * 运行在服务端的ServerSocket有两个作用
	 * 申请服务端口,客户端通过该端口与服务端建立连接
	 * 监听该接口,一旦客户端通过该端口连接后会创建一个socket实例与该客户端通讯
	 */
	private ServerSocket server;
	//保存所有客户端输出流的集合
	private List<PrintWriter> allout;
	
	public Server()throws IOException {
		/*
		 * 创建ServerSocket的同时需要申请服务端口
		 * 这个端口不能与其他使用TCP协议的应用程序冲突,否则抛出异常
		 */
		server=new ServerSocket(8088);
		allout=new ArrayList<PrintWriter>();
	}
	//向客户端输出流集合中添加元素
	private synchronized void addout(PrintWriter pw){
		allout.add(pw);
	}
	//向客户端输出流集合中删除元素
	private synchronized void removeout(PrintWriter pw){
		allout.remove(pw);
	}
	//遍历客户端输出流集合,将给定消息通过遍历集合将消息发送给每一个客户端
	private synchronized void sendmessage(String message){
		for(PrintWriter pw:allout){
			pw.println(message);
		}
	}
	//添加synchronized保证这三个方法互斥
	public void start()throws IOException{
		/*
		 * ServerSocket提供Socket accept( )方法
		 * 用于监听打开的服务端口,一旦一个客户端通过该端口请求时,就会创建一个
		 * Socket并返回,通过该Socket就可以与客户端进行交互
		 * 该方法是一个阻塞方法,直到客户端链接才会有返回值
		 */
		while(true){
			System.out.println("等待客户端链接......");
			Socket socket=server.accept();
			System.out.println("一个客户端已链接");
			/*
			 * 使用线程来解决多个客户端链接服务端的问题
			 */
			ClientHandle handle=new ClientHandle(socket);
			Thread t=new Thread(handle);
			t.start();
			}
	}
	public static void main(String[] args) {
		try{
			Server server=new Server();
			server.start();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	/**
	 * 该类是一个线程的任务,负责与指定客户端进行交互
	 *
	 */
	private class ClientHandle implements Runnable{
		/*
		 * 当前线程需要处理的针对指定客户端的socket
		 */
		private Socket socket;
		private String host;//该用户ip地址信息
		public ClientHandle(Socket socket) {
			this.socket=socket;
			//获取远程计算机地址信息
			InetAddress address=socket.getInetAddress();
			//获取ip地址
			host=address.getHostAddress();
		}
		public void run() {
			PrintWriter pw=null;
			try{
				OutputStream out=socket.getOutputStream();
				OutputStreamWriter osw=new OutputStreamWriter(out, "UTF-8");
				pw=new PrintWriter(osw,true);
				addout(pw);
			/*
			 * InputStream getInputStream( )
			 * Socket提供的该方法可以获取一个输入流,通过该输入流
			 * 可以读取远端计算机发送过来的数据
			 */
				System.out.println(host+"上线了");
				InputStream in=socket.getInputStream();
				InputStreamReader isr=new InputStreamReader(in,"UTF-8");
				BufferedReader br=new BufferedReader(isr);
				String message=null;
				while((message=br.readLine())!=null){
					/*
					 * 使用br.readLine()方法读取远端计算机发送过来的数据时,
					 * 由于操作系统可能不同
					 * 断开连接时的反应也不相同,当linux的客户端断开连接时会返回null
					 * windos客户端断开时会抛出异常
					 */
					sendmessage(host+"上线"+",当前在线人数共"+allout.size());
					sendmessage("已收到来自于"+host+"的"+message);
				}
			}catch(IOException e){
				e.printStackTrace();
			}finally {
				//客户端断开链接后处理
				removeout(pw);
				System.out.println(host+"下线了");
				try{
					socket.close();
				}catch(IOException e){
					e.printStackTrace();
				}
			}
		}
		
	}
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

    

转载于:https://my.oschina.net/ProgramerLife/blog/2109139

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值