大家或多或少的都听过Socket
这个单词,我们都知道Socket
是用于网络通讯中的某个步骤,那么Socket
到底是怎么一回事呢?我们下面来仔细说一下。
TCP/IP协议
要想理解Socket,那么就不得不先熟悉一下TCP/IP协议族。
TCP/IP(Transmission Control Protocol/Internet Protocol),既传输控制协议/网间协议,定义了主机如何接入因特网以及网络数据是如何在它们之间传播的。TCP/IP协议参考模型吧所有的TCP/IP系列协议归类到四个抽象层中:
- 应用层:TFTP, HTTP, SNMP, FTP, SMTP, DNS, Telnet等等
- 传输层:TCP, UDP
- 网络层:IP, ICMP, OSPF, EIGRP, IGMP
- 数据链路层:SLIP, CSLIP, PPP, MTU
而Socket是在应用层之下、传输层之上的一个接口层。是操作系统提供给用户访问网络的系统接口,我们可以借助于Socket接口层,对传输层、网络层以及数据链路层进行操作,来实现我们不同的应用层协议。对于应用层,我们想实现网络功能,归根到底都是要通过Socket来实现的。
TCP和UDP
UDP (User Data Protocol,用户数据协议):数据报通信协议。UDP是一种无连接的协议,这就意味着我们每次发送数据报时,需要同时发送本机的socket描述符和接收端的socket描述符。因此,我们在每次通信时都需要发送额外的数据。
TCP (Transfer Control Protocol,传输控制协议):流通讯协议,TCP是一种基于连接的协议。在使用流通信之前,我们必须在通信的一对儿socket之间建立连接。其中一个socket作为服务器进行监听连接请求。另一个则作为客户端进行连接请求。一旦两个socket建立好了连接,他们可以单向或双向进行数据传输。
UDP和TCP的区别:两者最大的区别在于,TCP是可靠的,也就是说,我们通过TCP发送的数据,网络协议栈会保证数据可靠的传输到对端,而UDP是不可靠的,如果出现丢包,协议栈不会做任何处理,可靠性的保证交由应用层处理。因此,TCP的性能会比UDP低,但是可靠性会比UDP好很多。
Android中实现Socket
Socket是进程间通讯的一种方式,被广泛的应用于网络间进程间通讯。Socket起源于Unix,是基于Unix的基本思想”一切皆文件”来设计的。所以Socket是一种特殊的文件,一些Socket函数也是对这个文件进行的操作(读/写IO、打开、关闭)。
Socket既然可以跨设备进行通讯,那么自然可以在同一个设备上通讯,这里我们通过一个Android的小demo来实现Socket通讯。
这个小程序实现了一个模拟聊天的功能,客户端发送消息到服务端,服务端收到消息后,返回一个随机的回复语到客户端,这些对话会显示到屏幕上。
客户端编写(源码)
开启Socket
在客户端启动一个Socket的方法十分简单:
//连接Socket String host = "localhost"; int port = 8688; socket = new Socket(host, port);
上面代码中,host即客户端需要连接的集齐,port就是服务端用来监听的请求端口。注意1024以内端口都是系统预留端口,这些端口为一些常用的服务所使用,比如邮件、FTP和HTTP。所以服务端的端口选择要大于1024。
写入数据
从客户端的socket对象中得到OutputStream对象,然后写入数据,类似文件的IO操作:
try { //连接Socket String host = "localhost"; int port = 8688; socket = new Socket(host, port); //从Socket中获取输出流 printWriter = new PrintWriter(new BufferedWriter( new OutputStreamWriter(socket.getOutputStream())), true); //发送连接成功消息,使button可点击 handler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED); Log.d(TAG, "connect server success"); } catch (IOException e) { SystemClock.sleep(1000); Log.e(TAG, "connect tcp server failed,retry ... "); }
获取服务端返回数据
此时我们已经发送消息到服务端,然后我们需要等待服务端返回的消息
try { //获取服务端的回复数据流 BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream())); while (!TCPClientActivity.this.isFinishing()) { //读取回复的消息 String msg = br.readLine(); Log.d(TAG, "receive:" + msg); //回复的消息拼接格式后,输出到客户端 if (msg != null) { String time = formatDateTime(System.currentTimeMillis()); final String showedMsg = "server " + time + ":" + msg + "\n"; handler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showedMsg).sendToTarget(); } } Log.d("TAG", "quit..."); MyUtils.close(printWriter); MyUtils.close(br); socket.close(); } catch (IOException e) { e.printStackTrace(); }
这里需要注意的是:在Android中,是不允许在主线程进行网络通讯的,所以我们需要在新的线程中进行socket操作,然后操作完成后调用Handler来更新UI,感兴趣的同学可以查看对应的源码。
关闭IO对象
最后在onDestory中,我们需要关闭对应的流对象,以确保资源的正确释放
protected void onDestroy() { //退出时候销毁对应流 if (clientSocket != null) { try { clientSocket.shutdownInput(); clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } super.onDestroy(); }
服务端编写(源码)
打开服务端Socket
下面代码我们创建了一个服务端的socket,然后调用accept方法来监听并获取客户端的请求socket。accept方法是一个阻塞方法,在服务端和客户端之间 建立联系之前会一直等待阻塞。
ServerSocket serverSocket = new ServerSocket(8688); Socket client = serverSocket.accept();
读取数据
得到client对象以后,我们就可以像文件操作一样来读取文件了,由于这里是个聊天室demo,所以我们加入while循环来不断获取客户单的消息
ServerSocket serverSocket = null; try { //建立一个端口为8688的服务 serverSocket = new ServerSocket(8688); } catch (IOException e) { Log.e(TAG, "establish tcp server failed,port:8688"); e.printStackTrace(); return; } while (!isServiceDestoryed) { try { //连接客户端Socket final Socket client = serverSocket.accept(); Log.d(TAG, "accept"); new Thread() { public void run() { try { //回复客户端 responseClient(client); } catch (IOException e) { e.printStackTrace(); } } }.start(); } catch (IOException e) { e.printStackTrace(); } }
同样的,上述方法也需要运行在非UI线程中。
回传数据到客户端
在上述代码中的回复客户端的方法,具体操作如下:
//获取客户端输入流 BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream())); //连接客户端输出流 PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true); out.print("欢迎来聊天室"); //如果没有断开连接 while (!isServiceDestoryed) { //读取下一行 String str = in.readLine(); Log.d(TAG, "msg from client:" + str); if (str == null) { break; } //随机获取一个回复的短语 int i = new Random().nextInt(definedMessages.length); String msg = definedMessages[i]; //在客户端输出 out.println(msg); Log.d(TAG, "send:" + msg); } Log.d(TAG, "client quit"); MyUtils.close(out); MyUtils.close(in); client.close();
最后记得连接断开后关闭对应的流。
总结
我们现在基本上都离不开互联网了,而网络之中浏览器与web通讯,QQ聊天等常见的通讯都离不开socket,所以即使作为客户端开发人员,了解一些socket知识也是十分必要的,希望这篇文章能够帮助大家对Socket有一个简单直观的了解。
相关文章: