欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

详解Android 基于TCP和UDP协议的Socket通信

程序员文章站 2023-08-22 20:01:12
本来想讲一下基础的网络通信方面的知识点,发现太枯燥乏味了,不过笔试中也经常会问到这方面的问题,所以关于通信方面的知识点,小编会放到面试中去,因为实战中也就面试会用到这方面知...

本来想讲一下基础的网络通信方面的知识点,发现太枯燥乏味了,不过笔试中也经常会问到这方面的问题,所以关于通信方面的知识点,小编会放到面试中去,因为实战中也就面试会用到这方面知识点

android与服务器的通信方式主要有两种,一是http通信,一是socket通信。两者的最大差异在于,http连接使用的是“请求—响应方式”,即在请求时建立连接通道,当客户端向服务器发送请求后,服务器端才能向客户端返回数据。

而socket通信中基于tcp/ip协议的通信则是在双方建立起连接后就可以直接进行数据的传输,在连接时可实现信息的主动推送,而不需要每次由客户端想服务器发送请求。而udp则是提供无连接的数据报服务,udp在发送数据报前不需建立连接,不对数据报进行检查即可发送数据包

1.什么是socket?

详解Android 基于TCP和UDP协议的Socket通信

2.socket通信模型:

详解Android 基于TCP和UDP协议的Socket通信

socket通信实现步骤解析:

step 1:创建serversocket和socket

step 2:打开连接到的socket的输入/输出流

step 3:按照协议对socket进行读/写操作

step 4:关闭输入输出流,以及socket

好的,我们接下来写一个简单的例子,开启服务端后,客户端点击按钮然后链接服务端, 并向服务端发送一串字符串,表示通过socket链接上服务器~

一、1.基于tcpsocket服务端的编写:

服务端要做的事有这些:

step 1 :创建serversocket对象,绑定监听的端口

step 2 :调用accept()方法监听客户端的请求

step 3 :连接建立后,通过输入流读取客户端发送的请求信息

step 4 :通过输出流向客户端发送响应信息 step 5 :关闭相关资源

代码实现:

创建一个java项目,然后把java代码贴进去即可!这里可以用eclipse来写服务端,as来写安卓端

public class socketserver {
 public static void main(string[] args) throws ioexception {
  //1.创建一个服务器端socket,即serversocket,指定绑定的端口,并监听此端口
  serversocket serversocket = new serversocket(12345);
  inetaddress address = inetaddress.getlocalhost();
  string ip = address.gethostaddress();
  socket socket = null;
  //2.调用accept()等待客户端连接
  system.out.println("~~~服务端已就绪,等待客户端接入~,服务端ip地址: " + ip);
  socket = serversocket.accept();
  //3.连接后获取输入流,读取客户端信息
  inputstream is=null;
  inputstreamreader isr=null;
  bufferedreader br=null;
  outputstream os=null;
  printwriter pw=null;
  is = socket.getinputstream();  //获取输入流
  isr = new inputstreamreader(is,"utf-8");
  br = new bufferedreader(isr);
  string info = null;
  while((info=br.readline())!=null){//循环读取客户端的信息
   system.out.println("客户端发送过来的信息" + info);
  }
  socket.shutdowninput();//关闭输入流
  socket.close();
 }
}

然后我们把代码run起来,控制台会打印:

详解Android 基于TCP和UDP协议的Socket通信

好的,接下来到android客户端了!

2.socket客户端的编写: 客户端要做的事有这些:

step 1 :创建socket对象,指明需要链接的服务器的地址和端号

step 2 :链接建立后,通过输出流向服务器发送请求信息

step 3 :通过输出流获取服务器响应的信息

step 4 :关闭相关资源

代码实现:

mainactivity.java:

public class mainactivity extends appcompatactivity implements view.onclicklistener {

 @override
 protected void oncreate(bundle savedinstancestate) {
  super.oncreate(savedinstancestate);
  setcontentview(r.layout.activity_main);
  button btn_accept = (button) findviewbyid(r.id.btn_accept);
  btn_accept.setonclicklistener(this);
 }

 @override
 public void onclick(view v) {
  new thread() {
   @override
   public void run() {
    try {
     acceptserver();
    } catch (ioexception e) {
     e.printstacktrace();
    }
   }
  }.start();
 }

 private void acceptserver() throws ioexception {
  //1.创建客户端socket,指定服务器地址和端口
  socket socket = new socket("172.16.2.54", 12345);
  //2.获取输出流,向服务器端发送信息
  outputstream os = socket.getoutputstream();//字节输出流
  printwriter pw = new printwriter(os);//将输出流包装为打印流
  //获取客户端的ip地址
  inetaddress address = inetaddress.getlocalhost();
  string ip = address.gethostaddress();
  pw.write("客户端:~" + ip + "~ 接入服务器!!");
  pw.flush();
  socket.shutdownoutput();//关闭输出流
  socket.close();
 }
}

