JAVA网络编程入门

JAVA网络编程入门

软件结构

  1. C/S结构

    1568454627749

  2. B/S结构

1568454649812
无论哪一种结构,都离不开网络的支持。网络编程,就是在网络的条件下实现机器间的通信的过程

网络通信协议

网络通信协议:通信双方必须同时遵守才能完成数据交换

1568454801380
1568455164895
1568455186394
1568456326267
UDP:无连接性,数据被限制在64kb,适用于丢包问题不太大的情况,效率高

1568456464658
TCP:面向连接,可靠无差错,三次握手

网络编程三要素:协议,IP地址和端口号

  1. ip地址

    1568456757829
    查看本机Ip地址:控制台输入ipconfig

    查看是否可以和某一台计算机进行网络交换:ping ip地址

  2. 端口号

1568456899432
用协议+IP地址+端口号可以唯一的表示网络中的进程并进行进程间的通信了

软件在启动时可以和操作系统要指定的端口号,或者由操作系统分配随机的端口号

端口号由两个字节组成,范围为0-65535

1568457087569

TCP通信程序

TCP通信可以实现两台计算机之间的数据交互,通信的两端要严格区分客户端和服务器端

1568457259222
客户端和服务器端进行一个数据交流,一共需要4个IO流对象:

1568457536889
1568457609557
? Socket s1=server.accept();//获取客户端的流对象

1568457680300
java.net

套接字(Socket):包含了ip地址和端口号的网络单位(可以借此识别设备和进程)

  1. 客户端

1568458230273
这里使用Socket的输入流来读,输出流来写,不要混了

  1. 服务器端

1568458675650
服务器端必须明确知道是哪个客户端请求的服务器,所以要使用accept方法

一个TCP通信的小例子:

TCPServer.java

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

import org.omg.CORBA.portable.InputStream;

/*
*@author JiaDing
*服务器端
*/
public class TCPServer {
	public static void main(String[]args)throws IOException{
		//创建使用特定端口的对象
		ServerSocket server=new ServerSocket(8888);
		Socket socket=server.accept();
		java.io.InputStream is=socket.getInputStream();
		byte[]bytes=new byte[1024];
		int len=is.read(bytes);
		System.out.println(new String(bytes,0,len));//利用字符数组创建字符串
		OutputStream os=socket.getOutputStream();
		os.write("收到谢谢".getBytes());//要将字符串转换为字符数组才行
		socket.close();//销毁socket对象
		server.close();//销毁server对象
;	}
}

TCPClient.java

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/*
*@author JiaDing
*TCP通信的客户端
*使用host(服务器主机的名称/IP地址)和port(端口号)的构造方法
*getOutputStream 返回套接字的输出流,使用其write方法给服务器发送数据
*getInputStream 返回套接字的输入流,使用其read方法读取服务器回写数据
*close关闭套接字
*/

public class TCPClient {
	public static void main (String[] args) throws IOException {
		Socket socket=new Socket("127.0.0.1",8888);	
		OutputStream os=socket.getOutputStream();
		os.write("你好服务器".getBytes());
		InputStream is=socket.getInputStream();
		byte[]bytes=new byte[1024];
		int len=is.read(bytes);
		System.out.println(new String(bytes,0,len));
		socket.close();
	}
	
}

TCP通信往往是按字节处理而不是字符处理,因为不是所有类型都是字符

127.0.0.1是什么地址?*

127.0.0.1/8整个都是环回地址,用来测试本机的TCP/IP协议栈,发往这段A类地址数据包不会出网卡,网络设备不会对其做路由。

实例:文件上传

1568465232380
Client.java

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;

/*
*@author JiaDing
*文件上传案例的客户端
*/
public class Client {
	public static void main(String[]args)throws IOException {
		FileInputStream fis=new FileInputStream("E:\\out.txt");
		Socket socket=new Socket("127.0.0.1",8888);
		java.io.OutputStream os=socket.getOutputStream();
		byte[] re=new byte[1024];//准备了1kb的大小
		int len=0;
		while((len=fis.read(re))!=-1) {//这里一定要给len=fis.read(re)加一个括号,否则两个等号无法区分优先级
			os.write(re,0,len);
		}
		/*
		 * 注意!这里有一个很重要的问题!
		 * 由于我们的判断条件是len!=-1,所以我们无法将文件的结束标记上传过去,这样服务器会一直处于等待状态,造成阻塞
		 * 而对应的,由于服务器阻塞,所以无法执行回显,is.read()也就无法读入数据,同样陷入阻塞
		 * 解决办法就是在客户端利用套接字的方法人工结束输出流
		 */
		socket.shutdownOutput();
		InputStream is=socket.getInputStream();
		while((len=is.read(re))!=-1) {//这里一定要给len=fis.read(re)加一个括号,否则两个等号无法区分优先级
			System.out.println(new String(re,0,len));
		}
		fis.close();
		socket.close();
		}
	
	
}

Server.java

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;

/*
*@author JiaDing
*文件上传案例的服务器端
*/
public class Server {
	public static void main(String[]args) throws IOException{
		ServerSocket ss=new ServerSocket(8888);
		Socket socket=ss.accept();
		File file=new File("E:\\update");
		if(!file.exists())
			file.mkdir();
		byte[] by=new byte[1024];
		InputStream is=socket.getInputStream();
		FileOutputStream out=new FileOutputStream("E:\\update\\t1.txt");
		int len=0;
		while(len!=-1) {
			out.write(by, 0, len);
			len=is.read(by);
		}
		OutputStream os=socket.getOutputStream();
		os.write("Update is finished!".getBytes());
		socket.close();
		out.close();
		ss.close();
		
	}
}

