java网络编程

网络编程

基础知识

你为什么可以上网?

什么是互联网?

什么根服务器?

所谓互联网,就是将所有的计算机连接在一起就构成了互联网。

我们的每一台计算机在互联网上都有唯一的地址:IP地址。

我们为什么可以链接到百度的服务器,因为百度的服务器和我们的计算机在一个互联网中。

计算机虽然互联,但是不能随意的访问。

如果希望别人访问你的计算机,你的计算就必须提供一个可以供别人访问的服务。

黑客是如何进入你的计算机的?

①要有地址。(我去你家,必须知道你家在哪)

②要有一个端口。(你家得有一个门)

你的计算机希望别人访问:

①连上互联网,有一个唯一的IP地址。

②有一个服务监听一个端口

Java 网络编程

网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。

java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。

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

TCP:TCP(英语:Transmission Control Protocol,传输控制协议) 是一种面向连接的、可靠的、基于字节流的传输层通信协议,TCP 层是位于 IP 层之上,应用层之下的中间层。TCP 保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。

UDP:UDP (英语:User Datagram Protocol,用户数据报协议),位于 OSI 模型的传输层。一个无连接的协议。提供了应用程序之间要发送数据的数据报。由于UDP缺乏可靠性且属于无连接协议,所以应用程序通常必须容许一些丢失、错误或重复的数据包。

Socket 编程

套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。

当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。

java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。

以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:

服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。

服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。

服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。

Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。

在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。

连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。

TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送.以下是一些类提供的一套完整的有用的方法来实现 socket

ServerSocket 类

在这里插入图片描述

服务器应用程序通过使用 java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。
ServerSocket 类有四个构造方法:
在这里插入图片描述
ServerSocket的两个有用的方法:

public Socket accept() throws IOException
侦听并接受到此套接字的连接。
	public void bind(SocketAddress host, int backlog)
将 ServerSocket 绑定到特定地址(IP 地址和端口号)。

案例

public class ServerSocketTets {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
            // 创建对象
            serverSocket = new ServerSocket(9825);
            // 开始监听
            System.out.println("服务端开始监听端口9852");
            serverSocket.accept();
            System.out.println("接收到请求....");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在浏览器中访问:http://localhost:9852

Socket 类

java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket 对象通过实例化 ,而 服务器获得一个 Socket 对象则通过 accept() 方法的返回值。

Socket 类有五个构造方法.

public Socket(String host, int port) throws UnknownHostException, IOException.
创建一个流套接字并将其连接到指定主机上的指定端口号。
	public Socket(InetAddress host, int port) throws IOException
创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException.
创建一个套接字并将其连接到指定远程主机上的指定远程端口。
public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException.
创建一个套接字并将其连接到指定远程地址上的指定远程端口。
	public Socket()
通过系统默认类型的 SocketImpl 创建未连接套接字

当 Socket 构造方法返回,并没有简单的实例化了一个 Socket 对象,它实际上会尝试连接到指定的服务器和端口。

Socket 的几个重要的方法:

	public InputStream getInputStream() throws IOException
返回此套接字的输入流。
public OutputStream getOutputStream() throws IOException
返回此套接字的输出流。
public void close() throws IOException
关闭此套接字。

调整上面的服务端案例程序

    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
            // 创建对象
            serverSocket  = new ServerSocket(9825);
            // 开始监听
            System.out.println("服务端开始监听端口9852");
            Socket socket = serverSocket.accept();
            //得到输出流
            OutputStream out = socket.getOutputStream();
            out.write("HTTP/1.1 200 OK\n".getBytes());
            out.write("Content-type: text/html;charset=utf-8\n\n".getBytes());
            out.write("<h2>你好!我是戴着假发的程序员!</h2>".getBytes());
            System.out.println("接收到请求....");

            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

在浏览器中再次访问测试!!!

我们自己实现一个Socket链接服务器端,并且像服务器端发送请求数据。
InetAddress 类
这个类表示互联网协议(IP)地址。

static InetAddress getByAddress(byte[] addr)
在给定原始 IP 地址的情况下,返回 InetAddress 对象。
	static InetAddress getByName(String host)
在给定主机名的情况下确定主机的 IP 地址。

Socket实例:

