Java Socket编程与客户/服务器应用开发(二)

数据包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();
        }
    }
}


总结流程:
接收者:
  1. 接收者创建DatagramSocket进行监听固定端口
  2. 使用DatagramPacket 携带数据缓冲进行接收消息
  3. 使用receive方法持续监听
  4. 关闭DatagramSocket
发送者:
  1. 发送者将接收者IP地址与端口号 以及消息 封装到DatagramPacket 里
  2. 使用DatagramSocket 的send 发送消息
  3. 关闭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;
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值