因为android不允许在主线程(ui线程)中做网络操作,所以这里需要我们自己 另开一个线程来连接socket!

运行结果:

点击按钮后,服务端控制台打印:

详解Android 基于TCP和UDP协议的Socket通信

3.简易聊天室

那么通过上面的案例,我们就可以做一个简单的聊天软件,这里知道怎么实现的就可以了,实战中我们都是采用的第三方api,比如网易云,我会专门写一个网易云的im通信

实现的效果图:

先把我们的服务端跑起来:

详解Android 基于TCP和UDP协议的Socket通信

接着把我们的程序分别跑到两台模拟器上:

详解Android 基于TCP和UDP协议的Socket通信

接下来我们来写代码:

首先是服务端,就是将读写socket的操作放到自定义线程当中,创建serversocket后,循环 调用accept方法,当有新客户端接入,将socket加入集合当中,同时在线程池新建一个线程!

另外,在读取信息的方法中,对输入字符串进行判断,如果为bye字符串,将socket从集合中 移除,然后close掉!

public class server {
 //定义相关的参数,端口,存储socket连接的集合,serversocket对象
 //以及线程池
 private static final int port = 12345;
 private list<socket> mlist = new arraylist<socket>();
 private serversocket server = null;
 private executorservice myexecutorservice = null;
 
 
 public static void main(string[] args) {
  new server();
 }

 public server()
 {
  try
  {
   server = new serversocket(port);
   //创建线程池
   myexecutorservice = executors.newcachedthreadpool();
   system.out.println("服务端运行中...\n");
   socket client = null;
   while(true)
   {
    client = server.accept();
    mlist.add(client);
    myexecutorservice.execute(new service(client));
   }
   
  }catch(exception e){e.printstacktrace();}
 }
 
 class service implements runnable
 {
  private socket socket;
  private bufferedreader in = null;
  private string msg = "";
  
  public service(socket socket) {
   this.socket = socket;
   try
   {
    in = new bufferedreader(new inputstreamreader(socket.getinputstream()));
     msg = "用户:" +this.socket.getinetaddress() + "~加入了聊天室" 
       +"当前在线人数:" +mlist.size(); 
    this.sendmsg();
   }catch(ioexception e){e.printstacktrace();}
  }
  
  
  
  @override
  public void run() {
   try{
    while(true)
    {
     if((msg = in.readline()) != null)
     {
      if(msg.equals("bye"))
      {
       system.out.println("~~~~~~~~~~~~~");
       mlist.remove(socket);
       in.close();
       msg = "用户:" + socket.getinetaddress() 
         + "退出:" +"当前在线人数:"+mlist.size(); 
       socket.close(); 
       this.sendmsg(); 
       break;
      }else{
       msg = socket.getinetaddress() + " 说: " + msg; 
       this.sendmsg(); 
      }
     }
    }
   }catch(exception e){e.printstacktrace();}
  }
  
  //为连接上服务端的每个客户端发送信息
  public void sendmsg()
  {
   system.out.println(msg);
   int num = mlist.size();
   for(int index = 0;index < num;index++)
   {
    socket msocket = mlist.get(index); 
    printwriter pout = null; 
    try { 
     pout = new printwriter(new bufferedwriter( 
       new outputstreamwriter(msocket.getoutputstream(),"utf-8")),true); 
     pout.println(msg); 
    }catch (ioexception e) {e.printstacktrace();} 
   }
  }
  
 }
}

接着到客户端,客户端的难点在于要另外开辟线程的问题,因为android不允许直接在 主线程中做网络操作,而且不允许在主线程外的线程操作ui,这里的做法是自己新建 一个线程,以及通过hanlder来更新ui,实际开发不建议直接这样做!!!

布局文件:activity_main.xml:

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical" >

 <textview
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="小猪简易聊天室" />
 <textview
  android:id="@+id/txtshow"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  />
 <edittext
  android:id="@+id/editsend"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  />
 <button
  android:id="@+id/btnsend"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="发送"
  />
</linearlayout>

mainactivity.java:

public class mainactivity extends appcompatactivity implements runnable {

 //定义相关变量,完成初始化
 private textview txtshow;
 private edittext editsend;
 private button btnsend;
 private static final string host = "172.16.2.54";
 private static final int port = 12345;
 private socket socket = null;
 private bufferedreader in = null;
 private printwriter out = null;
 private string content = "";
 private stringbuilder sb = null;

 //定义一个handler对象,用来刷新界面
 public handler handler = new handler() {
  public void handlemessage(message msg) {
   if (msg.what == 0x123) {
    sb.append(content);
    txtshow.settext(sb.tostring());
   }
  }

  ;
 };