    public static void main(String[] args)   {
        Socket socket = null;
        try {
            socket = new Socket(InetAddress.getByAddress(new byte[]{127,0,0,1}),9825);
            //得到输入流和输出流
            InputStream in = socket.getInputStream();
            OutputStream out = socket.getOutputStream();
            out.write("你好服务器!我是一个无聊的客户端".getBytes());
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            String line = null;
            while((line = reader.readLine())!=null){
                System.out.println(line);
            };
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

协议

所谓服务器端,就是写一个程序可以监听服务器计算机上的一个端口。客户端无所谓是Socket或者其他语言编写的链接等等。

之所以可以做到这一点就是靠协议。

TCP协议
在这里插入图片描述
一个长连接,两台计算机之间架设一个管道,类似于打电话。

UDP协议
在这里插入图片描述
UDP协议是不保持链接的,会根据IP和端口发送数据报。类似于发短信。

我们上面所使用的案例就是使用的TCP协议,我们使用字节流发送数据的。

UDP协议的使用


案例:
服務器端:

/**
 * @author 戴着假发的程序员
 * @TODO 接收端
 * @organization 飞虎队
 * 2020年8月29日 上午10:29:34
 */
public class DatagramSocketATest {
	public static void main(String[] args) throws Exception {
		int port = 8585;
		DatagramSocket socketA = new DatagramSocket(port);
		//准备数据报
		byte [] buff = new byte[1024];
		//接收数据的数据报
		DatagramPacket packet = new DatagramPacket(buff, buff.length);
		//接收数据
		System.out.println("等待接收数据....");
		socketA.receive(packet);
		//将数据输出
		String str = new String(buff);
		System.out.println("接收到数据");
		System.out.println(str);
	}
}

客戶端

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年8月29日 上午10:29:34
 */
public class DatagramSocketBTest {
	public static void main(String[] args) throws Exception {
		int port = 8585;
		DatagramSocket socketB = new DatagramSocket();
		//准备数据报
		byte[] ip = new byte[] { (byte) 192, (byte) 168, 10, 19 };
		InetAddress inetAddress = InetAddress.getByAddress(ip);
		byte [] buff = "这里是B点发送的数据".getBytes();
		//创建一个发送数据的数据报
		DatagramPacket packet = new DatagramPacket(buff, buff.length,inetAddress,port);
		//将数据发送
		socketB.send(packet);
		System.out.println("发送完成");
	}
}

UDP协议发送端,只管发送数据,至于接收端是否接收到数据并不在意。
TCP是按照流处理数据的,而UDP是按照数据报(数据块)处理数据的。

服务器端优化-循环监听

上面的案例中我们的服务器端只监听一次,接收到客户端请求之后,主线程就结束了,无法继续监听。
所以我们调整上面的服务器端程序,让主线程循环监听,每次接收到请求之后就开启子线程处理请求,主线程继续监听。
案例:


/**
 * @author  戴着假发的程序员
 */
public class ServerSocketTets {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        boolean run = true;
        try {
            // 创建对象
            serverSocket  = new ServerSocket(9825);
            // 开始监听
            System.out.println("服务端开始监听端口9852");
            while(run) {
                //循环监听
                Socket socket = serverSocket.accept();
                //启动子线程处理请求
                new Thread(new ExecuteRequest(socket)).start();
            }
            System.out.println("服务器停止....");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ExecuteRequest implements Runnable{
    private Socket socket;
    public ExecuteRequest(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        //得到输出流
        OutputStream out = null;
        InputStream in = null;
        try {
            in = socket.getInputStream();
            out = socket.getOutputStream();
            out.write("HTTP/1.1 200 OK\n".getBytes());
            out.write("Content-type: text/html;charset=utf-8\n\n".getBytes());
            out.write("<h2>你好!我是戴着假发的程序员!</h2>".getBytes());
            System.out.println("接收到请求....");
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if(out!=null)
                    out.close();
                if(in!=null)
                    in.close();
            }catch (Exception e2){

            }
        }
    }
}

重复启动客户端测试。 当然也可以在浏览器中重复访问测试!!!

实现一个聊天室

聊天室可能会有多个客户端链接. 就需要服务端循环的监听。

服务器端的处理socket的线程类:

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年8月29日 上午11:29:11
 */
public class ExecuSocket implements Runnable{
	private Socket socket;
	public ExecuSocket(Socket socket) {
		this.socket = socket;
	}

	public void run() {
		InputStream in = null;
		BufferedReader reader = null;
		try {
			in = socket.getInputStream();
			//默认所有客户端一旦链接就发送自己的名字
			reader = new BufferedReader(new InputStreamReader(in));
			//读取第一行
			String name = reader.readLine();
			//输出名字到群聊中
			printMsg("欢迎["+name+"]加入聊天室!");
			//循环的读取这个客户端发送的消息
			String line = null;
			while((line = reader.readLine())!=null) {
				printMsg(line+"\r\n");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	//将消息内容输出到群聊中
	private void printMsg(String msg) {
		//socket全部缓存在Server的成员变量sockets中
		for(Socket sc : Server.sockets) {
			OutputStream out = null;
			try {
				out = sc.getOutputStream();
				out.write((msg+"\r\n").getBytes());
				out.flush();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

服务器端的主线程类:

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年8月29日 上午11:20:53
 */
public class Server {
	public static List<Socket> sockets = new ArrayList<Socket>();
	public static void main(String[] args) throws IOException {
		int count = 0;
		int port = 8888;
		ServerSocket serverSocket = new ServerSocket(port);
		System.out.println("服务器端开始监听......");
		//循环监听
		while(true) {
			Socket socket = serverSocket.accept();
			count ++;
			System.out.println("链接了一个客户端,当前客户端数量:"+count);
			//缓存所有的客户端链接
			sockets.add(socket);
			//开启线程
			Thread t = new Thread(new ExecuSocket(socket));
			t.start();
		}
	}
}

客户端发消息的线程

/**
 * @author 戴着假发的程序员
 * @TODO 发消息的线程
 * @organization 飞虎队
 * 2020年8月29日 上午10:44:59
 */
public class SendMsg implements Runnable {
	private OutputStream out;
	public SendMsg(OutputStream out) {
		this.out = out;
	}

	public void run() {
		Scanner sc = new Scanner(System.in);
		//获取当前线程的名字
		System.out.println("请输入您的名字:");
		String clientName = sc.next();
		try {
			//第一行将名字先写到服务器
			out.write((clientName+"\r\n").getBytes());
			out.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
		while(true) {
			System.out.println("请输入您要发送的消息(输入exit表示不再发送):");
			String str = null;
			while(true) {
				try {
					str = sc.next();
					break;
				} catch (Exception e) {
					System.out.println("您输入的格式不正确,请重新输入:");
				}
			}
			if(str.equals("exit")) {
				break;
			}
			//将输入发出去  写一行数据出去
			str=clientName+"说:"+str+"\r\n";
			try {
				out.write(str.getBytes());
				out.flush();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

客户端受消息的线程:

/**
 * @author 戴着假发的程序员
 * @TODO
 * @organization 飞虎队 2020年8月29日 上午10:45:11
 */
public class ReviceMsg implements Runnable {
	private InputStream in;

	/**
	 * @param in
	 */
	public ReviceMsg(InputStream in) {
		super();
		this.in = in;
	}

	public void run() {
		// 获取当前线程的名字
		String name = Thread.currentThread().getName();
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new InputStreamReader(in));
			String line = null;
			while ((line = reader.readLine()) != null) {
				System.out.println(line);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

客户端主线程类:

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年8月29日 上午11:22:40
 */
public class Client {
	public static void main(String[] args) throws IOException {
		int port = 8888;
		byte[] ip = new byte[] { (byte) 192, (byte) 168, 10, 19 };
		InetAddress inetAddress = InetAddress.getByAddress(ip);
		// 创建Socket socket会自动链接服务器
		Socket socket = new Socket(inetAddress, port);
		Thread tin = new Thread(new ReviceMsg(socket.getInputStream()));
		tin.start();
		Thread tout = new Thread(new SendMsg(socket.getOutputStream()));
		tout.start();
		while(true) {}
	}
}

使用TCP协议实现一个点对点的聊天

准备两个线程

发送消息的线程:

/**
 * @author 戴着假发的程序员
 * @TODO 发消息的线程
 * @organization 飞虎队
 * 2020年8月29日 上午10:44:59
 */
public class SendMsg implements Runnable {
	private OutputStream out;
	public SendMsg(OutputStream out) {
		this.out = out;
	}

	public void run() {
		Scanner sc = new Scanner(System.in);
		//获取当前线程的名字
		String name = Thread.currentThread().getName();
		while(true) {
			System.out.println("请输入您要发送的消息(输入exit表示不再发送):");
			String str = null;
			while(true) {
				try {
					str = sc.next();
					break;
				} catch (Exception e) {
					System.out.println("您输入的格式不正确,请重新输入:");
				}
			}
			if(str.equals("exit")) {
				break;
			}
			//将输入发出去  写一行数据出去
			str+="\r\n";
			try {
				out.write(str.getBytes());
				out.flush();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

接收消息的线程

/**
 * @author 戴着假发的程序员
 * @TODO
 * @organization 飞虎队 2020年8月29日 上午10:45:11
 */
public class ReviceMsg implements Runnable {
	private InputStream in;

	/**
	 * @param in
	 */
	public ReviceMsg(InputStream in) {
		super();
		this.in = in;
	}

	public void run() {
		// 获取当前线程的名字
		String name = Thread.currentThread().getName();
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new InputStreamReader(in));
			String line = null;
			while ((line = reader.readLine()) != null) {
				System.out.println(name+"接受到消息:"+line);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

服务端:

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年8月29日 上午10:41:49
 */
public class Server {
	public static void main(String[] args) throws IOException {
		int port = 8888;
		ServerSocket serverSocket = new ServerSocket(port);
		System.out.println("服务器端开始监听......");
		Socket socket = serverSocket.accept();
		System.out.println("接收到客户端的请求");
		//从socket中输入流和输出流
		InputStream in = socket.getInputStream();
		OutputStream out = socket.getOutputStream();	
		//启动两个子线程
		Thread tin = new Thread(new ReviceMsg(in),"服务器端接收:");
		Thread tout = new Thread(new SendMsg(out),"服务器端发送:");
		tin.start();
		tout.start();
		//不让主线程结束
		while(true) {}
	}
}

客户端:

/**
 * @author 戴着假发的程序员
 * @TODO
 * @organization 飞虎队 2020年8月29日 上午11:13:06
 */
public class Client {
	public static void main(String[] args) throws IOException {
		int port = 8888;
		byte[] ip = new byte[] { (byte) 192, (byte) 168, 10, 19 };
		InetAddress inetAddress = InetAddress.getByAddress(ip);
		// 创建Socket socket会自动链接服务器
		Socket socket = new Socket(inetAddress, port);
		// 从socket中输入流和输出流
		InputStream in = socket.getInputStream();
		OutputStream out = socket.getOutputStream();
		// 启动两个子线程
		Thread tin = new Thread(new ReviceMsg(in), "客户端端接收:");
		Thread tout = new Thread(new SendMsg(out), "客户端端发送:");
		tin.start();
		tout.start();
		// 不让主线程结束
		while (true) {
		}
	}
}

实现一个简单的web服务器

HTTP请求的请求行:
在这里插入图片描述

请求处理线程类:

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年8月29日 下午2:08:02
 */
public class RequestHandler implements Runnable {
	private Socket socket;
	public RequestHandler(Socket socket) {
		super();
		this.socket = socket;
	}
	public void run() {
		//读取请求中的请求行
		InputStream in = null;
		OutputStream out = null;
		BufferedReader reader = null;
		BufferedReader fileReader = null;
		try {
			in = socket.getInputStream();
			out = socket.getOutputStream();
			//读取第一行数据
			reader = new BufferedReader(new InputStreamReader(in));
			String firstLine = reader.readLine();//读取第一行数据
			System.out.println("接收到请求:"+firstLine);
			//使用空格分割第一行数据
			String[] info = firstLine.split(" ");
			String resourceName = info[1];//资源名称(网页的名称)
			if("/".endsWith(resourceName)) {
				//直接返回index.html即可
				resourceName = "/index.html";
			}
			File file = new File("home/st"+resourceName);
			//判断文件是否存在
			if(file.exists() && file.isFile()) {
				//将HTML文件输出到客户端
				//输出响应行
				out.write("HTTP/1.1 200 OK\r\n".getBytes());
				out.write("Content-Type: text/html; charset=UTF-8\r\n".getBytes());
				out.write("\r\n".getBytes());//这里输出一个空行表示接下来是正文
				//将对应的文件输出到浏览器(文件拷贝)
				fileReader = new BufferedReader(new FileReader(file));
				//循环的输出
				String line = null;
				while((line = fileReader.readLine())!=null) {
					out.write((line+"\r\n").getBytes());
					out.flush();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			try {
				//将所有的流全部关闭
				if(in!=null)
					in.close();
				if(out!=null)
					out.close();
				if(fileReader!=null) {
					fileReader.close();
				}
				if(socket!=null)
					socket.close();
			} catch (Exception e2) {
				// TODO: handle exception
			}
		}
	}
}

服务主程序类:

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年8月29日 下午2:06:44
 */
public class Server {
	public static void main(String[] args) throws IOException {
		int port = 8888;
		ServerSocket serverSocket = new ServerSocket(port);
		System.out.println("服务器开始监听");
		while(true) {
			Socket socket = serverSocket.accept();
			//启动子线程处理请求
			new Thread(new RequestHandler(socket)).start();
		}
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

戴着假发的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值