最近在搞Android,对UDP通信也有了一定的认识,希望写篇文章好好总结下,也能对这块的学习能有更清晰的认识。
何为UDP?
国际惯例,先上度娘上的解释:
UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。UDP在IP报文的协议号是17。
UDP是网络协议的一种,网络协议是通信计算机双方必须共同遵从的一组约定。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流。它的三要素是:语法、语义、时序。
此外,说到UDP一般是会和TCP作比较来更好认识的:
- UDP是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户机和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
- TCP-传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。至于如何建立联系,可以自己去学习思考。
因此,UDP和TCP的区别包括:
- TCP面向连接(比如打电话需要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
- TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即无法保证可靠交付;
- UDP对系统资源要求较少,而TCP对系统资源要求相对较多;
- UDP支持一对一,一对多,多对一和多对多的交互通信,而每一条TCP连接只能是点到点的;
- UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
基于上述特点,UDP通常在音频、视频和普通数据在传送时使用较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。比如我们聊天QQ就有使用UDP协议。
UDP通信简单示例
相信大家都知道,进行网络通信,必须编写创建发送端和接收端,UDP亦如此。其中UDP数据包的发送,接收都要通过 一个java.net.DatagramSocket对象实现。DatagramSocket类表示发送和接送数据的套接字。我们看下该类的构造方法:
DatagramSocket
在发送前,首先要创一个DatagramPacket即数据 报对象,该对象中包含了要发送的数据,发送者的地址和目标地址,接着调用 DatagramSocket对象发送即可。发送数据报时,首要指定本地IP和端口创建发送的Socket对象。
代码如下:
public class DatagramSender {
public static void main(String args[]) throws Exception {
//①.创建要用来发送的本地地址对象
SocketAddress localAddr = new InetSocketAddress("192.168.1.147",13000);
//②.创建发送的Socket对象
DatagramSocket dSender = new DatagramSocket(localAddr);
int count=0;
while(true){
// 创建要发送的数据,字节数组
count++;
//③.要发送的数据
byte buffer[] = (count+"-hello").getBytes();
//④.发送数据的目标地址和端口
SocketAddress destAdd = new InetSocketAddress("192.168.1.149",
14000);
//⑤.创建要发送的数据包,指定内容,指定目标地址
DatagramPacket dp = new DatagramPacket(buffer, buffer.length,
destAdd);
dSender.send(dp);//⑥.发送
System.out.println("数据己发送: "+count);
Thread.sleep(1000);
}
}
}
最后,调用DatagramSocket发送对象UDP数据包。 调用了其 send方法,可立即将数据报发送到网络中
至于在这发送前,必然要有接收端的存在,代码如下:
public class DatagramReciver {
public static void main(String args[]) throws Exception {
//①.创建要用来发送的本地地址对象
SocketAddress localAddr = new
InetSocketAddress("192.168.1.149", 14000);
//②.接收的服务器UDP端口
DatagramSocket recvSocket = new DatagramSocket(localAddr);
while(true){
//③.指定接收缓冲区大小
byte[] buffer = new byte[20];
//④.创建接收数据包对象,指定接收大小
DatagramPacket packet = new DatagramPacket(buffer,
buffer.length);
//⑤.阻塞等待数据到来,如果收到数据,存入packet中的缓冲区中
System.out.println("UDP服务器等待接收数
据:"+recvSocket.getLocalSocketAddress());
recvSocket.receive(packet);
//得到发送方的ip和端口
SocketAddress address = packet.getSocketAddress();
//转换接收到的数据为字符串
String msg=new String(packet.getData()).trim();
//接收到后,打印出收到的数据长度
System.out.println("recv is:"+msg+" from:"+address);
}
}
}
在UDP中 的接收也会阻塞,与 TCP/IP Socket 的阻塞不同的它:它只是在读取一个数据包的时候阻塞。 recvSocket.receive(packet)这句代码会一直等待,到有数据包到达。
另外,我们还要用到DatagramPacket类,其实将数据字节填充到UDP包。用法很简单,DatagramSocket收发DatagramPacket即可。与TCP不同,UDP的socket并没有客户端和服务端的区别而统一应用此对象。同样的看下其构造方法:
DatagramPacket(byte[] buf, int length) //构造DatagramPacket,并接收长度为length的数据包
DatagramPacket(byte[] buf, int lenght, InetAddress address int port) //将长度为length的数据包发送到主机上指定端口号
DatagramSocket(byte[] buf, int offset, int lenght, InetAddress address int port) // 构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号
DatagramPacket(byte[] buf, int offset, int length) //构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) //构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号
DatagramPacket(byte[] buf, int length, SocketAddress address) //构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号
UDP向底层IP数据添加少部分内容:源端口、目的端口、IP数据部分长度和可选校验和,这些总共占用8字节。端口、地址、数据长度和数据等信息都可以从DatagramPacket中提取或者向其设置。
安卓与PC间
Android端代码:
public class MainActivity extends Activity {
DatagramSocket socket;//在收发信息的过程中只需要创建一个DatagramSocket对象即可
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
socket = new DatagramSocket(9999);
} catch (SocketException e1) {
e1.printStackTrace();
}
Button btn = (Button) this.findViewById(R.id.sendBtn);
final EditText et = (EditText) this.findViewById(R.id.sendtext);
TextView tv = (TextView) this.findViewById(R.id.receivetext); // 获取文本框的内容
// 发送信息的函数
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
*new Thread(new Runnable() {
public void run() {//在Android通信协议必须要放在线程里面进行**
String str = et.getText().toString();
Log.v("发送", str);
sendMsg(str);
}
}).start();
}
});
// 接收信息的函数
try {
receiveMsg(); //receiveMsg()函数也已经封装好,这里只需要拿来用即可,要体现java的封装思想**
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 定义发送消息的函数sendMsg()
*/
**public void sendMsg(String msg)** {
Log.v("v", "开始发送");
try {
**socket = new DatagramSocket(9999); //用DatagramSocket创建一个sokcet对象发送数据包,9999是本机的端口号
InetAddress server = InetAddress.getByName("192.168.31.207");
//获取本地主机的IP的地址,一般情况下使用InetAddress.getByName("IP")的形式,但是也可以用 getAllName()或者getLocalHost()的形式,得到本地主机的地址
byte data[] = msg.getBytes(); //将字符串转换成字节流,因为底层的传输都是字节传输
DatagramPacket pack = new DatagramPacket(data, data.length, server,
6024); // 创建DatagramPacket 对象数据包,这里的6024是我们要发送信息主机的端口号
socket.send(pack);**
Log.v("f", "发送成功!");
} catch (Exception e) {
Log.v("f", "发送失败!");
e.printStackTrace();
}
}
public void receiveMsg() throws Exception** {
// 创建线程,同理,接收信息也要放在线程里面接收
new Thread(new Runnable() {
public void run() {
if (socket != null) {
try {
String str;
while (true) {
// 创建一个空的字节数组
byte[] data = new byte[1024];
// 将字节数组和数组的长度传进DatagramPacket 创建的对象里面
DatagramPacket pack2 = new DatagramPacket(data,
data.length);**
Log.v("s", "pack2");
Log.v("s", "开始接收");
**socket.receive(pack2); // socket对象接收pack包,程序启动的时候,socket会一直处于阻塞状态,直到有信息传输进来**
String ip = pack2.getAddress().getHostAddress(); //这段代码的作用获取发送数据的IP地址
int port = pack2.getPort(); //获取发送数据端的端口号
System.out.println(ip);
System.out.println(port);
str = new String(pack2.getData(), 0, pack2.getLength()); // 将字节数组转化成字符串表现出来
Log.v("s", "接收成功: " + str);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
PC端代码:
public class UDPServer{
private SendandRecieveMsg srm;
public void UDPsetFrame(){
srm = new SendandRecieveMsg();
Frame frame = new Frame();
frame.setTitle("QQ");
frame.setSize(800, 800);
frame.setLayout(new FlowLayout()); //设置流式布局方式
frame.setLocationRelativeTo(null);
frame.setVisible(true); //设置可见
TextField tf = new TextField(20); //创建文本框, 20是指定的列数
Button senBtn = new Button("发送");
TextArea ta = new TextArea(20,30);//创建文本域
frame.add(tf); //添加进frame窗体里面
frame.add(senBtn);
frame.add(ta);
ActionLis ml = new ActionLis(tf,srm); //将文本框里面内容传进去
senBtn.addActionListener(ml);//给发送按钮加上监听器
//关闭窗口调用的函数
frame.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0); //这个函数的意思就是把整个窗体停用下来
}
});
while(true){
try {
**srm.UDPReceiveMsg(ta); //接收信息的socket不断接收信息,而且必须要放在最后,不断while会阻断其他程序的进行,UDPReceiveMsg()函数已经封装进SendandRecieveMsg类中**
} catch (SocketException e) {
e.printStackTrace();
}
}
}
public static void main(String[] arrys) throws IOException{
new UDPServer().UDPsetFrame(); //主函数,创建对象并启动
}
public class ActionLis implements ActionListener**{
private TextField tf;
private SendandRecieveMsg srm;
//构造函数
public ActionLis() {
}
public ActionLis(TextField tf, SendandRecieveMsg srm) { //构造函数将文本tf和srm传进来
this.tf = tf;
this.srm = srm;
}
public void actionPerformed(ActionEvent e) {
if(e.getActionCommand().equals("发送")){
srm.UDPSendMsg(tf.getText().trim()); //发送信息个Android端
System.out.println("发送的内容是:" + tf.getText().trim());
}
}
}
public class SendandRecieveMsg**{
DatagramSocket socket; //定义DatagramSocket类对象
public SendandRecieveMsg(){
try {
**socket = new DatagramSocket(6024); //这里6024是我们PC的程序的端口号**
} catch (SocketException e) {
e.printStackTrace();
}
}
/**
* 定义发送信息函数
*/
**public void UDPSendMsg(String msg)**{
if(socket != null) {
try {
**InetAddress server = InetAddress.getByName("192.168.31.175");
byte data[] = msg.getBytes();
DatagramPacket pack = new DatagramPacket(data, data.length, server, 9999); //这是对方网络软件的端口
socket.send(pack); //这段代码在Android端已经介绍过,所以在这里就不再详述**
System.out.println("发送成功");
} catch (Exception e) {
System.out.println("发送失败");
e.printStackTrace();
}
}
}
/**
* 定义接收信息函数
*/
**public void UDPReceiveMsg(TextArea ta) throws SocketException**{
String str = null;
while(true) {
//创建字节数组
byte[] data = new byte[100];
//将字节数组和数组的长度传进DatagramPacket里面
DatagramPacket pack1 = new DatagramPacket(data, data.length);
try {
System.out.println("启动");
**//socket对象接收pack包
socket.receive(pack1);
String ip = pack1.getAddress().getHostAddress(); //获取IP地址
int port = pack1.getPort();//这段代码在Android端已经介绍过,所以在这里就不再详述**
str = new String(pack1.getData(), 0, pack1.getLength());
str = ip + ":" + port + ":" + str;
ta.insert(str, pos); //将消息插到文本里面
if(str.equals("bye")){
socket.close(); //关闭接口
}
} catch (Exception e) {
System.out.println("发生异常");
e.printStackTrace();
}
}
}
}
小插曲:
除此之外,很有幸能在28号胡风范学长听到其在创新工场DeeCamp冬令营的收获的分享。其中有趣的是,面对对他羡慕不已的我们,师兄还在说自己是菜鸟,我们更是苦不堪言呐哈哈哈。师兄建议我们一定要打好基本功,再牛的项目也是由哪些基础的东西支撑起来的。再者就是合作的重要性,就比如我昨天刚结束的美赛,即使只是三个人,我也深刻感受到协作的重要性,也许我们三个人拉出来都能独挡一面,但是一个人的思维往往难以达到全面,即使是硬本事也难真正的全面发展。除此之外,多和牛人在一起是促使你发展的很好的一个选择。
另外经过这段时间的学习,我也发现从前的我还是稍稍有点畏首畏尾了,大胆尝试的心总是没有,怕自己技术不行,这不敢做那不敢做,导致一些到了眼前的机会甚至是自己喜欢的一些东西丧失了,因此,今后,没有什么不行的!!!
怀挺!