------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
网络编程
说到网络编程,我们可以理解为从一开始的单机操作变成多机操作,在本机内的数据传输,已经在我们调用的代码内部封装好了如何传输、传输的过程,比如使用File类操作文件,使用IO流对数据的读取和写入。但是网络编程和本地不同,根据面对对象的思想:计算机本地操作,我们可以把数据的传输当成计算机内部封装,内部调用。不同的计算机的连接靠的是网线,接口等,我们就可以通过这样的思想来理解java的net包的设计思想。
一、网络参考模型:
OSI的描述:
1.物理层:物理设备,网线的接口类型,光纤的接口类型等。作用:传输比特流。
2.数据链路层:将物理层接收的数据进行mac地址的封装和解封装。常把这一层的数据叫做帧。设备对应是交换机,数据时通过交换机来传输的。
3.网络层:主要将下层接收到的数据进行IP地址的封装和解封装。设备对应为路由器,这一层数据叫做数据包。
4.传输层:定义了一些传输数据的协议和端口号,TCP、UDP等协议。
5.会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通道。主要在你的系统之间发起会话或者接收会话请求。
6.表示层:主要是进行对接收的数据的解释,加密与解密、压缩与解压缩等(也就是把计算机能够是别的东西换成人能识别的东西)。
7.应用层:主要是一些终端的应用,比如说FTP服务、WEB、QQ等。
注:计算机(计算机安装的应用程序)之间的通信,就是将数据经过上述的层层封装,发送出去,接收端在层层解封,读取出数据。
二、网络通讯的几个关键的词:
1. ip地址:InetAddress(网络设备的标示)。
本地回环地址:127.0.0.1,使用主机名:localhost(没有互联网的情况下,可以使用这个地址进行本机的访问,模仿互联网的访问)
注:因为ipv4已经不够大家分的了,所以又有了ipv6.
InetAddress:表示IP地址的类(无构造方法)。
常用方法:
|-byte[]getAddress();//返回该InetAddress对象的原始IP地址
|--InetAddressgetByAddress(byte[] addr);//在给定原始IP地址的情况下,返回InetAddress对象
|--InetAddressgetByName(String host);//给定主机名的情况下确认主机的ip地址。
|--InetAddressgetLocalHost();//返回本地主机
示例:
import java.net.*;
class IPDemo
{
public static void main(String[] args) throws Exception
{
sop("-------本地主机名---------");
InetAddress ip = InetAddress.getLocalHost();
sop(ip.getHostAddress());
sop(ip.getHostName());
sop("--------其他主机-----------");
InetAddress ip1 = InetAddress.getByName("www.baidu.com");
sop(ip1.getHostAddress());
sop(ip1.getHostName());
}
public static void sop(Object obj){
System.out.println(obj);
}
}
result:
2. 端口号:用于标示进程(应用程序)的逻辑地址,不同进程的标识。
假如我们把ip地址当成xx路xx大厦,那么端口号就是具体的房间名,比如五楼的第三个房间。
有效的端口号:0—65535,其中0—1024是系统使用或保留的端口。
注:防火墙,就是将发送到某个程序端口的数据屏蔽以及将从该程序端口发出的数据也屏蔽。
3. 传输协议:
常见的传输协议:UDP、TCP
UDP: 将数据机源目标封装在数据包中,不需要建立连接。
每个数据报包的大小限制在64k内。
优点:无需建立连接,速度快。
缺点:无连接,不可靠。
生活场景对应:对讲机,某一个对讲机发出的信号,不管对方有没有开对讲机,都会发送,不管有没有接收到。使用场景:QQ、在线视频等。
TCP: 建立连接,形成传输数据的通道。
在连接中进行大数据量传输。
优点:要通过三次的握手完成连接,是可靠的连接。
缺点:必须建立连接才能传输数据,效率会较低。
生活场景对应:打电话,必须对方接听才能进行通话,无论哪一方断开,通话结束。使用场景:迅雷下载。
2. Socket:网络服务提供的一种机制(并不是唯一的)。
通信的两端都要有Socket。
网络通信就是Socket通信。
通信的数据在两个Socket的IO流中传输。
Socket:代表Socket的类
常用构造方法:
|--Socket();
|--Socket(InetAddressaddress,int port);//创建一个流套接字并将其连接到指定的IP地址的指定端口号
|--Socket(InetAddressaddress,int port,InetAddress localAddr,int localPort);//创建一个套接字并将其连接到指定的远程端口上的指定远程地址
|--Socket(Stringhost,int port);//创建一个流套接字并将其连接到指定主机上的指定端口号
|--Socket(Stringhost,int port,InetAddress locakAddr,int localPort);//创建一个套接字并将其连接到指定的远程主机的指定远程端口
常用方法:
|--voidbind(SocketAddress bindpoint);//将套接字绑定到本地地址
|--booleanisBound();//返回套接字的绑定状态
|--voidclose();//关闭套接字
|--voidconnect(SocketAddress endpoint);//将此套接字连接到服务器
|--InetAddressgetInetAddress();//返回套接字连接的地址
|--InputStreamgetInputStream();//返回此套接字的输入流
|--OutputStreamgetOutputStream();//返回此套接字的输出流
|--booleanisConnected();//返回套接字的连接状态
|--InetAddressgetLocalAddress()//返回套接字绑定的本地地址
|--intgetLocalPort();//返回套接字绑定的本地端口
|--intgetPort();//返回此套接字连接到的远程端口
详细使用在后续章节中给出。
小知识点:
域名解析的过程:
根据以上的访问过程,我们可以在Host文件中添加指定网站的域名到127.0.0.1上来屏蔽掉游戏网站内容弹出。
三、1.UDP协议的实现:
UDP的实现主要依靠两个类:DatagramSocket(用来发送和接收数据报包的套接字)与DatagramPacket(数据报包)。
简单示例:
import java.net.*;
/*
1.建立udp的socket传输服务
2.建立数据报包,将要传输的数据封装进数据报包中
3.通过udp的socket传输服务的send方法把数据报包发送出去
4.关闭udp的socket传输服务
*/
class UDPSend
{
public static void main(String[] args) throws Exception
//1.建立UDP的socket传输服务
{ //这个端口可写可不写,不写的话系统会自动寻找一个。
DatagramSocket ds = new DatagramSocket(10000);
//2.建立数据报包,将要传输的数据封装进数据报包中
//要传输的数据
byte[] content = "wo shi yao chuan shu de shu ju!".getBytes();
DatagramPacket dp = new DatagramPacket(content,0,content.length,
InetAddress.getByName("127.0.0.1"),10001);
//3.通过udp的socket传输服务的send方法把数据报包发送出去
ds.send(dp);
//4.关闭udp的socket传输服务
ds.close();
}
}
接收端:
import java.net.*;
/*
1.创建udp的Socket服务监听接收数据的端口
2.创建接收数据的数据报包
3.使用udp的socket服务的receive方法接收数据,并封装在数据报包中
4.解包
5.关闭socket
*/
class UDPReceive
{
public static void main(String[] args) throws Exception
{
//1.创建udp的Socket服务监听接收数据的端口
DatagramSocket ds = new DatagramSocket (10001);
//2.创建接收数据的数据报包
byte[] content = new byte[1024];
DatagramPacket dp = new DatagramPacket(content,0,content.length);
//3.使用udp的socket服务的receive方法接收数据,并封装在数据报包中
ds.receive(dp);
//4.解包
System.out.println(new String(dp.getData(),0,dp.getLength()));
//5.close
ds.close();
}
}
result:
打印出了传输的数据。
依据UDP的聊天室:
1. 双窗口模式(将发送和接收分成两个窗口):
import java.net.*;
import java.io.*;
class UdpSendDemo
{
public static void main(String[] args) throws Exception
{
System.out.println("---------SEND------");
DatagramSocket ds = new DatagramSocket(8888);
BufferedReader readFromKey = new BufferedReader(
new InputStreamReader(System.in));
String line = null;
while((line = readFromKey.readLine())!= null){
byte[] buf = line.getBytes();
DatagramPacket dp = new DatagramPacket(
buf,buf.length,InetAddress.getByName("127.0.0.1"),10000);
ds.send(dp);
//接收到over就退出聊天
if("over".equals(line)){
break;
}
}
ds.close();
<span style="white-space:pre"> </span>}
}
class UdpReceDemo
{
public static void main(String[] args)throws Exception{
<span style="white-space:pre"> </span>System.out.println("---------RECE------");
DatagramSocket ds = new DatagramSocket(10000);
while(true){
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
ds.receive(dp);
String ip = dp.getAddress().getHostAddress();
int port = dp.getPort();
String text = new String(dp.getData(),0,dp.getLength());
System.out.println(ip+":"+port+":"+text);
}
}
}
result:
2. 单窗口模式
import java.net.*;
import java.io.*;
class SingleWinChatRoom
{
public static void main(String[] args) throws Exception
{
DatagramSocket send = new DatagramSocket();
DatagramSocket rece = new DatagramSocket(10000);
new Thread(new UdpSendDemo(send)).start();
new Thread(new UdpReceDemo(rece)).start();
}
}
class UdpSendDemo implements Runnable
{
private DatagramSocket ds ;
public UdpSendDemo(DatagramSocket ds){
this.ds = ds;
}
public void run()
{
System.out.println("---------SEND------");
try
{
BufferedReader readFromKey = new BufferedReader(
new InputStreamReader(System.in));
String line = null;
while((line = readFromKey.readLine())!= null){
byte[] buf = line.getBytes();
DatagramPacket dp = new DatagramPacket(
buf,buf.length,InetAddress.getByName("127.0.0.255"),10000);
ds.send(dp);
if("over".equals(line)){
break;
}
}
ds.close();
}
catch (Exception e )
{
e.printStackTrace();
}
}
}
class UdpReceDemo implements Runnable
{
private DatagramSocket ds ;
public UdpReceDemo(DatagramSocket ds){
this.ds = ds;
}
public void run(){
System.out.println("---------RECE------");
try
{
while(true){
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
ds.receive(dp);
String ip = dp.getAddress().getHostAddress();
int port = dp.getPort();
String text = new String(dp.getData(),0,dp.getLength());
System.out.println(ip+":"+port+":"+text);
if("over".equals(text))
{
<span style="white-space:pre"> </span>break;
}
}
}
catch (Exception e )
{
e.printStackTrace();
}
}
}
result:
四、TCP协议的实现:
通过Socket,Client和Server建立连接,形成可以传输数据的通道(IO流),我们只要获取了Socket中封装的流,就能通过流进行数据的传输了。
1.Socket—>客户端
2.ServerSocketà服务端
|--Socket accept();//获取客户端发过来的Socket对象,建立连接
3.通过Socket中的IO流进行数据传输
|--InputStreamgetInputStream();//获取输入流
|--OutputStreamgetOutputStream();//获取输出流
4.关闭Socket
简单演示:
TCP客户端:import java.net.*;
import java.io.*;
/*
1.客户端需要明确连接的服务器的ip地址以及端口,才能建立连接,连接失败发出异常
2.连接成功,客户端和服务端建立通道,可以通过IO流进行数据传输
3.通讯结束后关闭Socket
*/
class ClientDemo
{
public static void main(String[] args) throws Exception
{
//1.使用Socket创建客服端服务,传入的参数分别为要访问的服务器IP地址和要传输数据的端口
Socket s = new Socket("127.0.0.1",10000);
//2.获取socket流中的输出流
OutputStream os = s.getOutputStream();
//使用输出流将指定的数据写出去
os.write("tcpDemo".getBytes());
//3.关闭资源,不必特地关闭流对象,内部已经封装了关闭方法。
s.close();
}
}
TCP服务端:
/*
1.服务端需要明确处理的数据是从哪个端口进入
2.当客户端访问时,要明确是哪个客户端,可以通过accept()获取以链接的客户端对象,并通过该对象与客户端通过IO流进行数据传输
3.客户端访问结束,关闭该客户端
*/
class ServerDemo
{
public static void main(String[] args)throws Exception{
//1.通过ServerSocket,创建服务器对象,需要监听数据接收的端口
ServerSocket ss = new ServerSocket(10000);
//通过accept方法获取链接过来的客户端对象。
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"已连接");
//通过获取的客户端对象获取流对象。读取客户端发过来的数据
InputStream is = s.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
String text = new String(buf,0,len);
System.out.println(ip+":"+text);
//关闭获取的客户端
s.close();
//这里为演示方便关闭服务端,在生活中,应该一直开着。
ss.close();
}
}
result:
客户端:
服务端:
示例:使用TCP进行简单通讯(客户端发送一句话到服务端,服务端收到返回一句话)
import java.net.*;
import java.io.*;
class ClientDemo1
{
public static void main(String[] args) throws Exception
{
Socket s = new Socket("127.0.0.1",10000);
OutputStream os = s.getOutputStream();
os.write("tcpDemo".getBytes());
//读取服务端返回的数据
InputStream is = s.getInputStream();
byte[] buf = new byte[1024];
//注:read是阻塞式方法,程序会在里被阻塞
int len = is.read(buf);
String text = new String (buf,0,len);
System.out.println(text);
s.close();
}
}
class ServerDemo1
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10000);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"已连接");
InputStream is = s.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
String text = new String(buf,0,len);
System.out.println(ip+":"+text);
//使用服务端中获取的客户端对象的输出流给客户端返回数据
OutputStream os = s.getOutputStream();
os.write("ok!!".getBytes());
s.close();
ss.close();
}
}
result:
客户端:
服务端;
应用:客户端通过DOS命令行输入一串字符串,服务端返回字符串的大写结果。
<pre name="code" class="java">import java.net.*;
import java.io.*;
/*
思路:
客户端:
1.建立socket服务
2.客户端的数据源:键盘-->System.in
3.客户端的目的:socket
4.接收服务端的数据,源:socket
5.打印出数据,目的:控制台
6.操作的是文本数据
*/
class ClientTest
{
public static void main(String[] args) throws Exception
{
//1.建立socket服务
Socket s = new Socket("127.0.0.1",10000);
//2.客户端的数据源:键盘-->System.in
//键盘获取数据
BufferedReader readFromKey = new BufferedReader(new InputStreamReader(
System.in));
//3.客户端的目的:socket
PrintWriter pWForSocket = new PrintWriter(new OutputStreamWriter(
s.getOutputStream()),true);
//OutputStream os = s.getOutputStream();
//4.socket输入流,读取服务端返回的大写数据
BufferedReader readFromSocket = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String line = null;
while((line = readFromKey.readLine())!=null){
if("over".equals(line))
break;
pWForSocket.println(line);
String upperStr = readFromSocket.readLine();
System.out.println(upperStr);
}
s.close();
}
}
/*
1.ServerSocket建立服务
2.获取socket对象
3.获取的数据源:socket
4.目的:控制台
5.将大写的数据返回客户端,目的:socket
*/
class ServerTest
{
public static void main(String[] args) throws Exception
{
//1.ServerSocket建立服务
ServerSocket ss = new ServerSocket(10000);
//2.获取socket对象
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"已连接");
//3.获取的数据源:socket
BufferedReader readFromSocekt = new BufferedReader(new InputStreamReader(
s.getInputStream()));
//InputStream is = s.getInputStream();
//4.socket输出流,目的:socket
PrintWriter pWForSocket = new PrintWriter(new OutputStreamWriter(
s.getOutputStream()),true);
String line = null;
while((line = readFromSocekt.readLine())!= null){
System.out.println(line);
pWForSocket.println(line.toUpperCase());
}
s.close();
ss.close();
}
}
result:
客户端:
服务端:
练习产生的问题
1. 客户端结束,服务端也结束。原因:客户端的socket关闭后,服务端获取的socket读取流也关闭了,因此读不到数据,line = readFromSocket.readLine等于 null,循环结束。
2. PrintWriter操作的是字符流,字符流都要刷新,否则数据不会发送,PrintWriter中封装了println方法,能自动刷新。
3. 使用BufferedReader时,readLine为阻塞式方法,只有遇到回车符(\r\n)才会认为数据读取完毕,所以要加上一个newLine()方法。注:PrintWriter使用的也是 BufferedReader获取数据,所以使用PrintWriter的flush方法刷新数据时要加上换行。
示例:使用TCP上传文件<pre name="code" class="java">import java.net.*;
import java.io.*;
/*
思路:
客户端:
1.建立socket服务
2.客户端的数据源:文件-->FileReader
3.客户端的目的:socket
4.接收服务端的数据,源:socket
5.打印出数据,目的:控制台
6.操作的是文本数据
*/
class ClientTest1
{
public static void main(String[] args) throws Exception
{
Socket s = new Socket("127.0.0.1",10000);
BufferedReader readFromFile = new BufferedReader(new FileReader(new File("1.txt")));
PrintWriter pWForSocket = new PrintWriter(new OutputStreamWriter(
s.getOutputStream()),true);
BufferedReader readFromSocket = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String line = null;
while((line = readFromFile.readLine())!=null){
pWForSocket.println(line);
}
//告诉服务端,客户端写完了
s.shutdownOutput();
//最好不要使用标记,比如“over”,因为文本中可能含有这样的词汇
String str = readFromSocket.readLine();
System.out.println(str);
readFromFile.close();
s.close();
}
}
/*
1.ServerSocket建立服务
2.获取socket对象
3.获取的数据源:socket
4.目的:File -->FileWriter
5.将大写的数据返回客户端,目的:socket
*/
class ServerTest1
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10000);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"已连接");
BufferedReader readFromSocekt = new BufferedReader(new InputStreamReader(
s.getInputStream()));
BufferedWriter writerForFile = new BufferedWriter(new FileWriter(
new File("file.txt")));
String line = null;
while((line = readFromSocekt.readLine())!= null){
//if("over".equals(line))
// break;
writerForFile.write(line);
writerForFile.newLine();
}
PrintWriter pWForSocket = new PrintWriter(s.getOutputStream(),true);
pWForSocket.println("上传成功!");
writerForFile.close();
s.close();
ss.close();
}
}
result:
客户端:
服务端: