JAVA 基础学习之网络通讯编程
网络通讯编程
1、基本概念
什么是网络编程呢?网络编程就是在两个或者两个以上的设备(比如计算机)之间的数据传输。程序员需要做的事情就是把数据发送到指定的地方或者接收指定的数据,这个就是狭义的网络编程范畴。其实大部分的程序设计语言都设计了专门的API实现这些功能,程序员只需要调用即可。
2、TCP方式和UDP方式
2.1、TCP 方式 (传输控制方式)被称 TCP / IP
TCP方式就类似于拨打电话,使用这种方式进行网络传输时,需要建立专门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据。
TCP(Transfer Control Protocol)是面向连接的,所谓面向连接,就是当计算机双方通信时必需经过先建立连接,然后传送数据,最后拆除连接三个过程。
TCP**在建立连接时又分三步走:**
第一步:是请求端(客户端)发送一个包含SYN即同步(Synchronize)标志的TCP报文,SYN同步报文会指明客户端使用的端口以及TCP连接的初始序号。(客户端采用TCP协议将带有SYN标志的数据包发送给服务器,等待服务器的确认。)
第二步:服务器在收到客户端的SYN报文后,将返回一个SYN+ACK的报文,表示客户端的请求被接受,同时TCP序号被加一,ACK即确认(Acknowledgement)。(服务器端在收到SYN的数据包后,必须确认SYN,即自己发送的ACK标志,同时,自己也将会向客户端发送一个SYN标志。)
第三步:客户端也返回一个确认报文ACK给服务器端,同样TCP序列号被加一,到此一个TCP连接完成。然后才开始通信的第二步:数据处理。(客户端在接收到服务器的SYN+ACK包后,自己会向服务器发送ACK包,完成三次握手。那么客户端和服务器正式建立了连接,开始传输数据。)
举个打电话的例子:
A : 你好我是A,你听得到我在说话吗
B : 听到了,我是B,你听到我在说话吗
A : 嗯,听到了
建立连接,开始聊天!
2.2、UDP方式(用户数据报协议)
UDP方式就类似于发送短信,使用这种方式进行网络通讯时,不需要建立专门的虚拟连接,传输也不是很可靠,如果发送失败则服务端无法获得。
这两种传输方式都在实际的网络编程中使用,重要的数据一般使用TCP方式进行数据传输,而大量的非核心数据则可以通过UDP方式进行传递,在一些程序中甚至结合使用这两种方式进行数据传递。
由于TCP需要建立专用的虚拟连接以及确认传输是否正确,所以使用TCP方式的速度稍微慢一些,而且传输时产生的数据量要比UDP稍微大一些。
如何合理地使用这样种方式呢?
如果是比较重要的数据,建议使用TCP方式进行传输。
如果是大量非核心的数据,建议使用UDP方式进行传输。
为什么要这样使用呢?
1、TCP方式是面向连接的,使用的时候需要建立专门的虚拟的连接的时间,虽然执行速度会比UDP方式效率低,但是安全。
2、UDP方式是面向无连接的,不需要建立专门的虚拟连接的时间,虽然执行速度会比TCP方式效率高,但是不安全。
UDP**和TCP的区别及特点:**
UDP是一个不可靠协议,发送方所发送的数据报并不一定以相同的次序到达接收方。
TCP是一个可靠的协议,它能确保接收方完全正确地获取到发送方的全部数据。
UDP对数据传输时传输时的大小是有限制的,每个被传输的数据报必须限定在64K之内。
TCP传输数据大小限制,一旦连接建立起来,双方的socket(套接字)就可以按统一的格式传输大量的数据。
UDP:每个数据报中都给出了完整的地址信息,因此不需要建立发送方和接收方的连接
TCP:对于TCP协议,由于它是一个面向连接的协议,在socket之间进行数据传输前必须建立连接,所以在TCP中多了一个连接建立的时间 。
总结
- TCP是面向连接的,传输安全,稳定,效率较低。
- UDP是面向无连接的,传输不安全,效率较高。
3、InetAddress
3.1、作用
用于封装计算机的ip地址和DNS(没有端口信息)
注意:DNS指的是 Domain Name System,域名系统。
3.2、特点
这个类构造方法(不能被实例)。如果要得到对象,只能通过静态方法:
getLocalHost() 返回本地主机(IP地址+计算机名)
getByName() 在给定主机名的情况下确定主机的 IP 地址。
getAllByName() 在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组。
getAddress() 返回此 InetAddress 对象的原始 IP 地址。
getHostName() 获取此 IP 地址的主机名。
实例:
public static void main(String[] args) throws IOException {
//获取本机的IP地址和计算机名称
InetAddress address1=InetAddress.getLocalHost();
//返回ip地址
System.out.println(address1.getHostAddress());
//返回计算名
System.out.println(address1.getHostName());
//根据域名来获取服务的ip和域名名称
InetAddress address2=InetAddress.getByName("www.baidu.com");
// 返回 百度服务器的IP:14.215.177.38
System.out.println(address2.getHostAddress());
// 输出:www.baidu.com
System.out.println(address2.getHostName());
//根据IP地址来获取ip地址和域名名称
InetAddress addr = InetAddress.getByName("14.215.177.38");
// 返回百度服务器的IP:14.215.177.38
System.out.println(addr.getHostAddress());
/*
* 输出ip而不是域名。如果这个IP地址不存在或DNS服务器(域名系统)
* 不允许进行IP地址和域名的映射,
* getHostName方法就直接返回这个IP地址。
*/
System.out.println(addr.getHostName());
}
总结:它的构造函数不能够被实例化,用于封装计算机的ip地址和DNS,可以根据对应的方法用来获取本机的(服务器)IP地址和域名名称。
3.3、InetSocketAddress
作用:包含IP和端口信息,常用于Socket通信。此类实现 IP 套接字地址(IP 地址 + 端口号),不依赖任何协议。
使用:
InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8080);
InetSocketAddress socketAddress2 = new InetSocketAddress("localhost", 8000);
System.out.println(socketAddress.getHostName());
System.out.println(socketAddress2.getAddress());
总结:创建指定IP地址/主机名和端口号的套接字(socket)地址
3.4、URL(Uniform Resource Locator) 类
IP地址唯一标识了Internet上的计算机,而URL则标识了这些计算机上的资源。类 URL 代表一个统一资源定位符(有时也被俗称为网页地址),它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。
URL 解析:
协议为(protocol):http
主机为(host:port):www.runoob.com
端口号为(port): 80 ,以上URL实例并未指定端口,因为 HTTP 协议默认的端口号为 80。
文件路径为(path):/index.html
请求参数(query):language=cn
定位位置(fragment):j2se,定位到网页中 id 属性为 j2se 的 HTML 元素位置 。
注意:protocol(协议)可以是 HTTP、HTTPS、FTP 和 File,port 为端口号,path为文件路径及文件名。
示例:
public class TestURL1 {
public static void main(String[] args) throws MalformedURLException {
//统一资源定位符
URL url=new URL("https://www.baidu.com/?tn=93380420_hao_pg");
System.out.println("获取与此url关联的协议的默认端口:"+url.getDefaultPort());
System.out.println("主机名:"+url.getAuthority());//www.baidu.com
System.out.println("域名后面的内容:"+url.getFile());//域名后面的内容
System.out.println("路径:"+url.getPath());// 端口号后,参数前的内容
// 如果www.google.cn:80则返回80.否则返回-1时
System.out.println("端口:"+url.getPort());
System.out.println("协议:"+url.getProtocol());
System.out.println("参数部分:"+url.getQuery());
System.out.println("锚点:"+url.getRef());
}
}
总结:说白了它就是一个地址,可以通过URL可以到达任何一个地方寻找需要的东西,比如文件、数据库、图像、新闻组等等。
3.5、根据TCP协议的Socket编程实现来通信功能
1、ServerSocket**:**服务器套接字
构造函数 :
ServerSocket**(int port) 创建绑定到特定端口的服务器套接字。**
常用方法:
accept**()** 侦听并接受到此套接字的连接。
close**()** 关闭此套接字。
2、Socket**:**客户端套接字(也可以就叫“套接字”)。
构造函数:
Socket(**InetAddress** address, int port) 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
常用方法:
getInputStream**()** 返回此套接字的输入流。
getOutputStream**()** 返回此套接字的输出流。
close**()** 关闭此套接字。
实现TCP通信编程基本步骤:
- 在创建服务器ServerSocket时,定义ServerSocket的监听端口(注意:这个端口用于接收客户端发来的消息)。
- ServerSocket调用accept()方法,使之处于阻塞状态。等待客户端请求
- 创建客户端Socket,并设置服务器的IP及端口。
- 客户端发出连接请求,建立连接。
- 分别取得服务器和客户端Socket的InputStream和OutputStream。
- 利用Socket和ServerSocket进行数据传输。
- 关闭流及Socket。
3.5.1、单向通信Socket
服务端:
public class TestServer {
public static void main(String[] args) {
Socket socket=null;
BufferedWriter bw=null;
try {
//建立服务器套接字,并指定监听接口
ServerSocket serverSocket=new ServerSocket(8080);
System.out.println("服务器监听已建立");
//等待客户端请求,并愿意接收连接
socket=serverSocket.accept();
//获取socket的输出流,并使用缓冲流进行包装
bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//向客户端发送反馈信息
bw.write("没空!!!");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
// 关闭流及socket连接
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端:
public class TestClient {
public static void main(String[] args) {
Socket socket=null;
BufferedReader br=null;
try {
//创建Scoket对象:指定要连接的服务器的IP和端口而不是自己机器的端口。
//发送端口是随机的。
socket=new Socket(InetAddress.getLocalHost(),8080);
//获取scoket的输入流,并使用缓冲流进行包装
br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
//接收服务器端发送的信息
System.out.println(br.readLine());
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
// 关闭流及socket连接
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.5.2、双向通信Socket
服务器:
public class TestServer {
public static void main(String[] args) {
Socket socket=null;
BufferedWriter out=null;
BufferedReader in=null;
BufferedReader br=null;
try {
//建立服务器套接字,并指定监听接口
ServerSocket serverSocket=new ServerSocket(8080);
System.out.println("服务器监听已建立");
//等待客户端请求,并愿意接收连接
socket=serverSocket.accept();
//获取socket的输入输出流接收和发送信息
in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
//获取socket的输出流,并使用缓冲流进行包装
out=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//向客户端发送反馈信息
br = new BufferedReader(new InputStreamReader(System.in));
while(true){
//接收客户端发送的信息
String str=in.readLine();
System.out.println("客户端说:"+str);
String str2="";
//如果客户端发送的是“end”则终止连接
if (str.equals("end")){
break;
}
//否则,发送反馈信息
str2=br.readLine();//读到\n为止,因此一定要输入换行符!
out.write(str2+"\n");
out.flush();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
// 关闭流及socket连接
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端:
public class TestClient {
public static void main(String[] args) {
Socket socket=null;
BufferedReader in=null;
BufferedWriter out=null;
BufferedReader br=null;
try {
//创建Scoket对象:指定要连接的服务器的IP和端口而不是自己机器的端口。发送端口是随机的。
socket=new Socket(InetAddress.getLocalHost(),8080);
//获取scoket的输入输出流接收和发送信息
in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
out=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
br=new BufferedReader(new InputStreamReader(System.in));
while(true) {
//发送信息
String str=br.readLine();
out.write(str+"\n");
out.flush();
//如果输入的信息为“end”则终止连接
if (str.equals("end")) {
break;
}
//否则,接收并输出服务器端信息
System.out.println("服务器端说:" + in.readLine());
}
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
// 关闭流及socket连接
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意: 运行时,要先启动服务器端,再启动客户端,才能得到正常的运行效果。
总结:实现TCP协议的通讯功能需要用到ServerSocket 服务器套接字,一般建在服务端上和 Socket 套接字,一般建在客户端上。
3.6、UDP通讯的实现
- DatagramSocket :主要用于发送或接收数据包
当服务器要向客户端发送数据时,需要在服务器端产生一个DatagramSocket对象,在客户端产生一个DatagramSocket对象。
DatagramSocket有两种常用的构造函数。一种是无需任何参数的,常用于客户端;另一种需要指定端口,常用于服务器端。如下所示:
DatagramSocket() :构造数据报套接字并将其绑定到本地主机上任何可用的端口。
DatagramSocket(int port) :创建数据报套接字并将其绑定到本地主机上的指定端口。
常用方法:
send(DatagramPacket p) :从此套接字发送数据报包。
receive(DatagramPacket p) :从此套接字接收数据报包。
close() :关闭此数据报套接字。
DatagramPacket**:数据容器(封包)的作用**
此类表示数据报包。 数据报包用来实现封包的功能。也就是说用于将数据包装起来。
常用方法:
DatagramPacket(byte[] buf, int length) :构造数据报包,用来接收长度为 length 的数据包。
DatagramPacket(byte[] buf, int length, InetAddress address, int port) :构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
getAddress() :获取发送或接收方计算机的IP地址,此数据报将要发往该机器或者是从该机器接收到的。
getData() :获取发送或接收的数据。
setData(byte[] buf) :设置发送的数据。
UDP**通信编程基本**步骤:
在创建客户端的DatagramSocket(数据报套接字)时,定义客户端的监听端口
(注意:在本机上可以不定义监听接口)。
在创建服务器的DatagramSocket(数据报套接字)时,需要定义服务器的监听端口
- 在服务器端定义DatagramPacket对象,封装待发送的数据报包。
- 客户端将数据报发送出去。(注意:定义数据报时,必须告诉数据报包要发到哪台计算机的哪个端口、发送的数据以及数据的长度)
- 服务器接收数据报包。
- 关闭DatagramSocket 。
客户端:
public class TestClient {
public static void main(String[] args) throws Exception {
byte[] b="我是UDP客户端".getBytes();
//必须告诉数据报包要发到哪台计算机的哪个端口,
//发送的数据以及数据的长度
DatagramPacket packet=new DatagramPacket(b, b.length,
new InetSocketAddress("127.0.0.1", 6666));
//创建数据报套接字:指定发送信息端口(在本机上可以不指定)
DatagramSocket socket=new DatagramSocket(2000);
//发送数据报包
socket.send(packet);
//关闭资源
socket.close();
}
}
服务器:
public class TestServer {
public static void main(String[] args) throws Exception {
//创建数据报套接字:指定接收信息的端口
DatagramSocket ds=new DatagramSocket(6666);
byte[] b = new byte[1024];
//创建数据报包,指定要接收的数据的缓存位置和长度
DatagramPacket dp=new DatagramPacket(b, b.length);
//接收客户端发送的数据报
ds.receive(dp);// 阻塞式方法
String string=new String(dp.getData(),0,dp.getLength());
System.out.println(string);
//关闭资源
ds.close();
}
}
注意: 运行时,要先启动服务器端,再启动客户端,才能得到正常的运行效果。
总结:UDP的通讯实现主要用到DatagramSocket 和DatagramPacke,DatagramSocket 主要用于发送或接收数据,DatagramPacket 主要用于包装(封包)数据。
还没有评论,来说两句吧...