优化:

  1. 名称写死了:自定义服务器端命名规则,防止同名文件被覆盖
//自定义文件名规则:域名+毫秒值+随机数
		//毫秒值广泛应用在服务器端文件名保存上
		String name="jd"+System.currentTimeMillis()+new Random().nextInt(999999)+".txt";
		FileOutputStream out=new FileOutputStream("E:\\update\\"+name);
  1. 服务器上传后就停止:让服务器一直处于监听状态:使用死循环,服务器不关闭

B\S服务器的模拟

服务器代码-返回请求版:

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

/*
*@author JiaDing
*BS版本的TCP服务器
*/
public class Server {
	public static void main(String[]args)throws IOException{
		ServerSocket server=new ServerSocket(8080);
		Socket socket=server.accept();
		InputStream is=socket.getInputStream();
		byte[]by=new byte[1024];
		int len=0;
		while((len=is.read(by))!=-1) {
			System.out.println(new String(by,0,len));
		}
	}
	
	
}

访问时要指明文件夹名\要访问的资源名,如果端口不是80还要在IP后面加上端口号

服务器端结果(其实就是客户端的请求信息):

GET /src/Test1.html HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.75 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: none
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

服务器应该回写应该网页(文件),我们需要读取文件地址,地址已经包含在了请求第一行

可以使用BufferedReader中的方法readList读取第一行

1568471404194
服务器代码—返回网页版:

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
*@author JiaDing
*BS版本的TCP服务器
*/
public class Server {
	public static void main(String[]args)throws IOException{
		ServerSocket server=new ServerSocket(8080);
		Socket socket=server.accept();
		InputStream is=socket.getInputStream();
		/*byte[]by=new byte[1024];
		int len=0;
		while((len=is.read(by))!=-1) {
			System.out.println(new String(by,0,len));
		}*/
		//把is网络字节输入流对象转换为字符缓存输入流
		BufferedReader br=new BufferedReader(new InputStreamReader(is));
		String line=br.readLine();
		String htmlpath=line.split(" ")[1].substring(1);//取第一行中间的地址,去掉最前面的、
		//System.out.println(htmlpath);
		FileInputStream fis=new FileInputStream(htmlpath);
		OutputStream os=socket.getOutputStream();
		//写入HTTP协议响应头,固定写法
		os.write("HTTP/1.1 200 OK\r\n".getBytes());
		os.write("Content-Type:text/html\r\n".getBytes());
		//必须写入空行,否则浏览器不解析
		os.write("\r\n".getBytes());
		int len=0;
		byte[]by=new byte[1024];
		while((len=fis.read(by))!=-1) {
			os.write(by,0,len);
		}
		fis.close();
		socket.close();
		server.close();
	}
	
	
}

1568472450459

  • 为什么服务器要是多线程呢?

    如果不采用多线程,那么服务器只能线型地处理浏览器的请求,浏览器上的资源只能一个一个打开,体验会很差

采样头多线程之后的代码:

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
*@author JiaDing
*BS版本的TCP服务器
*/
public class Server {
	public static void main(String[]args)throws IOException{
		ServerSocket server=new ServerSocket(8080);
		while(true) {
			Socket socket=server.accept();
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						InputStream is=socket.getInputStream();
						//把is网络字节输入流对象转换为字符缓存输入流
						BufferedReader br=new BufferedReader(new InputStreamReader(is));
						String line=br.readLine();
						String htmlpath=line.split(" ")[1].substring(1);//取第一行中间的地址,去掉最前面的、
						//System.out.println(htmlpath);
						FileInputStream fis=new FileInputStream(htmlpath);
						OutputStream os=socket.getOutputStream();
						//写入HTTP协议响应头,固定写法
						os.write("HTTP/1.1 200 OK\r\n".getBytes());
						os.write("Content-Type:text/html\r\n".getBytes());
						//必须写入空行,否则浏览器不解析
						os.write("\r\n".getBytes());
						int len=0;
						byte[]by=new byte[1024];
						while((len=fis.read(by))!=-1) {
							os.write(by,0,len);
						}
						fis.close();
						socket.close();
					}catch(IOException e) {
						e.printStackTrace();
					}
				}
			}).start();
			
		}
	}
	
	
}

注意这里会报一个错误:java.io.FileNotFoundException: favicon.ico。这是因为浏览器会自动访问网站的图标而我们的网站没有的原因,这个图标必须放在根目录,例如如果访问的地址是“http://127.0.0.1/src/Test1.html”,那么就要放在src文件夹的外面

另外,实验了一下,将服务器的端口改成了80好像并没有什么问题,因为服务器监听的是本机的80端口,而如果我们访问外网的话,应该是访问外网网站服务器的80端口,两者并不冲突。部署在本机上的服务器只能监听到访问本机且本端口的请求。所以,网页中如果有外链,那么打开这个外链的过程和我们的服务器是没有丝毫关系的,我们自建的服务器的好坏不会对其造成影响

posted @ 2019-10-19 21:35  别再闹了  阅读(1420)  评论(0编辑  收藏  举报