数据包Socket API
数据包Socket在应用层可以支持无连接通信及面向连接通信。
这是因为,尽管数据包在传输层发送和接收时没有连接信息,但Socket API的运行时支持可以为进程间的数据包交换创建和维护逻辑连接。
Java为数据包Socket API提供了两个类:针对Socket的DatagramSocket类和针对数据包交换的DatagramPacket类。
1、无连接数据包Socket API
接收消息方:
public class DatagramReceiver1 {
public static void main(String[] args){
//接收端口号(即本地监听端口号)
int port=9527;
//消息长度(中文汉字:每个占用3个字节,故12字节只能接收4个汉字)
final int MAX_LEN=12;
try {
//数据包Socket,绑定端口号
DatagramSocket mySocket = new DatagramSocket(port);
byte[] buffer = new byte[MAX_LEN];
DatagramPacket datagramPacket = new DatagramPacket(buffer, MAX_LEN);
for(int i=1;i<=10;i++) {
System.out.println("Waiting for receiving the data "+i+" times...");
//监听的时候会阻塞,为了避免无期限阻塞下去,可以使用如下方法设置时间
//mySocket.setSoTimeout(5000);//5秒
mySocket.receive(datagramPacket);
String msg = new String(buffer);
System.out.println(msg);
}
//关掉数据包Socket
mySocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
发送消息方:
public class DatagramSender1 {
public static void main(String[] args){
try {
//接收者IP地址(这里本地测试,所以用本地地址)
InetAddress receiverHost = InetAddress.getByName("127.0.0.1");
//接收者端口号
int receiverPort=9527;
//数据包Socket与数据包
DatagramSocket mySocket = new DatagramSocket();
DatagramPacket datagramPacket;
Scanner scanner = new Scanner(System.in);
for(int i=0;i<10;i++) {
System.out.println("请输入你要传送的消息:");
String msg=scanner.nextLine();
byte[] buffer=msg.getBytes();
//数据包封装信息与接收者地址、端口号
datagramPacket = new DatagramPacket(buffer, buffer.length,receiverHost,receiverPort);
//数据包Socket发送数据包
mySocket.send(datagramPacket);
}
//关掉数据包Socket
mySocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结流程:
接收者:
- 接收者创建DatagramSocket进行监听固定端口
- 使用DatagramPacket 携带数据缓冲进行接收消息
- 使用receive方法持续监听
- 关闭DatagramSocket
发送者:
- 发送者将接收者IP地址与端口号 以及消息 封装到DatagramPacket 里
- 使用DatagramSocket 的send 发送消息
- 关闭DatagramSocket
由于在无连接方式中,数据是通过一系列独立的报文发送的,因此无连接数据包Socket中存在一些异常情形:
- 如果数据包被发送给一个仍未被接收者创建的Socket,该数据包将可能被丢弃。在这种情况下,数据将丢失,并且receive操作可能会导致无限阻塞。(如上先运行发送者的send,后运行接收者的receive)
- 如果接收者定义了一个大小为n的数据包缓存,那么大小超过n的接收消息将被截断。(如上传输超过4个中文汉字的部分将被截断)
前面是单工方式,下面使用双工通信方式:
将对DatagramSocket的操作封装到一个类里,在接收者与发送者中进行解耦:
public class MyDatagramSocket extends DatagramSocket {
//最大接收字节数
private static final int MAX_LEN = 100;
MyDatagramSocket(int portNo) throws SocketException{
super(portNo);
}
public void sendMessage(InetAddress receiverHost, int receiverPort, String message) throws IOException {
byte[] sendBuffer = message.getBytes( );
//数据包封装消息、地址、端口号
DatagramPacket datagram = new DatagramPacket(sendBuffer, sendBuffer.length, receiverHost, receiverPort);
//DatagramSocket发送数据包
this.send(datagram);
} // end sendMessage
public String receiveMessage() throws IOException {
byte[ ] receiveBuffer = new byte[MAX_LEN];
//数据包携带接收缓冲
DatagramPacket datagram = new DatagramPacket(receiveBuffer, MAX_LEN);
//DatagramSocket阻塞接收数据包
this.receive(datagram);
return new String(receiveBuffer);
} //end receiveMessage
} //end class
public class DatagramSocketReceiverSender {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
//接收者IP地址
InetAddress receiverHost = InetAddress.getByName("127.0.0.1");
//接收者端口号
int receiverPort = 9527;
//自己的端口号
int myPort = 9528;
//监听自己的端口(即发送者消息的目的端口)
MyDatagramSocket mySocket = new MyDatagramSocket(myPort);
for (int i = 0; i < 10; i++) {
// 阻塞接收消息
System.out.println("接收到了:" + mySocket.receiveMessage());
// 发送消息
System.out.println("请输入应答:");
String msg = scanner.nextLine();
mySocket.sendMessage(receiverHost, receiverPort, msg);
}
//关闭Socket
mySocket.close();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}
public class DatagramSocketSenderReceiver {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
//接收者IP地址(因为测试的时候接收者和发送者都在本机)
InetAddress receiverHost = InetAddress.getByName("127.0.0.1");
//接收者端口(与上面 DatagramSocketReceiverSender 的端口相对应)
int receiverPort = 9528;
int myPort = 9527;
//监听自己端口
MyDatagramSocket mySocket = new MyDatagramSocket(myPort);
for(int i=0;i<10;i++) {
System.out.println("请输入要发送的消息:");
String message = scanner.nextLine();
mySocket.sendMessage(receiverHost, receiverPort, message);
// 阻塞接收消息
System.out.println("收到应答:" + mySocket.receiveMessage());
}
mySocket.close();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}
如上,接收者可以接收消息,也可以发送消息;发送者可以发送消息,也可以接收消息;
2、面向连接数据包Socket API
BTW:面向连接数据包Socket API不经常使用,因为该API提供的连接非常简单,通常不能满足面向连接的通信要求。而流式Socket是面向连接通信中更典型和实用的方法。
还需要注意的是:
一旦连接建立后,Socket将只能用来与建立连接的远程Socket交换数据报文。如果数据包地址与另一端Socket地址不匹配,将引发IllegalArgumentException异常。如果发送到Socket的数据来源于其他发送源,而不是与之相连接的远程Socket,那么该数据就会被忽略。因此,连接一旦与数据包Socket绑定后,该Socket将不能与任何其他Socket通信,直到该连接终止。
同时,由于连接时单向的,也就是说限制了其中一方,另一方的Socket可以自由地向其他Socket发送或接收数据,除非有其他Socket建立到它的连接。
接收者(建立连接):
public class DatagramSocketConnectReceiver {
public static void main(String[] args) {
try {
//发送者地址与端口号
InetAddress senderHost = InetAddress.getByName("127.0.0.1");
int senderPort = 9527;
//自己的接收端口号,并且监听该端口
int myPort = 9528;
MyDatagramSocket mySocket = new MyDatagramSocket(myPort);
//跟发送着建立连接,通过IP地址和端口号
mySocket.connect(senderHost,senderPort);
for(int i=0;i<10;i++) {
System.out.println(mySocket.receiveMessage());
}
//可以发回消息
mySocket.sendMessage(senderHost,senderPort,"接收到了消息,现在回复!!!");
//断开连接,并且关闭Socket
mySocket.disconnect();
mySocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
发送者(可以不建立连接):
public class DatagramSocketConnectSender {
public static void main(String[] args) {
try {
//接收者地址与端口号
InetAddress receiverHost = InetAddress.getByName("127.0.0.1");
int receiverPort = 9528;
//自己的接收端口号,并且监听该端口
int myPort = 9527;
MyDatagramSocket mySocket = new MyDatagramSocket(myPort);
for(int i=0;i<10;i++) {
mySocket.sendMessage(receiverHost,receiverPort,"发送的消息:"+i);
}
System.out.println(mySocket.receiveMessage());
//关闭Socket
mySocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
下篇讲流式Socket API;