详解Android 基于TCP和UDP协议的Socket通信
本来想讲一下基础的网络通信方面的知识点,发现太枯燥乏味了,不过笔试中也经常会问到这方面的问题,所以关于通信方面的知识点,小编会放到面试中去,因为实战中也就面试会用到这方面知识点
android与服务器的通信方式主要有两种,一是http通信,一是socket通信。两者的最大差异在于,http连接使用的是“请求—响应方式”,即在请求时建立连接通道,当客户端向服务器发送请求后,服务器端才能向客户端返回数据。
而socket通信中基于tcp/ip协议的通信则是在双方建立起连接后就可以直接进行数据的传输,在连接时可实现信息的主动推送,而不需要每次由客户端想服务器发送请求。而udp则是提供无连接的数据报服务,udp在发送数据报前不需建立连接,不对数据报进行检查即可发送数据包
1.什么是socket?
2.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客户端了!
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!
运行结果:
点击按钮后,服务端控制台打印:
3.简易聊天室
那么通过上面的案例,我们就可以做一个简单的聊天软件,这里知道怎么实现的就可以了,实战中我们都是采用的第三方api,比如网易云,我会专门写一个网易云的im通信
实现的效果图:
先把我们的服务端跑起来:
接着把我们的程序分别跑到两台模拟器上:
接下来我们来写代码:
首先是服务端,就是将读写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(); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读