 @override
 protected void oncreate(bundle savedinstancestate) {
  super.oncreate(savedinstancestate);
  setcontentview(r.layout.activity_main);
  sb = new stringbuilder();
  txtshow = (textview) findviewbyid(r.id.txtshow);
  editsend = (edittext) findviewbyid(r.id.editsend);
  btnsend = (button) findviewbyid(r.id.btnsend);

  //当程序一开始运行的时候就实例化socket对象,与服务端进行连接,获取输入输出流
  //因为4.0以后不能再主线程中进行网络操作,所以需要另外开辟一个线程
  new thread() {

   public void run() {
    try {
     socket = new socket(host, port);
     in = new bufferedreader(new inputstreamreader(socket.getinputstream(), "utf-8"));
     out = new printwriter(new bufferedwriter(new outputstreamwriter(
       socket.getoutputstream())), true);
    } catch (ioexception e) {
     e.printstacktrace();
    }
   }
  }.start();

  //为发送按钮设置点击事件
  btnsend.setonclicklistener(new view.onclicklistener() {

   @override
   public void onclick(view v) {
    string msg = editsend.gettext().tostring();
    if (socket.isconnected()) {
     if (!socket.isoutputshutdown()) {
      out.println(msg);
     }
    }
   }
  });
  new thread(mainactivity.this).start();
 }

 //重写run方法,在该方法中输入流的读取
 @override
 public void run() {
  try {
   while (true) {
    if (socket.isconnected()) {
     if (!socket.isinputshutdown()) {
      if ((content = in.readline()) != null) {
       content += "\n";
       handler.sendemptymessage(0x123);
      }
     }
    }
   }
  } catch (exception e) {
   e.printstacktrace();
  }
 }
}

二、基于udp协议的socket通信

tcp和udp最大的区别在于是否需要客户端与服务端建立连接后才能进行 数据传输,

tcp:传输前先开服务端,accept,等客户端接入,然后获得 客户端socket然后进行io操作,而udp则不用

udp:以数据报作为数据的传输载体,在进行传输时 首先要把传输的数据定义成数据报(datagram),在数据报中指明数据要到达的socket(主机地址 和端口号),然后再将数据以数据报的形式发送出去

1.服务端实现步骤:

step 1:创建datagramsocket,指定端口号

step 2:创建datagrampacket

step 3:接收客户端发送的数据信息

step 4:读取数据

示例代码:

public class updserver {
 public static void main(string[] args) throws ioexception {
  /*
   * 接收客户端发送的数据
   */
  // 1.创建服务器端datagramsocket,指定端口
  datagramsocket socket = new datagramsocket(12345);
  // 2.创建数据报,用于接收客户端发送的数据
  byte[] data = new byte[1024];// 创建字节数组,指定接收的数据包的大小
  datagrampacket packet = new datagrampacket(data, data.length);
  // 3.接收客户端发送的数据
  system.out.println("****服务器端已经启动,等待客户端发送数据");
  socket.receive(packet);// 此方法在接收到数据报之前会一直阻塞
  // 4.读取数据
  string info = new string(data, 0, packet.getlength());
  system.out.println("我是服务器,客户端说:" + info);

  /*
   * 向客户端响应数据
   */
  // 1.定义客户端的地址、端口号、数据
  inetaddress address = packet.getaddress();
  int port = packet.getport();
  byte[] data2 = "欢迎您!".getbytes();
  // 2.创建数据报,包含响应的数据信息
  datagrampacket packet2 = new datagrampacket(data2, data2.length, address, port);
  // 3.响应客户端
  socket.send(packet2);
  // 4.关闭资源
  socket.close();
 }
}

2.客户端实现步骤:

step 1:定义发送信息

step 2:创建datagrampacket,包含将要发送的信息

step 3:创建datagramsocket

 step 4:发送数据

public class udpclient {
 public static void main(string[] args) throws ioexception {
  /*
   * 向服务器端发送数据
   */
  // 1.定义服务器的地址、端口号、数据
  inetaddress address = inetaddress.getbyname("localhost");
  int port = 8800;
  byte[] data = "用户名:admin;密码:123".getbytes();
  // 2.创建数据报,包含发送的数据信息
  datagrampacket packet = new datagrampacket(data, data.length, address, port);
  // 3.创建datagramsocket对象
  datagramsocket socket = new datagramsocket();
  // 4.向服务器端发送数据报
  socket.send(packet);

  /*
   * 接收服务器端响应的数据
   */
  // 1.创建数据报,用于接收服务器端响应的数据
  byte[] data2 = new byte[1024];
  datagrampacket packet2 = new datagrampacket(data2, data2.length);
  // 2.接收服务器响应的数据
  socket.receive(packet2);
  // 3.读取数据
  string reply = new string(data2, 0, packet2.getlength());
  system.out.println("我是客户端,服务器说:" + reply);
  // 4.关闭资源
  socket.close();
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。