一、概述
1、网络参考模型
2、网络通讯要素
◆IP地址:InetAddress
网络中设备的标识
不易记忆,可用主机名
本地回环地址:127.0.0.1主机名:localhost
◆端口号
用于标识进程的逻辑地址,不同进程的标识
有效端口:0~65535,其中0~ 1024系统使用或保留端口。
◆传输协议
通讯的规则
常见协议:TCP, UDP
通讯例子模型:
3、TCP和UDP
◆UDP
将数据及源和目的封装成数据包中,不需要建立连接
每个数据报的大小在限制在64k内
因无连接,是不可靠协议
不需要建立连接,速度快
◆TCP
建立连接,形成传输数据的通道。
在连接中进行大数据量传输
通过三次握手完成连接,是可靠协议(你收到了吗?我收到了。哦,我知道你收到了)
必须建立连接,效率会稍低
.4、Socke
Socket就是为网络服务提供的一种机制。
通信的两端都有Socket。
网络通信其实就是Socket间的通信。
数据在两个Socket间通过io传输。
二、UDP传输
◆DatagramSocket与DatagramPacket
◆建立发送端,接收端。
◆建立数据包。
◆调用Socket的发送接收方法。
◆关闭Socket 。
◆发送端与接收端是两个独立的运行程序。
1、发送端
在发送端要在数据包对象中明确目的地 IP及端口。
DatagramSocket ds=new DatagramSocket();
byte[]by="hello,udp".getBytes();
DatagramPacket dp=new DatagramPacket(by,0,by.length)
InetAddress.getByName(“127.0.0.1”),10000);
ds·send(dp);
ds.close();
2、接收端
在接收端,要指定监听的端口。
DatagramSocket ds=new DatagramSocket(10000);
byte[]by=n ew byte[1024];
Datag ram Packet dp=new DatagramPacket(by,by.length);
ds.receive(dp);
String str=new String(dp.getData(),0,dp.getLength());
System .out.println(str+”——”+dp.g etAdd ress());
ds.close();
下面用一个通讯小程序演示:
发送端:
package internet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UdpSend {
public static void main(String[] args) throws IOException {
// 建立数据借口规则
DatagramSocket sendsocket = new DatagramSocket();
// 创建键录入的读取流
BufferedReader buf = new BufferedReader(
new InputStreamReader(System.in));
String line = null;
while ((line = buf.readLine()) != null) {
// 如果输入over的话就结束
if ("over".equals(line))
break;
// 把得到的字符转换成字节,并且放入字节数组
byte by[] = line.getBytes();
// 把数据打包入数据包对象,出入字节数据,和字节数据长度,和确定主机的 IP 地址。,和接收端的端口号
DatagramPacket sentpacket = new DatagramPacket(by, by.length,
InetAddress.getByName("192.168.0.255"), 7778);
// 开始发送
sendsocket.send(sentpacket);
}
// 关闭流
buf.close();
// 关闭资源
sendsocket.close();
}
}
接收端:
package internet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UdpSend {
public static void main(String[] args) throws IOException {
// 建立数据借口规则
DatagramSocket sendsocket = new DatagramSocket();
// 创建键录入的读取流
BufferedReader buf = new BufferedReader(
new InputStreamReader(System.in));
String line = null;
while ((line = buf.readLine()) != null) {
// 如果输入over的话就结束
if ("over".equals(line))
break;
// 把得到的字符转换成字节,并且放入字节数组
byte by[] = line.getBytes();
// 把数据打包入数据包对象,出入字节数据,和字节数据长度,和确定主机的 IP 地址。,和接收端的端口号
DatagramPacket sentpacket = new DatagramPacket(by, by.length,
InetAddress.getByName("192.168.0.255"), 7778);
// 开始发送
sendsocket.send(sentpacket);
}
// 关闭流
buf.close();
操作结果为:
三、TCP传输
1、TCP分客户端和服务端。客户端对应的对象是Socket,服务端对应的对象是ServerSocket。
2、方法:
◆创建客户端对象:
Socket():创建空参数的客户端对象,一般用于服务端接收数据
Socket(String host,int port),指定要接收的IP地址和端口号
◆创建服务端对象:ServerSocket(int port):指定接收的客户端的端口
◆Socket accept():监听并接收到此套接字的连接
◆void shutdownInput():此套接字的输入流至于“流的末尾”
◆void shutdownOutput():禁用此套接字的输出流
◆InputStream getInputStream():返回此套接字的输入流,Socket对象调用
◆OutputStream getOutputStream():返回套接字的输出流,Socket对象调用
3、基本思路
客户端:
◆客户端需要明确服务器的ip地址以及端口,这样才可以去试着建立连接,如果连接失败,会出现异常。
◆连接成功,说明客户端与服务端建立了通道,那么通过IO流就可以进行数据的传输,而Socket对象已经提供了输入流和输出流对象,通过getInputStream(),getOutputStream()获取即可。
◆与服务端通讯结束后,关闭Socket。
服务端:
◆服务端需要明确它要处理的数据是从哪个端口进入的。
◆当有客户端访问时,要明确是哪个客户端,可通过accept()获取已连接的客户端对象,并通过该对象与客户端通过IO流进行数据传输。
◆当该客户端访问结束,关闭该客户端。
4、步骤
客户端:
通过查阅Socket对象的API文档,发现在该对象在建立时,就可去连接指定主机,因为TCP是面向连接的,所以在建立Socket服务时,就要有服务端存在,并连接成功,形成通路后,再通过该通道进行数据的传输。
◆创建Socket服务,并指定要连接的主机端口。通路一建立,就会产生Socket流(包括输入流和输出流),通过方法获取
◆为了发送数据,应获取Socket中的输出流,如果要接收服务端的反馈信息,还需要获取Socket的输入流
◆通过输出流的write()方法将要发送的数据写入到流中
◆关闭Socket流资源
服务端:
服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。需监听一个端口。
◆建立服务端的Socket服务,并监听一个端口。通过ServerSocet带端口参数的构造函数
◆获取连接过来的客户对象,通过ServerSocket的accept()方法,此方法是阻塞式的,如果服务端没有连接到就会一直等待
◆客户端如果发过来数据,则服务端要使用对应的客户端对象,并获取到该客户端对象的读取流读取发过来的数据,并输出到指定目的地。
◆关闭服务端(可选)。一般服务端是常开的,因为在实际应用中,随时有客户端在请求连接和服务。但这里需要定时关闭客户端对象流,避免某一个客户端长时间占用服务器端。
下面通过一个客服端与服务端连接与反馈来说明:
示例1:需求是客服端给服务端一堆小写字母,服务端返回其大写字母
先建立服务端:
package internet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpTransSever {
public static void main(String[] args) throws IOException {
// 建立服务端接口规则对象,和设定监视端口
ServerSocket ssocket = new ServerSocket(18989);
// 获取到客服端的Socket对象
Socket socket=ssocket.accept();
// 通过Socket对象获得客服端的输入流,并且放入缓冲区
BufferedReader bur = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 建立打印流,开始自动刷流
PrintWriter pw = new PrintWriter(socket.getOutputStream(),true);
String line = null;
// 还是读取
while((line=bur.readLine())!=null)
{
// 先生对方的数据
System.out.println(line);
// 用打印流把对应的大写字母反馈给客服端
pw.println(line.toUpperCase());
}
// 关流
bur.close();
// 关闭资源
ssocket.close();
}
}
再建立客服端:
package internet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class TcpTrans {
public static void main(String[] args) throws IOException {
// 建立规则借口对象,制定ip与端口号
Socket socke = new Socket("192.168.0.101",18989);
// 建立键盘录入
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
// 建立输入流接受服务端的反馈
BufferedReader buf = new BufferedReader(new InputStreamReader(socke.getInputStream()));
// 建议打印流把输入的数据给服务端
PrintWriter pw=new PrintWriter(socke.getOutputStream(),true);
String line = null;
while((line=bf.readLine())!=null)
{
// 当输入over表示请求的服务结束
if(line.equals("over"))
break;
pw.println(line);
// 读取反馈信息
System.out.println(buf.readLine());
}
socke.close();
bf.close();
}
}
结果为:
示例2:
需求是上传图片到服务端,当上传成功后服务端给与客服端反馈信息
建立服务端:
package internet;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ClientUploadHandle {
public static void main(String[] args) throws IOException {
// 建立服务端口
ServerSocket ssocket = new ServerSocket(22346);
// 获取客服端对象
Socket socket = ssocket.accept();
// 通过客服端对象获取客服端输入流
InputStream input = socket.getInputStream();
// 通过客服端对象获取输出流
OutputStream output =socket.getOutputStream();
// 建立输出流
FileOutputStream fos = new FileOutputStream("c://3.jpg");
// 建立缓存容器用来接受客服端的数据
byte by[] =new byte[1024*10];
int num=0;
while((num=input.read(by))!=-1)
{
// 数据加载到输出流
fos.write(by, 0,num);
}
// 数据加载完毕,提示用户
output.write("恭喜你上传成功".getBytes());
// 关闭流,关闭资源
socket.close();
ssocket.close();
fos.close();
}
}
◆建立客服端:
package internet;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class ClientUpload {
public static void main(String[] args) throws UnknownHostException, IOException {
// 建立客服端对象
Socket socket = new Socket("192.168.0.101",22346);
// 把路径文件封装成对象
File file = new File("c://1.jpg");
// 创建输入流
FileInputStream fis = new FileInputStream(file);
// 利用对象获取输出流
OutputStream sOut = socket.getOutputStream();
// 定义缓存容器
byte by[]=new byte[1024*10];
int num=0;
while((num=fis.read(by))!=-1)
{
sOut.write(by,0,num);
}
// 结束输出,不然客服端循环里的read将会一直等待
socket.shutdownOutput();
// 这个容器是接受服务端的反馈
byte buf[]=new byte[1024];
// 建立输入流
InputStream input = socket.getInputStream();
int i =input.read(buf);
// 打印反馈信息
System.out.println(new String(buf,0,i));
// 关闭流
socket.close();
fis.close();
}
}
上传成功,客服端收到了反馈信息:
示例三:
这是一个验证客户端输入的用户名是否注册过,并且给出相应处理的小程序
建立服务端:
package internet;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/*
服务端,判断客服端输入的用户名存不存在,如果有就返回恭喜你登录成功
没有的话,提示从新输入,如果输入次数大于五次就断开服务
这里需要考虑到多个客服端请求的情况
*/
public class TCPLoginServer {
public static void main(String[] args) throws Exception {
// 建立黑名单的对象引用
String Blacklist=null;
// 建立服务端对象,指定窗口
ServerSocket server = new ServerSocket(8848);
// 获得客户端对象
Socket socket = server.accept();
// 判断客户端的ip时候是黑名单中的数据
String ip=socket.getInetAddress().getCanonicalHostName();
// 判断客户端的ip时候是黑名单中的数据,这里先不考虑并发和多个用户等情况,实际情况可用集合或者文本储存
if(Blacklist==ip)
{
socket.close();
}
// 获得客户端输入流
BufferedReader bfr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 获得客户端输出流
PrintWriter pw = new PrintWriter(socket.getOutputStream(),true);
// 建立读取存档的输入流指向
BufferedReader bfr_2 =null;
String line=null;
String line_2=null;
// 建立标记
boolean flag=false;
// 建立计数器
int mark=0;
// 开始获得客服端传来的数据
while((line=bfr.readLine())!=null)
{
// 记录运行次数
mark++;
// 判断运行次数
if(mark>=5)
{
// 反馈客服端
pw.println("您尝试登录的次数已经到了");
// 把此客户端ip加入黑名单
Blacklist=socket.getInetAddress().getCanonicalHostName();
// 关闭连接
socket.close();
// 跳出循环
break;
}
// 把本地数据库加载到输入流
bfr_2 = new BufferedReader(new FileReader("c:\\SQL.txt"));
// 读取数据
while((line_2=bfr_2.readLine())!=null)
{
// 查找数据库里是否有用户输入的用户名
if(line.equals(line_2))
{
// 有的话就反馈客户端登陆成功
pw.println("用户:"+line+"您好!"+"恭喜您已经成功登录了");
// 改掉标记
flag=true;
// 并且跳出循环
break;
}
}
if(!flag)
{
pw.println("没有这个用户,请重新输入");
}
}
// 关闭流
bfr_2.close();
}
}
◆建立客户端
package internet;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/*
客户端:输入自己的用户名登录,输入正确与错误都会有提示。超过五次错误本客户端ip将会被加入黑名单
*/
public class TCPLogin {
public static void main(String[] args) throws Exception, Exception {
// 建立客户端对象
Socket socket = new Socket("192.168.0.101",8848);
// 通过对象获得客户端的输入流
BufferedReader bfr=new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 获得客户端对象的输出流
PrintWriter pw=new PrintWriter(socket.getOutputStream(),true);
// 键盘录入
BufferedReader burd =new BufferedReader( new InputStreamReader(System.in));
String line = null;
// 读取键盘录入的数据
while((line=burd.readLine())!=null)
{
// 数据加载到打印流
pw.println(line);
// 获得服务端反馈
String str=bfr.readLine();
// 打印服务端反馈
System.out.println(str);
// 反馈如果保护”成功“那么登陆成功,跳出循环
if(str.contains("成功"))
break;
}
// 关闭资源
socket.close();
}
}
结果演示
当输入错误时:
当输入正确时;
当数次次数到达时,会被加入黑名单,并且断开连接
5、TCP客户端高并发的解决方案
上述程序演示的全是一对一的传输,但在实际开发中最常见的是客户端并发的访问服务端,下面我将对上面程序的代码做出修改
以满足并发请求的需求
这是示例三的服务端多并发处理方案:
package internet;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/*
服务端,判断客服端输入的用户名存不存在,如果有就返回恭喜你登录成功
没有的话,提示从新输入,如果输入次数大于五次就断开服务
这里需要考虑到多个客服端请求的情况
*/
public class TCPLoginServer {
public static void main(String[] args) throws Exception {
// 建立黑名单的对象引用
String Blacklist=null;
// 建立服务端对象,指定窗口
ServerSocket server = new ServerSocket(8848);
// 获得客户端对象
Socket socket = server.accept();
// 判断客户端的ip时候是黑名单中的数据
String ip=socket.getInetAddress().getCanonicalHostName();
// 判断客户端的ip时候是黑名单中的数据,这里先不考虑并发和多个用户等情况,实际情况可用集合或者文本储存
if(Blacklist==ip)
{
socket.close();
}
// 获得客户端输入流
BufferedReader bfr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 获得客户端输出流
PrintWriter pw = new PrintWriter(socket.getOutputStream(),true);
// 建立读取存档的输入流指向
BufferedReader bfr_2 =null;
String line=null;
String line_2=null;
// 建立标记
boolean flag=false;
// 建立计数器
int mark=0;
// 开始获得客服端传来的数据
while((line=bfr.readLine())!=null)
{
// 记录运行次数
mark++;
// 判断运行次数
if(mark>=5)
{
// 反馈客服端
pw.println("您尝试登录的次数已经到了");
// 把此客户端ip加入黑名单
Blacklist=socket.getInetAddress().getCanonicalHostName();
// 关闭连接
socket.close();
// 跳出循环
break;
}
// 把本地数据库加载到输入流
bfr_2 = new BufferedReader(new FileReader("c:\\SQL.txt"));
// 读取数据
while((line_2=bfr_2.readLine())!=null)
{
// 查找数据库里是否有用户输入的用户名
if(line.equals(line_2))
{
// 有的话就反馈客户端登陆成功
pw.println("用户:"+line+"您好!"+"恭喜您已经成功登录了");
// 改掉标记
flag=true;
// 并且跳出循环
break;
}
}
if(!flag)
{
pw.println("没有这个用户,请重新输入");
}
}
// 关闭流
bfr_2.close();
}
}
◆下面是并发上传图片是服务端代码示例:
package internet;
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;
public class ConcurrentUploadServer {
public static void main(String[] args) throws IOException {
// 建立服务端对象
ServerSocket ssocket = new ServerSocket(22346);
// 一有客服端请求就获取其客服端对象,并且为其单独开启一个线程
while(true)
{
Socket socket = ssocket.accept();
new Thread(new ConcurrentHandle(socket)).start();;
}
}
}
class ConcurrentHandle implements Runnable
{
private Socket socket;
ConcurrentHandle(Socket socket)
{
this.socket =socket;
}
public void run()
{
InputStream input=null;
OutputStream output=null;
FileOutputStream fos=null;
// 建立一个计数器
int temp =1;
String ip=socket.getInetAddress().getHostAddress();
try
{
// 得到客服端的输入输出流
input = socket.getInputStream();
output =socket.getOutputStream();
// 把ip当作名字并且封装成对象
File file = new File("c:/",ip+".jpg");
// 如果有这个文件的话,名字就为计计数器的值加上ip
while(file.exists())
{
file = new File("c:/",ip+"("+(temp++)+")"+".jpg");
}
// 把数据加载到输出流
fos = new FileOutputStream(file);
// 建立缓冲区
byte by[] =new byte[1024*10];
int num=0;
while((num=input.read(by))!=-1)
{
// 加载到输入流
fos.write(by, 0,num);
}
// 反馈客服端上传成功
output.write("恭喜你上传成功".getBytes());
fos.close();
socket.close();
}
catch(Exception e)
{
throw new RuntimeException("上传失败");
}
}
}
四、URL和URLConnection
1、URL:
URI:范围更大,条形码也包含于此范围
URL:范围较小,即域名
方法:
1)构造函数:URL(String protocol,String host,int port,String file);//根据指定 protocol、host、port号和 file 创建 URL对象。
2)String getProtocol();//获取协议名称
3)String getHost();//获取主机名
4)int getPort();//获取端口号
5)String getFile();//获取URL文件名
6)String getPath();//获取此URL的路径部分
7)String getQuery();//获取此URL的查询部,客户端传输的特定信息
注:一般输入网址,是不带端口号的,此时可进行获取,通过获取网址返回的port,若port为-1,则分配一个默认的80端口,如
int port = getPort();
if(port == -1)
port = 80;
2、URLConnection
方法:
1)URLConnection openConnection();//用URL调用此方法,返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。
2)InputStream getInputStream();//获取输入流
3)OutputStream getOutputStream();//获取输出流
感谢浏览,不足之处希望谅解