这里我们首先先简单介绍一下socket编程的基础知识,Java对网络提供了高速的支持,使得客户端和服务器的沟通变成了现实,而在网络编程中使用最多的就是socket,大家所熟悉的网络程序,像QQ,MSN等等,这些网络应用都使用了socket相关的技术,JAVA平台为我们提供了一组功能强大的类,为我们使用socket进行网络程序的开发,本专题就为大家一起揭开socket的神秘面纱,看看网络通信是怎样练成的,本节课我们会讲下面这些内容:网络基础知识、InetAddress类、URL类、TCP编程、UDP编程。
下面我们来看网络相关的基础知识,两台计算机要想通过网络进行通信,那么他们需要满足一些必然的条件:
这是两台主机通过网络进行通信的必备条件:IP地址,协议,端口号。那么TCP/IP协议是什么呢?
TCP/IP协议:TCP/IP是目前世界上应用最为广泛的协议,它是以TCP和IP为基础的不同层次上多个协议的集合,也成TCP/IP协议族,或TCP/IP协议栈。两个主机之间要进行通信,他们都要遵守这个TCP和IP的协议。
TCP:Transmission Control Protocol,它的全称叫传输控制协议
IP:Internet Protocol 叫互联网协议
我们在实际的应用当中,一般会将网络进行分层,常见的分为5层:
5 应用层
4 传输层
3 网络层
2 数据链路层
1 物理层
这叫TCP/IP的5层模型,最底层物理层是我们用户最直观接触到的网线、双绞线、网卡等等都处于我们的物理层;而TCP和IP协议,在我们的第4层也就是传输层,最上方的应用层也是用户直接接触到的,那么应用层有许多协议是大家所熟知的,像HTTP协议超文本传输协议,各位小伙伴们会经常上网看新闻,看网页或者电子商务等等,那么他们所使用的协议就是HTTP超文本传输协议;还有像FTP文件传输协议,可以进行文件的上传和下载,文件的共享等等;SMTP简单邮件传送协议可以进行文件的发送;Telnet远程登陆服务等等,这些都是应用层的协议。
IP地址:它是每台计算机唯一的标识,用来区分网络中的不同的主机,是两台主机通信所必不可少的。这个IP地址就非常类似我们现实当中的人的手机号码,比如说张三想给李四发消息,首先他要知道李四的手机号,通过这个手机号码他就可以定位到这个人,然后才能与之通信,IP地址也是一样的,它就是计算机的唯一的一个身份,唯一的标识,如果你想实现与另外一台计算机的通信,那么你就要知道对方的IP地址。我们说现实中的手机号码它有一个固定的格式,11位,有关部门制定的。而IP地址它也是有固定格式的。IP地址格式:数字型,如192.168.0.1 这是IPV4,也就是32位的长度。
端口:端口号实际上就是为了区分不同的应用程序,我们的一个主机它可以同时运行多个程序,比方说可以同时安装QQ,迅雷,暴风影音,idea,那么我们如何知道我们所发送的信息被另一台主机的对应的软件所接收呢?可不可能出现我们使用QQ发送消息,被对方的MSN接收到呢?这个是不会的,就是因为有端口号的存在。
JAVA对网络的支持
针对网络通信的不同层次,JAVA提供的网络功能有四大类:
1.JAVA中InetAddress的应用,了解了网络基础知识以后,我们来了解一下InetAddress类InetAddress类用于标识网络上的硬件资源,标识互联网协议(IP)地址的相关信息。打开API查看该类,没有构造方法,也就是没有办法直接通过new的方式来创建一个它的对象,我们接着往下看一下方法摘要,它提供了一些静态方法。如:
方法体 | 作用 |
---|---|
getLocalHost() | 返回本地主机 |
getByName() | 在给定主机名的情况下,确定主机的IP地址 |
getByAdress() | 在给定原始IP地址的情况下,返回InetAddress的对象 |
getByAdress() | 根据提供的主机名和IP地址,创建InetAddress对象 |
getHostName() | 获取此IP地址的主机名 |
getHostAddress() | 返回IP地址的字符串(以文本表现形式) |
下面我们来写一个实际的例子
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
/**
* Created by Administrator on 2017/7/25.
* InetAddress类
*/
public class Test01 {
public static void main(String[] args) {
//获取本机的InetAddress类的实例
try {
InetAddress address=InetAddress.getLocalHost();
System.out.println("计算机名:"+address.getHostName());
System.out.println("IP地址:"+address.getHostAddress());
byte[] bytes=address.getAddress();//获取IP地址的字节数组
System.out.println("字节形式的数组IP:"+ Arrays.toString(bytes));
System.out.println(address);
//根据主机名获取InetAddress实例
InetAddress address2=InetAddress.getByName("PC-20160824WQYZ");
System.out.println("计算机名:"+address2.getHostName());
System.out.println("IP地址:"+address2.getHostAddress());
InetAddress address3=InetAddress.getByName("192.168.1.103");
System.out.println("计算机名:"+address3.getHostName());
System.out.println("IP地址:"+address3.getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
2.JAVA中URL的应用,首先我们需要先了解下URL:
这里我们该如何使用它呢?打开API文档来查看一下。构造方法,可以根据字符串方式来创建,参数要求不同,也可以根据协议名,主机、端口号、和文件的信息来创建URL。也可以根据一个已存在的URL,以及相应的字符串信息来创建一个新的URL,也就是说根据一个父URL来创建一个子URL,这是我们看到的它的构造方法,这个类还提供了许多其他方法来获取URL的信息。
关于getPort()方法返回值问题:
使用URL读取网页内容,通过URL对象的openStream()方法可以得到指定资源的输入流,通过输入流可以读取、访问网络上的数据。
下面是案例:
import java.net.MalformedURLException;
import java.net.URL;
/**
* Created by Administrator on 2017/7/25.
* URL常用方法
*/
public class Test02 {
public static void main(String[] args) {
//创建一个URL的实例
try {
URL imooc=new URL("http://www.imooc.com");
//根据已创建的URL,来创建新的URL
URL url=new URL(imooc,"/index.html?username=tom#test");
//?后面表示参数,#后面表示锚点
System.out.println("协议:"+url.getProtocol());
System.out.println("主机:"+url.getHost());
//如果未指定端口号,根据协议不同使用默认端口号,那我们这里HTTP使用的是80
//此时因为是默认的端口号,所以getPort返回值是-1
System.out.println("端口号:"+url.getPort());
System.out.println("文件路径:"+url.getPath());
System.out.println("文件名:"+url.getFile());
System.out.println("相对路径:"+url.getRef());
System.out.println("查询字符串:"+url.getQuery());//查询字符串就是我们的参数
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
/**
* 使用URL来读取网页中的内容
* Created by Administrator on 2017/7/25.
*/
public class Test03 {
public static void main(String[] args) {
//创建一个URL实例
try {
URL url=new URL("http://www.baidu.com");
//通过url的openstream方法来获取url对象所表示资源的字节输入流
InputStream is=url.openStream();
//之后把它转化成字符输入流
InputStreamReader isr=new InputStreamReader(is);
//为字符输入流添加缓冲
BufferedReader br=new BufferedReader(isr);
String data=br.readLine();
while(data!=null){
System.out.println(data);
data=br.readLine();
}
br.close();
isr.close();
is.close();
} catch (MalformedURLException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}
}
}
在了解了网络的相关知识,下面我们来看下Socket编程及基于TCP的网络通信。
TCP协议是面向连接、可靠的、有序的,以字节流的方式发送数据。在数据通信之前需要建立连接,保证数据传输的可靠性,那么它的传输是有顺序的,并且以字节流的方式进行传输,在我们JAVA当中如何去基于TCP协议实现网络通信呢?
有这么两个相关的类,基于TCP协议实现网络通信的类:
我们来看一下它具体的通信的过程,也就是Socket通信模型。两台主机需要通信,就必然存在着一台主机为服务器端Server,一台为客户端Client。那么通信的时候是这样的,首先我们在服务端,建立一个ServerSocket(服务端倾听Socket),绑定相应的端口,并且在指定的端口进行侦听,等待客户端的连接。当我们在客户端创建Socket并且向服务器端发送请求,服务器收到请求并且接受客户端的请求信息,一旦接受客户端请求信息以后,会创建一个连接Socket,用来与客户端的Socket进行通信。到底该如何进行通信,就是通过相关的输入流和输出流 InputStream,OutpuStream进行数据的交换,发送接收以及数据的响应等等。那么在客户端和服务器端双方通信完以后,我们需要分别关闭两边的Socket及相关资源,进行通信的断开。这是基于TCP-socket通信,整个通信的过程,这点需要各位小伙伴们清楚。
那么具体该怎么进行操作呢?Socket实现通信操作的基本步骤:
那如何使用SeverSocket和Socket呢?我们可以打开API文档来看一下这里是怎么说明这两个类的。
我们知道编程实现是基于TCP的Socket通信之服务端,下面我们通过socket的一个案例来看一下,如果实现基于TCP的socket通信,我们要实现的是一个用户的登陆。实现的过程,实际上就是用户信息在客户端向服务器端发送过去,服务器端接收到用户信息以后,进行响应这样一来一回的过程,它的运行结果也非常的简单。在客户端向服务端发送登陆请求,提交用户名和密码,那服务器端呢进行响应,欢迎之类的信息。它有一个相对固定的实现的步骤,我们要在服务器端创建一个服务器端的Socket。
服务器端的步骤:
客户端的步骤:
仅仅发送信息到服务器端是不够的,如果要实现服务器端对客户端信息的响应,我们该如何做呢?其实也很简单,响应是通过输出流来实现的。
在了解了基于TCP的socket编程以后,我们有的小伙伴们就会发出这样的疑问,那我们所编写的程序只能实现服务器和一个客户端的对话,而在实际的应用中,往往是在服务器中运行一个永久的程序,它可以和多个客户端进行通信,并且可以接受多个客户的请求,提供相应的服务。那么我们如何实现一个服务器和多客户端之间的通信呢?对于这种需求我们又该使用何种技术去实现呢?
我们可以使用之前所讲过的多线程来实现服务器与多客户端之间的通信。它的基本步骤:
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by Administrator on 2017/7/26.
* Socket基于TCP通信,服务器端SeverSocket
*/
public class Server {
public static void main(String[] args) {
try {
//1.创建一个服务器端ServerSocket,并且绑定指定的端口号
ServerSocket serverSocket=new ServerSocket(8888);
Socket socket=null;
//记录客户端的数量
int count=0;
//2.调用accept()方法开始监听,等待客户端的连接
System.out.println("***服务器即将启动,等待客户端的连接***");
while(true){
socket=serverSocket.accept();
ServerThread st=new ServerThread(socket);
st.start();
count++;
System.out.println("客户端的数量"+count);
InetAddress address=socket.getInetAddress();
System.out.println("当前客户端的IP地址:"+address.getHostAddress());
}
// Socket socket=serverSocket.accept();//一旦调用这个方法它就会处于阻塞的状态,等待客户端的请求信息
// //如果客户端发送连接请求,这个时候我们会接收客户端的请求,并且我们看到accpet方法会返回一个socket的实例
// //用来与客户端进行通信
// //一旦与客户端建立socket通信以后,我们下面就需要在客户端和服务器端实现数据的交互,获取客户端提交的登陆
// //信息,那么如何获取呢?需要通过输入输出流来实现。
// //3.获取一个输入流,然后来读取客户信息
// InputStream is=socket.getInputStream();//字节输入流
// //为了提升效率可以把它包装成一个字符输入流
// InputStreamReader isr=new InputStreamReader(is);
// //我们可以为字符流添加缓冲,以缓冲的方式去进行数据的读取
// BufferedReader br=new BufferedReader(isr);
// String info=null;
// while((info=br.readLine())!=null){
// System.out.println("我是服务器,客户端说"+info);
// }
// socket.shutdownInput();//关闭输入流
// //4.获取输出流,用来服务器端响应客户端的信息
// OutputStream os=socket.getOutputStream();
// PrintWriter pw=new PrintWriter(os);
// pw.write("欢迎您!");
// pw.flush();
// //5.关闭相关的资源
// pw.close();
// os.close();
// br.close();
// isr.close();
// is.close();
// socket.close();
//serverSocket.close();有一个死循环所以无法关闭也不会进展到这一步,所以删掉
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.*;
import java.net.Socket;
/**
* Created by Administrator on 2017/7/26.
* 客户端
*/
public class Client {
public static void main(String[] args) {
try {
//1.创建socket用来与服务器端进行通信,发送请求建立连接,指定服务器地址和端口
Socket socket=new Socket("localhost",8888);
//2.获取输出流用来向服务器端发送登陆的信息
OutputStream os=socket.getOutputStream();//字节输出流
PrintWriter pw=new PrintWriter(os);//将输出流包装成打印流
pw.write("用户名:admin;密码:123");
pw.flush();
socket.shutdownOutput();//关闭输出流
//3.获取输入流,用来读取服务器端的响应信息
InputStream is=socket.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(is));//添加缓冲
String info=null;
while((info=br.readLine())!=null){
System.out.println("我是客户端,服务器端说"+info);
}
//4.关闭其他相关资源
br.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (IOException e) {
}
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by Administrator on 2017/7/26.
* 服务器端线程处理类
*/
public class ServerThread extends Thread {
//每个客户端请求了以后,服务器端都会创建一个socket,与之进行通信,所以我们每个线程都对应了一个与
//本线程相关的socket
Socket socket=null;
//构造方法初始化
public ServerThread(Socket socket){
this.socket=socket;
}
//线程执行的操作,响应客户端的请求
public void run() {
InputStream is = null;//字节输入流
InputStreamReader isr=null;
BufferedReader br=null;
OutputStream os=null;
PrintWriter pw=null;
try {
is = socket.getInputStream();
//3.获取输入流读取客户端的信息
//为了提升效率可以把它包装成一个字符输入流
isr = new InputStreamReader(is);
//我们可以为字符流添加缓冲,以缓冲的方式去进行数据的读取
br = new BufferedReader(isr);
String info = null;
while ((info = br.readLine()) != null) {
System.out.println("我是服务器,客户端说" + info);
}
socket.shutdownInput();//关闭输入流
//4.获取输出流,用来服务器端响应客户端的信息
os = socket.getOutputStream();
pw = new PrintWriter(os);
pw.write("欢迎您!");
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
//5.关闭相关的资源,finally是一定会被执行的
try {
if(pw!=null)
pw.close();
if(os!=null)
os.close();
if(br!=null)
br.close();
if(isr!=null)
isr.close();
if(is!=null)
is.close();
if(socket!=null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
UDP协议(用户数据报协议)是无连接、不可靠、无序的,不固定的。特点在于它的速度相对来说会比较快.UDP它以数据报作为数据传输的载体。在我们使用UDP进行数据传输时,会将数据封装成Datagram(定义成数据报),然后在数据报中指明数据所要到达的socket(主机地址和端口号),然后再将数据报发送出去。
那么到底该如何实现udp-socket编程呢?这里我们需要使用如下两个类:
DatagramPacket:表示数据报包
DatagramSocket:进行端到端通信的类,实现UDP的socket通信
下面我们来做一个实现应用登陆的案例
服务器端实现步骤:
1.创建DatagramSocket,并且指定端口号
2.创建一个数据报 DatagramPacket,用来接收客户端发送的数据
3.接收客户端发送的数据信息
4.读取数据
客户端实现步骤:
1.定义发送的数据信息
2.创建数据报,DatagramPacket,包含将要发送的信息
3.创建DatagramSocket对象,用来实现数据的发送
4.我们把数据发送过去
这里我们需要思考一下如何像TCP一样实现多客户端的通信
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
/**
* Created by Administrator on 2017/7/26.
* 服务器端,基于UDP的用户登陆
*/
public class UDPServer {
public static void main(String[] args) {
/**
* 接受客户端发送的数据
*/
try {
//1.创建服务器端DatagramSocket,并且指定端口
DatagramSocket socket=new DatagramSocket(8800);
//2.创建数据报,用于接收客户端发送的数据
byte[] data=new byte[1024];
DatagramPacket packet=new DatagramPacket(data,data.length);//接受信息的数组以及接收的长度
//3.接收客户端发送的数据
System.out.println("***服务器端已经启动,等待客户端发送数据***");
socket.receive(packet);//此方法在接收到数据报之前会一直阻塞
//4.读取这个数据
//实际上信息已经保存在字节数组当中,所以这个时候直接转化就好了
String info=new String(data,0,packet.getLength());
System.out.println("我是服务器,客户端说:"+info);
/**
* 向客户端响应数据
*/
//1.定义客户端的地址,端口号和数据
InetAddress adress=packet.getAddress();
int port=packet.getPort();
byte []data2="欢迎您".getBytes();
//2.创建数据报,包含响应的数据信息,
DatagramPacket packet2=new DatagramPacket(data2,data2.length,adress,port);
//3.响应客户端
socket.send(packet2);
//4.关闭相应的资源信息
socket.close();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.IOException;
import java.net.*;
/**
* Created by Administrator on 2017/7/26.
*/
public class UDPClinet {
public static void main(String[] args) {
/**
* 向服务器端发送数据
*/
try {
//1.定义服务器的地址、端口号以及相应要发送的数据
InetAddress address=InetAddress.getByName("localhost");
int port=8800;
byte data[]="用户名:admin;密码:123".getBytes();
//2.创建数据报,包含发送的相关数据,服务器地址端口以及信息
DatagramPacket packet=new DatagramPacket(data,data.length,address,port);
//3.创建datagramSocket来实现数据的发送
DatagramSocket socket=new DatagramSocket();
//4.使用datagramsocket向服务器端发送数据报
socket.send(packet);
/**
* 接收服务端响应的数据
*/
//1.创建数据报,用来接收服务器端响应的数据,数据保存在字节数组里
byte [] data2=new byte[1024];
DatagramPacket packet2=new DatagramPacket(data2,data2.length);
//2.接收服务器端响应的数据
socket.receive(packet2);
//3.读取服务器端相应的数据信息
String reply=new String(data2,0,packet2.getLength());
System.out.println("我是客户端,服务器说:"+reply);
//4.关闭资源
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
}catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结一下socket通信的相关内容,本节课的重点是socket通信原理以及基于tcp的socket通信。希望各位小伙伴可以通过示例代码能反复的练习。这里主要有以下几个问题需要和大家多强调以下: