------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
自学Java第十二天笔记。本人是根据毕向东老师的JavaSE视频学习的。
Java网络编程
一.理解。
1.网络通信步骤。1)、找到对方IP。
2)、数据要发送到对方指定的应用程序上。为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识。为了方便称呼这个数字,叫做端口。逻辑端口。
3)、定义通信规则。这个通信规则称为协议。国际组织定义了通用协议TCP/IP。
2.网络模型。
1)、网络模型:OSI参考模型和TCP/IP参考模型。
OSI参考模型:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层。
TCP/IP参考模型:应用层,传输层,网际层,主机至网络层。
2)、一般来说开发处于传输层和网际层,应用层为:FTP和HTTP协议等,传输层为:UDP和TCP等,网际层为:IP。
3.网络通信三要素:IP地址,端口号,传输协议。
1)、IP地址。
它是网络中的设备标识
不易记忆,可用主机名表示。
本机回环地址:127.0.0.1,主机名为:localhost。
演示:
(java中IP地址对应的是InetAddress类,存在于java.net包中)。
(代码 IPDemo.java):
import java.net.*;
class IPDemo
{
public static void main(String[] args) throws Exception
{
//此类无构造函数,可通过getLocalHost()方法获取InetAddress对象,
//此方法是静态的,返回本类对象。
InetAddress i = InetAddress.getLocalHost();
sop(i.toString());//打印主机名和IP。
sop("address:"+i.getHostAddress());//打印ip地址。
sop("name:"+i.getHostName());//打印主机名。
//获取指定主机的ip信息。
//最好用ip地址去获取,主机名需要解析。
InetAddress ia = InetAddress.getByName("169.254.133.0");
sop("address:"+i.getHostAddress());
sop("name:"+i.getHostName());
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
2)、端口号。
用于标识进程的逻辑地址,不同进程的标识。(只是数字标识,没有对应的类)
有效端口:0~65535,其中0~1024系统使用或保留端口。
3)、传输协议。
即通信规则。常见协议:TCP,UDP。
UDP特点:(例如QQ聊天应用程序)
面向无连接,即将数据及源和目的封装成数据包中,不需要建立连接。
每个数据包的大小限制在64K之内。
因无连接,是不可靠的协议。
不建立连接,速度快。
TCP特点:(例如下载)
面向连接,建立连接,形成传输数据的通道。
在连接中进行大数据量的传输。
通过三次握手完成连接,是可靠协议。
必须建立连接,效率会稍低。
(三次握手:第一次本方发送请求,第二次对方确认连接,第三次本方再次确认连接成功。)
二.传输协议。
1.Socket:套接字,通信的端点。
Socket就是为网络服务提供的一种机制。
通信的两端都有Socket。
网络通信其实就是Socket间的通信。
数据在两个Socket间通过IO传输。
2.UDP传输:
1)、通过类DatagramSocket,此类表示用发送和接收数据包的套接字,即Socket。
2)、发送与接收数据步骤:
(代码 UdpDemo.java):
/*
需求:通过UDP传输方式,将一段文字数据发送出去。
UDP发送端:
思路:
1,建立UDPSocket服务。
2,提供数据,并将数据封装到数据包中。
3,通过Socket服务的发送功能,将数据包发出去。
4,关闭资源。
*/
import java.net.*;
class UdpSend
{
public static void main(String[] args) throws Exception
{
//1,创建UDP服务,通过DatagramSocket对象。
DatagramSocket ds = new DatagramSocket();
//2,确定数据,并封装成数据包。
byte[] buf = "udp lai le".getBytes();//将字符串转成字节数组。
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("169.254.133.0"),10000);
//3,通过Socket服务,将已有的数据包发送出去。通过send方法。
ds.send(dp);
//4,关闭资源。
ds.close();
}
}
/*
需求:定义一个应用程序,用于接收数据并处理的。
UDP接收端:
思路:
1,定义UDPSocket服务。通常会监听一个端口,
其实就是给这个接收网络应用程序定义数字标识,方便于明确哪些数据过来该应用程序可以处理。
2,定义一个数据包,因为要存储接收到的字节数据。
因为数据包对象中有更多功能可以提取字节数据中的不同数据信息。
3,通过Socket服务的receive方法将接收到的数据存入已定义好的数据包中。
4,通过数据包对象的特有功能,将这些不同的数据取出,打印在控制台上。
5,关闭资源。
*/
class UdpReceive
{
public static void main(String[] args) throws Exception
{
//1,创建UDPSocket服务,建立端点。
DatagramSocket ds = new DatagramSocket(10000);
while(true)
{
//2,定义数据包,用于存储数据。
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//3,通过Socket服务的receive方法将数据存到数据包中。
ds.receive(dp);//阻塞式方法
//4,通过数据包的方法获取其中的数据。
String ip = dp.getAddress().getHostName();
String data = new String(dp.getData(),0,dp.getLength());
int port = dp.getPort();//接收到发送端的端口号
System.out.println(ip+"::"+data+"::"+port);
}
//5,关闭资源。
//ds.close();
}
}
/*
注意:
1、发送端与接收端是两个独立的运行程序。
2、在发送端,要在数据包对象中明确目的地IP及端口。
3、在接收端,要指定监听的端口。
*/
3)、练习:
(代码 UdpDemo2.java):
//练习:用键盘录入的方式,来发送数据.
import java.io.*;
import java.net.*;
class UdpSend2
{
public static void main(String[] args) throws Exception
{
DatagramSocket ds = new DatagramSocket();
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line=bufr.readLine())!=null)
{
if("886".equals(line))
break;
byte[] buf = line.getBytes();
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("169.254.133.0"),10001);
ds.send(dp);
}
ds.close();
}
}
class UdpReceive2
{
public static void main(String[] args) throws Exception
{
DatagramSocket ds = new DatagramSocket(10001);
while(true)
{
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
ds.receive(dp);
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(),0,dp.getLength());
System.out.println(ip+":::"+data);
}
}
}
(代码 UdpDemo3.java):
/*
练习:编写一个聊天程序。
有收数据的部分,和发数据的部分。这两部分需要同时执行。
那就需要用到多线程技术。一个线程控制收,一个线程控制发。
因为收和发动作是不一致的,所以要定义两个run方法。
而且这个两个方法要封装到不同的类中。
*/
import java.io.*;
import java.net.*;
class Send implements Runnable
{
private DatagramSocket ds;
Send(DatagramSocket ds)
{
this.ds = ds;
}
public void run()
{
try
{
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line=bufr.readLine())!=null)
{
if("886".equals(line))
break;
byte[] buf = line.getBytes();
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.255"),10002);
ds.send(dp);
}
ds.close();
}
catch (Exception e)
{
throw new RuntimeException("发送数据失败");
}
}
}
class Receive implements Runnable
{
private DatagramSocket ds;
Receive(DatagramSocket ds)
{
this.ds = ds;
}
public void run()
{
try
{
while(true)
{
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
ds.receive(dp);
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(),0,dp.getLength());
System.out.println(ip+"::"+data);
}
}
catch (Exception e)
{
throw new RuntimeException("接收数据失败");
}
}
}
class UdpDemo3
{
public static void main(String[] args) throws Exception
{
DatagramSocket ds = new DatagramSocket();
DatagramSocket d = new DatagramSocket(10002);
new Thread(new Send(ds)).start();
new Thread(new Receive(d)).start();
}
}
3.TCP传输:
1)、TCP分客户端和服务端。客户端对应的对象是Socket,服务端对应的对象是ServerSocket。
两个端点的建立连接后会有一个传输数据的通道,这通道称为流,而且是建立在网络基础上的流,称之为socket流。该流中既有读取,也有写入。
2)、客户端和服务端步骤:
(代码 TcpDemo.java):
/*
需求:客户端给服务端发送数据,服务端收到后,给客户端反馈信息。
TCP客户端:
步骤:
1,建立TCP的Socket服务,最好明确具体的地址和端口。
通路一建立,就会产生Socket流(包括输入流和输出流),通过方法获取。
2,为了发送数据,应获取Socket中的输出流,如果要接收服务端的反馈信息,还需要获取Socket的输入流。
3,通过输出流的write()方法将要发送的数据写入到流中。
4,关闭Socket流资源。
*/
import java.net.*;
import java.io.*;
class TcpClient
{
public static void main(String[] args) throws Exception
{
Socket s = new Socket("169.254.133.0",10003);
OutputStream out = s.getOutputStream();
out.write("tcp lai le".getBytes());
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int num = in.read(buf);
System.out.println(new String(buf,0,num));
s.close();
}
}
/*
TCP服务端:
步骤:
1,创建服务端Socket服务,并监听一个端口。
2,获取连接过来的客户对象,通过ServerSocket的accept()方法,此方法是阻塞式的,如果服务端没有连接到就会一直等待。
3,可以通过获取到的socket对象中的socket流和具体的客户端进行通讯。
客户端如果发过来数据,则服务端要使用对应的客户端对象,并获取到该客户端对象的读取流读取发过来的数据,并输出到指定目的地。
4,如果通讯结束,关闭资源。注意:要先关客户端,再关服务端。
*/
class TcpServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10003);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"....");
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int num = in.read(buf);
System.out.println(new String(buf,0,num));
OutputStream out = s.getOutputStream();
out.write("你也好".getBytes());
s.close();
ss.close();
}
}
3)、练习:
(代码 TcpDemo2.java):
/*
需求:建立一个文本转换服务器。
客户端给服务端发送文本,服务端会将文本转成大写再返回给客户端。
而且客户端可以不断的进行文本转换。当客户端输入over时,转换结束。
*/
import java.io.*;
import java.net.*;
class TcpClient2
{
public static void main(String[] args) throws Exception
{
Socket s = new Socket("169.254.133.0",10004);
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
out.println(line);
String data = in.readLine();
System.out.println(data);
}
s.close();
bufr.close();
}
}
class TcpServer2
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10004);
Socket s = ss.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
String line = null;
while((line=in.readLine())!=null)
{
System.out.println(line);
out.println(line.toUpperCase());
}
s.close();
ss.close();
}
}
/*
此练习出现的问题:
现象:客户端和服务端都在莫名的等待。
原因:因为客户端和服务端都有阻塞式方法。这些方法没有读到结束标记。那么就一直等。而导致两端都在等待。
解决:需要用到刷新和换行的方式将写入和读取的数据从流中刷新到内存中。
*/
(代码 TcpDemo3.java):
//需求:向服务器上传一个文件,服务返回一条信息。
import java.io.*;
import java.net.*;
class TcpClient3
{
public static void main(String[] args) throws Exception
{
Socket s = new Socket("169.254.133.0",10005);
BufferedReader bufr = new BufferedReader(new FileReader("IPDemo.java"));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while((line=bufr.readLine())!=null)
{
out.println(line);
}
s.shutdownOutput();//关闭客户端的输出流。相当于给流中加入一个结束标记-1.
String data = in.readLine();
System.out.println(data);
s.close();
bufr.close();
}
}
class TcpServer3
{
public static void main(String[] args) throws Exception
{
ServerSocket ss =new ServerSocket(10005);
Socket s = ss.accept();
BufferedWriter bufw = new BufferedWriter(new FileWriter("IPDemo.txt"));
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
String line = null;
while((line=in.readLine())!=null)
{
bufw.write(line);
bufw.newLine();
bufw.flush();
}
out.println("已收到");
s.close();
bufw.close();
ss.close();
}
}
4) 、用TCP客户端并发上传图片:
单线程的服务端有个局限性。当A客户端连接上以后,被服务端获取到。服务端执行具体流程。这时B客户端连接,只能等待。因为服务端还没有处理完A客户端的请求。还没有循环回来执行下一次accept方法。所以,暂时获取不到B客户端对象。
那么为了可以让多个客户端同时并发访问服务端。服务端最好就是将每个客户端封装到一个单独的线程中,这样,就可以同时处理多个客户端请求。
如何定义线程呢?只要明确了每一个客户端要在服务端执行的代码,将该代码存入run方法即可。
(代码 TcpDemo4.java):
//需求:并发上传图片。
import java.io.*;
import java.net.*;
class TcpClient4
{
public static void main(String[] args) throws Exception
{
Socket s = new Socket("169.254.133.0",10006);
FileInputStream fis = new FileInputStream("1.JPG");
OutputStream out = s.getOutputStream();
byte[] buf = new byte[1024];
int num = 0;
while((num=fis.read(buf))!=-1)
{
out.write(buf,0,num);
}
s.shutdownOutput();
InputStream in = s.getInputStream();
byte[] bufIn = new byte[1024];
int numIn = in.read(bufIn);
System.out.println(new String(bufIn,0,numIn));
s.close();
fis.close();
}
}
class TcpServer4
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10006);
while(true)
{
Socket s = ss.accept();
new Thread(new PicThread(s)).start();
}
}
}
class PicThread implements Runnable
{
private Socket s;
PicThread(Socket s)
{
this.s=s;
}
public void run()
{
int count = 1;
String ip = s.getInetAddress().getHostAddress();
try
{
System.out.println(ip+"....");
InputStream in = s.getInputStream();
File f = new File(ip+"("+count+")"+".JPG");
while(f.exists())
{
f = new File(ip+"("+(count++)+")"+".JPG");
}
FileOutputStream fos = new FileOutputStream(f);
byte[] buf = new byte[1024];
int num = 0;
while((num=in.read(buf))!=-1)
{
fos.write(buf,0,num);
}
OutputStream out = s.getOutputStream();
out.write("上传成功".getBytes());
s.close();
fos.close();
}
catch (Exception e)
{
throw new RuntimeException(ip+"上传失败");
}
}
}
5) 、客户端并发登陆:
(代码 TcpDemo5.java):
/*
需求:客户端并发登陆:
客户端通过键盘录入用户名,服务端对这个用户名进行校验。
如果该用户存在,在服务端显示xxx,已登陆;并在客户端显示xxx,欢迎光临。
如果用户不存在,在服务端显示xxx,尝试登陆;并在客户端显示xxx,该用户不存在。
最多就登录三次。
*/
import java.io.*;
import java.net.*;
class TcpClient5
{
public static void main(String[] args) throws Exception
{
Socket s = new Socket("169.254.133.0",10007);
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
for(int x=0;x<3;x++)
{
String name = bufr.readLine();
if(name==null)
break;
out.println(name);
String data = bufIn.readLine();
System.out.println(data);
if(data.contains("欢迎"))
break;
}
s.close();
bufr.close();
}
}
class TcpServer5
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10007);
while (true)
{
Socket s = ss.accept();
new Thread(new UserThread(s)).start();
}
}
}
class UserThread implements Runnable
{
private Socket s;
UserThread(Socket s)
{
this.s=s;
}
public void run()
{
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+".....connected");
try
{
for(int x=0;x<3;x++)
{
BufferedReader bufrIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String name = bufrIn.readLine();
if(name==null)
break;
BufferedReader bufr = new BufferedReader(new FileReader("user.txt"));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
boolean flag = false;
String line = null;
while((line=bufr.readLine())!=null)
{
if(name.equals(line))
{
flag = true;
break;
}
}
if(flag)
{
System.out.println(name+":已登录");
out.println(name+":欢迎光临");
}
else
{
System.out.println(name+":尝试登陆");
out.println(name+":该用户不存在");
}
}
s.close();
}
catch (Exception e)
{
throw new RuntimeException("校验失败");
}
}
}