1、概述
网络编程,又称为Socket编程,即网络通信的两端都是Socket的对象,Socket底层是IO流的传输,Socket对象可以自动完成网络底层模型中的工作,比如IP、传输协议、端口号的封装和解封装,数据传输等,它是网络服务向开发者提供的一种机制,可以让开发者避免直接面对复杂的网络模型和流程,而通过简单的Sockec操作完成网络数据的传输。
根据传输层中不同的传输协议:TCP/IP协议和UDP协议,数据的传输方式不同,他们之间的差异主要如下:
1、TCP协议通过三次握手,建立稳定的数据通道;而UDP则面向无连接的通信,通信前不需要建立连接。
2、TCP一次可以进行大量数据的传输;而UDP则一次传输的数据大小不能超过64KB。
3、TCP是可靠协议,数据可以无差错、有序的传输;而UDP是不可靠的传输协议,数据会出现丢失。
4、TCP因为需要事先建立连接,所以数据的传输效率比较低;而UDP传输效率更高。
不同的传输协议,导致了不同的Socket的编程方式,在Java API中,它们是不同的类,操作方式也存在很大的差异。
2、Java API 中UDP编程知识点。
1、Java API为UDP协议的提供的Socket类是DatagramSocket。
2、通信两端的Soekct对象,都是DatagramSocket对象。
3、DatagramSocket可以通过构造函数,绑定一个0-65536之间的端口号,指定Socket服务监听该端口号。
4、通信要素(IP地址、端口号)及传输数据都封装在DatagramPacket(数据报包)对象中,而不是DatagramSocket对象中。
5、DatagramSocket提供void send(DatagramPacket p)和void receive(DatagramPacket p)方法,用来发送传入的数据包和指定数据包来装载接收数据。
6、创建发送端数据包时,需要分别接收字节数组、IP地址、端口号,而创建接收数据包时,则只需指定一个足够长的字节数组。
7、DatagramSocket调用close()方法关闭底层资源。
3、创建一个简单的UDP通讯程序的步骤。
发送端:
1、通过无参构造函数创建一个DatagramSocket对象。如果Socket服务需要接收数据,则传入一个int类型的端口号,使Socket服务监听该端口号。
2、通过构造函数DatagramPacket(byte[] buf,int length,InetAddress address,int port)创建需要发送的数据包DatagramPacket对象,该构造函数接收4个参数,分别是一个字节数组(传输数据)、包长度(一般为数组长度)、IP对象(目的地址)、端口号(目的端口,范围:0-65536)。
3、通过DatagramSocket对象调用void send(DatagramPacket p)方法,把第2步中创建的包对象传入该方法的参数中。
4、通过DatagramSocket对象调用close()关闭资源。
代码示例:
package com.example.network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPSender {
public static void main(String[] args) throws IOException {
//1.创建Socket服务对象,绑定端口号
DatagramSocket ds = new DatagramSocket(8000);
//2.创建数据包对象
//传输一段文本信息
String text = "Hello,UDP Receiver!";
DatagramPacket dp = new DatagramPacket(text.getBytes(),text.getBytes().length,InetAddress.getLocalHost(),3000);
//3.调用socket服务的send()方法,发送数据包
ds.send(dp);
//4.关闭资源
ds.close();
}
}
接收端:
1、通过DatagramSocket(int port)构造函数创建一个DatagramSocket对象,该方法传入一个端口号,表示该Socket服务监听该端口号。
2、通过构造函数DatagramPacket(byte[] buf,int length)创建一个DatagramPacket对象,用来接收长度为length的数据包。当长度小于接收的数据包时,数据会进行截取。
3、通过DatagramSocket对象调用void receive(DatagramPacket p)方法,把第2步中创建的包对象传入该方法参数中,用来装载接收到的数据,当该方法返回时,该数据包对象包含了发送端的数据、IP地址、发送端端口号。该方法在未接收到数据前,处于阻塞状态,程序挂起。
4、如果第3步receive()方法返回,则参数中指定的DatagramPacket对象成功装载了发送端的数据,接着调用API所提供的方法,如byte[] getData()、InetAddress getAddress()、int getPort()等,返回接收到的具体的信息,并打印出来。需要注意的是在DatagramPacket对象接收了发送端数据后,包的长度将由初始化时的长度变为接收到数据的字节长度,可以通过int getLength()方法返回。其中端口号信息是指定发送socket服务绑定的端口号,而不是发送端数据包中指定的接收端的端口号,如果没有绑定,则随机分配一个可用的端口号。
4、通过DatagramSocket对象调用close()关闭资源。
代码示例:
package com.example.network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPReceiver {
public static void main(String[] args) throws IOException {
//1.创建Socket服务对象,绑定端口号
DatagramSocket ds = new DatagramSocket(3000);
//2.创建一个数据包,用来接收发送端的信息。
/*
* 定义一个足够长的字节数据,用来装载数据,如果你担心发送端的数据过大,你可以把字节数组长度设为65507,这是发送数据最大长度。
* 因为UDP每次最多只能发送64KB,长度最大为64×1024=65535,它还包含20字节的IP首部和8个字节的UDP首部,所以数据长度只能为65507个字节。
*/
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
/*
* 3.调用socket服务的receive()方法,传入第2步创建的数据包,该方法是阻塞式。
* 如果方法返回,则参数传入的数据包会装载接收到的数据,包括发送数据、IP地址、客户端端口号等。
*/
ds.receive(dp);
//4.调用第二步中数据包对象的方法,返回接收到的信息,如数据、IP地址、端口等,并打印出来。
InetAddress ip = dp.getAddress();
//端口是指发送端绑定的端口,而不是数据包的端口号,如果发送端没有指定,则socket服务会任意绑定到一个可用端口上
int port = dp.getPort();
byte[] receText = dp.getData();
//打印
System.out.println(ip.getHostAddress()+":"+port+","+new String(receText,0,dp.getLength()));
//5.关闭资源
ds.close();
}
}
结果:
4、代码升级。
1、发送端接收键盘输入并发送,而不是固定文本信息。
发送端:
package com.example.network;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPSender2 {
public static void main(String[] args) throws IOException {
//1.创建Socket服务对象,绑定端口号
DatagramSocket ds = new DatagramSocket(8000);
//键盘输入流
InputStream in = System.in ;
//装饰
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line ;
while((line = br.readLine()) != null){//阻塞式,在键盘按回车时读取信息,并且不会结束,继续以阻塞状态运行。
//2.创建数据包对象
DatagramPacket dp = new DatagramPacket(line.getBytes(),line.getBytes().length,InetAddress.getLocalHost(),3000);
//3.调用socket服务的send()方法,发送数据包
ds.send(dp);
//定义一个结束标志。
if("over".equals(line)){
break;
}
}
//4.关闭资源
ds.close();
}
}
接收端:
package com.example.network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPReceiver2 {
public static void main(String[] args) throws IOException {
//1.创建Socket服务对象,绑定端口号
DatagramSocket ds = new DatagramSocket(3000);
//2.创建一个数据包,用来接收发送端的信息。
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//一直等待接收数据
while(true){
ds.receive(dp);
InetAddress ip = dp.getAddress();
int port = dp.getPort();
byte[] receText = dp.getData();
//打印
System.out.println(ip.getHostAddress()+":"+port+","+new String(receText,0,dp.getLength()));
}
}
}
结果:
2、使用多线程,实现在一个DOS窗口下,同时进行发送和接收。
发送端:
package com.example.network;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Sender implements Runnable {
private DatagramSocket ds;
Sender(DatagramSocket ds){
this.ds = ds;
}
@Override
public void run() {
try {
InputStream in = System.in ;
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line ;
while((line = br.readLine()) != null){
DatagramPacket dp = new DatagramPacket(line.getBytes(),line.getBytes().length,InetAddress.getLocalHost(),3000);
ds.send(dp);
if("over".equals(line)){
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally{
ds.close();
}
}
}
接收端:
package com.example.network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Receiver implements Runnable {
private DatagramSocket ds;
Receiver(DatagramSocket ds){
this.ds = ds;
}
@Override
public void run() {
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
while(true){
try {
ds.receive(dp);
InetAddress ip = dp.getAddress();
int port = dp.getPort();
byte[] receText = dp.getData();
System.out.println(ip.getHostAddress()+":"+port+","+new String(receText,0,dp.getLength()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
主程序:
package com.example.network;
import java.net.DatagramSocket;
import java.net.SocketException;
public class ChatDemo {
public static void main(String[] args) throws SocketException {
DatagramSocket sendSocket = new DatagramSocket(8000);
DatagramSocket receSocekt = new DatagramSocket(3000);
new Thread(new Sender(sendSocket)).start();
new Thread(new Receiver(receSocekt)).start();
}
}
结果:
3、发送广播信息,而不是仅仅向一个主机发送信息。
只需把发送端数据包中发送的IP的地址,指定为当前网段的广播地址,即最后一位为255,则发送端发送数据包,局域网中所有的计算机都会接收到该数据。
代码示例:
DatagramPacket dp = new DatagramPacket(line.getBytes(),line.getBytes().length,InetAddress.getByName("192.168.1.255"),3000);
优秀BLOG学习(未完成):。