第一 概述
一、概述:
1、网络模型:OSI参考模型和TCP/IP参考模型
2、网络通讯要素:
1)IP地址:InetAddress
.网络中设备的标识
.不易记忆,可用主机名Internet上的主机有两种方式表示地址: 域名:ww.baidu.com, IP 地址:202.108.35.210
InetAddress 类对象含有一个 Internet 主机地址的域名和IP地址:ww.baidu.com。 域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接。
2)端口号 .用于标识进程的逻辑地址,不同进程的标识 .有效地址0~65535,其中0~1024系统使用或保留端口 3)传输协议:通讯规则 .常见协议:TCP、UDP①UDP(面向无连接)-->聊天、网络视频会议、步话机
- 将数据及源和目的封装成数据包中,不需要建立连接
- .每个数据包的大小限制在64k内
- .因无连接,是不可靠的协议
- .不需要建立连接,速度快
②TCP(面向连接)-->下载,打电话
- 建立连接,形成传输数据的通道
- 在连接中进行大数据量的传输
- 通过三次握手完成连接,是可靠的协议
- .必须建立连接,效率会稍低
注:三次握手:第一次本方发送请求,第二次对方确认连接,第三次本方再次发送确认信息告诉对方,这样双方就都知道了,从而才能建立连接
3、通信的步骤:
1)IP:找到需要通讯的IP地址
2)端口:数据要发送到对象指定应用程序,为标识这些应用程序,所以给这些网络应用程序都用数字标识,为方便称呼这个数字,叫做端口,即逻辑端口。每个网络程序都有自己唯一的标识端口。
3)定义通信规则,称之为协议。国际组织定义了通用协议,即TCP/IP。
注意:必须要有数字标识才能将数据发送到应用程序上。因为端口是明确数据需要有哪个应用程序来处理的标识。
二、网络模型:
1、对于TCP/IP协议,开发处于传输层和网际层
应用层:FTP和HTTP协议等
传输层:UDP和TCP等
网际层:IP
三、网络通信要素:
IP地址:java中对应的是InetAddress类,存在于java.net包中。
InetAddress类:
1、无构造函数,可通过getLocalHost()方法获取InetAddress对象,此方法是静态的,返回此对象。
InetAddress i = InetAddress.getLocalHost();
2、方法:
1)static InetAddress getByName(String host):在给定主机名的情况下获取主机的IP地址
2)String getHostAddress():返回IP地址字符串文本形式,以这个为主,即以IP地址为主。
3)String getHostName():返回IP地址主机名。
3、如何获取任意一台主机的IP地址对象:publicclassIPDemo{
publicstaticvoid main(String[] args)throwsUnknownHostException{
// 获取本地主机ip地址对象。
InetAddress ip =InetAddress.getLocalHost();
System.out.println(ip.getHostAddress());//192.168.1.167
System.out.println(ip.getHostName());//PC201508162051
// // 获取其他主机的ip地址对象。只要输入iP或域名
InetAddress byName =InetAddress.getByName("PC201508162051");// PC201508162051/192.168.1.167
InetAddress byName2 =InetAddress.getByName("www.baidu.com");// www.baidu.com/61.135.169.121
System.out.println(byName);
System.out.println(byName2);
}
}
第二 UDP
一、概述:
Socket:
- Socket就是为网络服务提供的一种机制
- 通讯的两端都必须有Socket(套接字),相当于港口
- 网络通讯其实就是Socket间的通讯
- 数据在两个Socket间通过IO传输
- IP 地址标识 Internet 上的计算机,端口号标识正在计算机上运行的进程(程序)。 端口号与IP地址的组合得出一个网络套接字。
二、UDP传输:发送端和接收端是两个独立运行的程序
1、UDP传输的流程:
- .DatagramSocke(创建Socket对象在发送端和接收端通讯)和DatagramPacket(发送端将数据封包,接收端将数据解包)
- .建立发送端、接收端
- .建立数据包
- .调用Socket的发送和接收方法
- .关闭Socket
(byte[] buf, int length)接收 构造 DatagramPacket ,用来接收长度为 length 的数据包。 |
(byte[] buf, int length, address, int port)发送 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。 |
2、方法:
- 创建 UDPSocket发送服务对象:DatagramSocket(),可不指定端口
- 创建 UDPSocket接收服务对象:DatagramSocket(int port),
- 发送:void send(DatagramPacket p)
- 接收:void receive(DatagramPacket p)
其中DatagramPacket:数据报包用来实现无连接包投递服务的,每条报文仅根据该包中包含的信息从一台机器路由到另一台机器中。
凡是带地址(InetAddress)的都是用于发送包的。
实例:
/*
* 需求:通过udp传输方式,将一段文字发送出去
* 定义一个udp发送端
*/
publicclassUDPSend{
publicstaticvoid main(String[] args)throwsSocketException,Exception{
// 1、建立udp服务,通过DategramSocket对象
DatagramSocket ds =newDatagramSocket(8888);
// 2、确定数据,并封装成数据包DatagramPacket(byte[] buf, int length, InetAddress,
address, int port)byte[] buf ="udp send message come on".getBytes();// 数据 ,转成字节数组
DatagramPacket dp =newDatagramPacket(buf, buf.length,
InetAddress.getByName("192.168.229.1"),1000);
// 发到哪 InetAddress.getByName("192.168.229.1"),1000别写系统端口
// 3、通过socket服务的send方法将数据包发送出去
ds.send(dp);
// 4、关闭资源
ds.close();
}
}
| () 返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。 |
byte[] | () 返回数据缓冲区。 |
int | () 返回将要发送或接收到的数据的长度。 |
/*
* 需求:定义一个应用程序,用于接收udp协议传输的数据并处理
*
* 定义udp接收端口
* 思路:
* 1、定义udp的socket服务,通常会监听一个端口。其实就是给这个接收网络应用程序定义一个数字标识,
* 明确哪些数据过来该应用程序可以处理
* 2、定义一个数据包,用于存储接收到的字节数据,可以方便提取数据中的信息
* 3、通过socket的receive方法将接收到的数据存入以定义好的数据包中
* 4、通过数据包中特有功能,将不同的数据取出
* 5、关闭资源
*/
publicclassUDPReceive{
publicstaticvoid main(String[] args)throwsException{
// 1、建立udp的socket服务,并指定端点
DatagramSocket ds =newDatagramSocket(1000);
// 2、定义数据包,用于存储数据
byte[] buf =newbyte[1024];
DatagramPacket dp =newDatagramPacket(buf, buf.length);
// 3、通过socket服务的receive方法将接收到的数据存入数据包中
ds.receive(dp);// 阻塞式方法 ,不发送就一直接收
// 4、通过数据包的方法获取其中的数据
// String getHostAddress():返回IP地址字符串文本形式,以这个为主,即以IP地址为主。
String ip = dp.getAddress().getHostAddress();// 获取ip
String data =newString(dp.getData(),0, dp.getLength());// dp.getData()获取数据,不需要获取1024,获取指定长度就行
int port = dp.getPort();// 获取端口号
,这里的端口和ip是发送端的,他是随机的,可以在套接字中指定,不指定系统随机分配System.out.println(ip +"::"+ data +"::"+ port);
// 5、关闭资源
ds.close();
}
}
-
/* * 发送键盘录入信息 */ public class UDPSend2 { public static void main(String[] args) throws Exception { DatagramSocket ds = new DatagramSocket(); BufferedReader bfr = new BufferedReader( new InputStreamReader(System.in)); //转换流,转成字节 String line = null; byte[] buf = null; DatagramPacket dp = null; while ((line = bfr.readLine()) != null) { buf = line.getBytes(); dp = new DatagramPacket(buf, buf.length, InetAddress.getByName("192.168.229.1"), 10000);//192.168.1.255广播地址,在这个频段里的所有ip都可以接收,聊天室 ds.send(dp); if("886".equals(line)){ break; } } ds.close(); buf.clone(); } }
练习:编写一个简单的聊天程序:
分析:
有收数据的部分,有发数据的部分,这两部分需要同时执行,那就需要多线程技术,一个线程控制接收,一个线程控制发。
因为收和发的动作不一致,所以要定义两个run方法,而且这个两个方法要封装到不同的类中。
-
public class UDPChatDemo { public static void main(String[] args) throws IOException { DatagramSocket sendSocket = new DatagramSocket(); DatagramSocket ReceiveSocket = new DatagramSocket(10002); new Thread(new ChatSend(sendSocket)).start(); new Thread(new ChatReceive(ReceiveSocket)).start(); } } /* * 聊天的发送端 */ public class ChatSend implements Runnable{ private DatagramSocket ds; public ChatSend(DatagramSocket ds){ this.ds = ds; } @Override public void run() { BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in)); byte[] buf = null; String line = null; try { while((line=bfr.readLine()) != null){ buf = line.getBytes(); DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getByName("192.168.1.255"), 10002); ds.send(dp); } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ ds.close(); try { bfr.close(); } catch (IOException e) { e.printStackTrace(); } } } } /* * 聊天,接收端 */ public class ChatReceive implements Runnable{ private DatagramSocket ds; public ChatReceive(DatagramSocket ds){ this.ds = ds; } @Override public void run() { while(true){ byte[] buf = new byte[1024]; DatagramPacket dp = new DatagramPacket(buf, buf.length); try { ds.receive(dp); String ip = dp.getAddress().getHostAddress(); String data = new String(dp.getData(),0,dp.getLength()); int port = dp.getPort(); System.out.println(ip + "::" + data + "::" + port); } catch (IOException e) { e.printStackTrace(); } } } }
第三 TCP传输
1、流程:
- Socket(客户端)和ServiceSocket服务端()
- 建立客户端和服务端
- 建立连接后,通过socket中的IO流进行数据的传输
关闭socket
2、方法:
1)创建客户端对象: Socket(String host,int port),指定要接收的IP地址和端口号
2)创建服务端对象:ServerSocket(int port):指定接收的客户端的端口
3)Socket accept():侦听并接受到此套接字的连接,服务器用于接收客户端socket对象的方法
注:服务器没有socket流,也就没有读写操作的流,服务器是通过获取到客户端的socket流然后获取到其中的读写方法,对数据进行操作的,也正是因为这样服务器与客户端的数据操作才不会错乱
4)void shutdownInput():此套接字的输入流至于“流的末尾”
5)void shutdownOutput():禁用此套接字的输出流
6)InputStream getInputStream():返回此套接字的输入流
7)OutputStream getOutputStream():返回套接字的输出流
实例:- 建立服务端的socket服务,ServerSocket,并监听一个端口
- 获取连接过来的客服端对象,通过ServerSokcet的 accept方法。没有连接就会等,所以这个方法阻塞式的。
- 客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发过来的数据。
- 关闭服务端(可选)
-
publicclassTCPServerDemo{publicstaticvoid main(String[] args)throwsIOException{// 建立服务端的socket服务,并监听一个端口ServerSocket ss =newServerSocket(10003);// 通过accept方法获取连接过来的客户端对象Socket socket = ss.accept();String ip = socket.getInetAddress().getHostAddress();System.out.println(ip +"......connected");// 获取客户端发过来的数据,那么要使用客户端对象的读取流来获取数据InputStream in = socket.getInputStream();byte[] buf =newbyte[1024];int len = in.read(buf);System.out.println(newString(buf,0, len));socket.close();// 关闭客户端ss.close();// 关闭服务端,可选的操作}}
- 客户端,在建立的时候就可以去连接指定的主机
- 因为tcp是面向连接的,所以在建立socket时就需要有服务端存在
- 并连接成功,形成通路,才能在该通道上传输数据
-
publicclassTCPClientDemo{publicstaticvoid main(String[] args)throwsException{//1、创建客户端的socket服务,指定目的主机和端口Socket socket =newSocket("192.168.229.1",10003);//2、获取socket的输出流,用于发送数据OutputStream out = socket.getOutputStream();//3、发送数据out.write("tcp client send message come on".getBytes());socket.close();}}
-
/* * 需求:建立一个文本转换服务器。 * 客户端给服务端发送文本,服务单会将文本转成大写在返回给客户端。 * 而且客户度可以不断的进行文本转换。当客户端输入over时,转换结束。 * * 分析: * 客户端 * 既然是操作设备上的数据,那就可以使用IO技术,并按照io操作规律来思考 * 源:键盘录入 * 目的:网络设备,网络输出流 * 而且操作的是文本数据,可以用字符流 * * 步骤: * 1、建立服务 * 2、获取键盘输入 * 3、将数据发给客户端 * 4、获取服务端返回的数据 * 5、结束,关闭资源 */ class TransClient { public static void main(String[] args) throws UnknownHostException, IOException { Socket s = new Socket(InetAddress.getLocalHost(), 10005); BufferedReader bfrb = new BufferedReader(new InputStreamReader( System.in)); // 定义目的,将数据写入socket输出流,发给服务器 OutputStream out = s.getOutputStream(); OutputStreamWriter ow = new OutputStreamWriter(out); BufferedWriter bfw = new BufferedWriter(ow); // 定义读取流 InputStream in = s.getInputStream(); InputStreamReader is = new InputStreamReader(in); BufferedReader bfr = new BufferedReader(is); String line = null; while ((line = bfrb.readLine()) != null) { bfw.write(line); bfw.newLine(); bfw.flush(); // 读取数据 String data = bfr.readLine(); System.out.println(data); } bfrb.close(); s.close(); } } /* * 服务端: 源:socket读取流 目的:socket输出流。 */ class TransServer { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(10005); Socket s = ss.accept(); String ip = s.getInetAddress().getHostAddress(); System.out.println(ip + "....connected"); InputStream in = s.getInputStream(); InputStreamReader isr = new InputStreamReader(in); BufferedReader bfr = new BufferedReader(isr); // 目的。socket输出流。将大写数据写入到socket输出流,并发送给客户端。 BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter( s.getOutputStream())); String line = null; while((line=bfr.readLine()) != null){ bufOut.write(line.toUpperCase()); bufOut.newLine(); bufOut.flush(); } s.close(); ss.close(); } } public class TCPTransText{ public static void main(String[] args) { new TransClient(); new TransServer(); } }
1、客户端:
源:硬盘上的文件;目的:网络设备,即网络输出流。若操作的是文本数据,可选字符流,并加入高效缓冲区。若是媒体文件,用字节流。
2、服务端:
源:socket读取流;目的:socket输出流。
3、出现的问题:现象:
a.文件已经上传成功了,但是没有得到服务端的反馈信息。
b.即使得到反馈信息,但得到的是null,而不是“上传成功”的信息
原因:
a.因为客户端将数据发送完毕后,服务端仍然在等待这读取数据,并没有收到结束标记,就会一直等待读取。
b.上个问题解决后,收到的不是指定信息而是null,是因为服务端写入数据后,也需要刷新,才能将信息反馈给客服端。
解决:
a.方法一:定义结束标记,先将结束标记发送给服务端,让服务端接收到结束标记,然后再发送上传的数据。但是这样定义可能会发生定义的标记和文件中的数据重复而导致提前结束。
方法二:定义时间戳,由于时间是唯一的,在发送数据前,先获取时间,发送完后在结尾处写上相同的时间戳,在服务端,接收数据前先接收一个时间戳,然后在循环中判断时间戳以结束标记。
方法三:通过socket方法中的shutdownOutput(),关闭输入流资源,从而结束传输流,以给定结束标记。这里主要用这个方法。
-
/* * 客户端 */ class TextClient { public static void main(String[] args) throws UnknownHostException, IOException { Socket s = new Socket(InetAddress.getLocalHost(), 10007); File file = new File("AwtDemo.java"); BufferedReader bfr = new BufferedReader(new FileReader(file)); // 将数据写入到socket流中 PrintWriter out = new PrintWriter(s.getOutputStream(), true); String line = null; while ((line = bfr.readLine()) != null) { out.println(line); } // 关闭客户端的输出流。相当于给流中加入一个结束标记-1. // 结束标记很重要,不然TCP中程序停不下来 s.shutdownOutput(); // 接收服务端发过来的数据 BufferedReader bfrs = new BufferedReader(new InputStreamReader( s.getInputStream())); String data = bfrs.readLine(); System.out.println(data); bfr.close(); s.close(); } } class TextServer { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(10007); Socket s = ss.accept();// 接收客户端的socket流 String ip = s.getInetAddress().getHostAddress(); System.out.println(ip + "....connected"); //建立读取客户端数据的流 BufferedReader bfr = new BufferedReader(new InputStreamReader( s.getInputStream())); //建立文件,关联流 PrintWriter out = new PrintWriter(new FileWriter("server.java"),true); String line = null; while((line=bfr.readLine()) != null){ out.println(line); } //建立给客户端回馈机制 PrintWriter outToC = new PrintWriter(s.getOutputStream(),true); outToC.println("上传成功"); out.close(); s.close(); ss.close(); } }
第四 实际应用
一、TCP并发执行请求
一)图片上传:
第一、客户端:
1、创建服务端点
2、读取客户端以后图片数据
3、通过Socket输出流将数据发给服务端
4、读取服务端反馈信息
5、关闭客户端
第二、服务端
对于客户端并发上传图片,服务端如果单纯的使用while(true)循环式有局限性的,当A客户端连接上以后,被服务端获取到,服务端执行具体的流程,这时B客户端连接就只有等待了,因为服务端还未处理完A客户端的请求,还有循环来回执行下须accept方法,所以暂时获取不到B客户端对象,那么为了可让多个客户端同时并发访问服务端,那么服务端最好就是将每个客户端封装到一个单独的线程,这样就可以同时处理多个客户端的请求。如何定义线程呢?只要明确每个客户端要在服务端执行的代码即可,将改代码存入到run方法中。
-
/* * 客户端 */ class PicCilent2 { public static void main(String[] args) throws UnknownHostException, IOException { File file = new File(args[0]); Socket s = new Socket("192.168.229.1", 10010); // 读取文件数据 BufferedInputStream bfr = new BufferedInputStream(new FileInputStream( file)); // 将数据写到socket流中,传递给服务端 BufferedOutputStream out = new BufferedOutputStream(s.getOutputStream()); byte[] buf = new byte[1024]; int len = 0; while ((len = bfr.read(buf)) != -1) { out.write(buf, 0, len); } s.shutdownInput();// 结束标记 // 接收服务端反馈数据 InputStream in = s.getInputStream(); byte[] bufin = new byte[1024]; int num = in.read(bufin); System.out.println(new String(bufin, 0, num)); bfr.close(); s.close(); } } /* * * 服务端: */ class PicThread implements Runnable { private Socket s; PicThread(Socket s) { this.s = s; } @Override public void run() { int count = 1; String ip = s.getInetAddress().getHostAddress(); try { System.out.println(ip + "....connected"); InputStream in = s.getInputStream(); File dir = new File("c:\\"); File file = new File(dir, ip + "(" + (count) + ")" + ".jpg"); while (file.exists()) file = new File(dir, ip + "(" + (count++) + ")" + ".jpg"); FileOutputStream fos = new FileOutputStream(file); byte[] buf = new byte[1024]; int len = 0; while ((len = in.read(buf)) != -1) { fos.write(buf, 0, len); } OutputStream out = s.getOutputStream(); out.write("上传成功".getBytes()); fos.close(); s.close(); } catch (Exception e) { throw new RuntimeException(ip + "上传失败"); } } } class Picserver2 { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(10011); while (true) { Socket s = ss.accept(); new Thread(new PicThread(s)).start(); } } }
1、浏览器
在浏览器地址栏输入你的IP和你的服务器的端口号,这就可以成功通过浏览器访问自己的服务器了。
2、telnet
telnet是windows提供的远程登录工具,可以连接服务器的任意一台主机,并在通过dos命令行配置服务器 访问方式:IP 端口号--> 192.168.229.1 端口号
2、 客户端:浏览器。
服务端:Tomcat服务器。启动tomcat服务器,自己写点数据用于服务器返回给客户端的数据,这里是html文件,自己写一个简单的html文件放在tomcat的webapps目录下就可以
3、客户端:自定义。(图形界面)
服务端:Tomcat服务器。第五 原理
publicclassMyTomcat{
/**
* @param args
* @throws IOException
*/
publicstaticvoid main(String[] args)throwsIOException{
ServerSocket ss =newServerSocket(9090);
Socket s = ss.accept();
System.out.println(s.getInetAddress().getHostAddress()+".....connected");
InputStream in = s.getInputStream();
byte[] buf =newbyte[1024];
int len = in.read(buf);
String text =newString(buf,0,len);
System.out.println(text);
//给客户端一个反馈信息。
PrintWriter out =newPrintWriter(s.getOutputStream(),true);
out.println("<font color='red' size='7'>欢迎光临</font>");
s.close();
ss.close();
}
}
publicclassMyBrowser{
/**
* @param args
* @throws IOException
* @throws UnknownHostException
*/
publicstaticvoid main(String[] args)throwsUnknownHostException,IOException{
Socket s =newSocket("192.168.1.100",8080);
//模拟浏览器,给tomcat服务端发送符合http协议的请求消息。
PrintWriter out =newPrintWriter(s.getOutputStream(),true);
out.println("GET /myweb/1.html HTTP/1.1");
out.println("Accept: */*");
out.println("Host: 192.168.1.100:8080");
out.println("Connection: close");
out.println();
out.println();
InputStream in = s.getInputStream();
byte[] buf =newbyte[1024];
int len = in.read(buf);
String str =newString(buf,0,len);
System.out.println(str);
s.close();
//http://192.168.1.100:8080/myweb/1.html
}
}
publicclassURLDemo{
/**
* @param args
* @throws IOException
*/
publicstaticvoid main(String[] args)throwsIOException{
String str_url ="http://192.168.1.100:8080/myweb/1.html";
URL url =new URL(str_url);
// System.out.println("getProtocol:"+url.getProtocol());
// System.out.println("getHost:"+url.getHost());
// System.out.println("getPort:"+url.getPort());
// System.out.println("getFile:"+url.getFile());
// System.out.println("getPath:"+url.getPath());
// System.out.println("getQuery:"+url.getQuery());
//这一部简化了下面的俩步
// InputStream in = url.openStream();
//获取url对象的Url连接器对象。将连接封装成了对象:java中内置的可以解析的具体协议的对象+socket.
URLConnection conn = url.openConnection();
// String value = conn.getHeaderField("Content-Type");
// System.out.println(value);
// System.out.println(conn);
//sun.net.www.protocol.http.HttpURLConnection:http://192.168.1.100:8080/myweb/1.html
InputStream in = conn.getInputStream();
byte[] buf =newbyte[1024];
int len = in.read(buf);
String text =newString(buf,0,len);
System.out.println(text);
in.close();
}
}
- C/S client/server
- B/S browser/server
小知识点
1、InetSocketAddress对象(IP+端口)
2、ServerSocket对象中的构造函数:
ServerSocket(int port,int backlog),其中的backlog表示队列的最大长度,即最多连入客户端的个数,即最大连接数。
3、在进行浏览器输入网址访问一台主机所做的操作:
如http://192.168.229.1:8080/myweb/demo.html,一般直接输入主机名:http://baidu.com等,那么如何通过主机名获取IP地址,从而连接到这台主机的呢?这就需要将主机名翻译成IP地址,即域名解析:DNS(存的是主机名和IP相对应的键值对)
在进行访问的时候,会现在本地的hosts文件(C:\WINDOWS\system32\drivers\etc\hosts)中找对应的映射,若有,则直接返回请求,若无,则到公网的映射列表即DNS中找对应的映射,找到后,将主机名对应的IP地址返回给本机,本机通过这个IP地址找到对应的服务器。
域名解析示意图:
host应用:
1、可屏蔽一些恶意网址,即将对应的映射关系写入hosts中,将IP地址改为本机的回环地址,那么会直接找到hosts,就不会将请求发送出去了。
2、不让软件更新,可以越过一些收费软件的限制。
3、把经常上的网站写入文件中,提高访问速率。