基于Java的TCP Socket通信详解(计算机端/Android手机端)

TCP Socket通信是一种比较常用的基于连接的网络通信方式。本文通过Java实现TCP Socket通信,并将其用于计算机端、Android手机端,同时做到代码规范化,实现代码最大化复用。

本文代码可在GitHub下载,建议对照源码阅读文章 https://github.com/jzj1993/JavaTcpSocket

TCP连接的建立

客户端和服务器间通过三次握手建立TCP连接。在Java中,连接建立完成后,服务器端和客户端分别获取到一个Socket实例,之后就可以通过这个Socket实例进行通信。服务器端和客户端使用不同的方法获取Socket实例。

服务器端

在服务器端,通过ServerSocket实现对指定端口的监听,代码如下。其中portint型端口数值,取值0~655350~1024为系统保留端口,这里取值1234。如果发生错误将会抛出异常。

  1. int port = 1234;
  2. ServerSocket server = new ServerSocket(port);

通过ServerSocket.accept()方法接受客户端连接。这个方法是阻塞的,从调用时开始监听端口,直到客户端连接建立时,执行结束并返回Socket实例。连接建立失败会抛出异常。

  1. Socket socket = server.accept();

客户端

客户端直接通过实例化的形式,产生Socket实例。实例化的过程中,尝试连接指定的服务器主机。连接成功则实例化完成,连接失败则抛出异常。hostIP为主机的IP地址,port为端口号,和服务器主机监听的端口号保持一致。

  1. String hostIP = "127.0.0.1";
  2. int port = 1234;
  3. Socket socket = new Socket(hostIP, port);

连接的建立过程

以上代码的执行顺序是:

  1. 服务器端实例化ServerSocket:new ServerSocket(port);
  2. 服务器端执行accept(),监听指定端口,此方法阻塞等待客户端连接:server.accept();
  3. 客户端实例化Socket实例,尝试连接服务器:new Socket(hostIP, port);
  4. TCP三次握手成功,服务器端的accept()返回Socket实例,同时客户端的Socket实例化成功。

Socket的读写

以收发字符串为例来说明Socket的读写。

向Socket对象写入数据,则会发送至TCP连接的另一方。这个操作在服务器端和客户端是一样的。可通过获取Socket的输出流来写入UTF8格式编码的字符串,代码如下。写入完成后,就会被发送到连接的另一端。

  1. private DataOutputStream out;
  2. out = new DataOutputStream(socket.getOutputStream());
  3. String s = "Test";
  4. out.writeUTF(s);
  5. out.flush();

在接收端,通过获取Socket的输入流,就可以读取字符串数据,代码如下。readUTF()方法是阻塞的,直到对方发送完一个字符串,该方法才会执行结束并返回收到的字符串。如果连接中断,或强制关闭Socket的输入流,即执行socket.shutdownInput(),该方法会抛出异常。

  1. private DataInputStream in;
  2. in = new DataInputStream(socket.getInputStream());
  3. String s = in.readUTF();

在建立了TCP连接后,由于无法确定对方的数据发送时间,为了保证及时接收到数据,通过一个新线程不断调用in.readUTF()方法读取数据(相当于轮询法);并在接收到数据后回调相关函数,对数据进行处理。

TCP连接的断开

TCP Socket连接是双向的,通过四次挥手的方式断开,双方分别调用Socket.close()方法断开连接。连接断开的过程中,一般一方A先断开连接,另一方B发现A断开连接后,也断开连接。为方便表述,将先断开连接的一方A称为“主动断开连接”;后断开的一方B,则为“被动断开连接”。

在一方B阻塞执行in.readUTF()方法时,如果对方A主动断开Socket连接,这个方法会抛出异常。从而在B处理异常时,可以被动的断开这边的连接。

为保证主动断开连接的一方不会阻塞在in.readUTF()方法中,需要先执行socket.shutdownInput()。所以主动断开连接的代码如下。

  1. socket.shutdownInput();
  2. in.close();
  3. socket.close();

被动断开连接的一方,在捕获到in.readUTF()的异常后,断开Socket连接。

  1. try {
  2. String s = in.readUTF();
  3. } catch (IOException e) {
  4. // 连接被断开(被动)
  5. try {
  6. in.close();
  7. socket.close();
  8. in = null;
  9. socket = null;
  10. } catch (IOException e) {
  11. e.printStackTrace();
  12. }
  13. }

SocketTransceiver的实现

考虑到在服务器端和客户端,Socket对象的操作是完全一样的,所以实现了一个SocketTransceiver(收发器),实现对Socket的直接操作,其他代码则通过SocketTransceiver间接操作Socket对象实现数据收发、断开连接等。

SocketTransceiver实现的功能有:

  • 开启新线程不断查询Socket是否收到数据;
  • 将字符串、文件等类型的数据进行打包,并通过Socket发送;
  • 从Socket接收数据,并自动解析出数据(字符串、文件等),接收完成后回调相应的方法;
  • 在发生错误、连接被动断开时,自动断开连接并进行相关处理,并回调相应方法。

SocketTransceiver.class使用抽象类实现,回调方法是抽象的,实例化时对抽象方法进行实现,处理回调。完整代码见附件。

TcpServer的实现

TcpServer为TCP Socket服务器端程序。为了让服务器能同时接受并处理来自多个客户端的TCP连接请求:

  • TcpServer中用一个监听线程对端口进行监听,即阻塞执行server.accept()方法,等待接受客户端连接;
  • 服务器端每次与一个客户端建立连接,即accept()方法执行结束并返回一个Socket对象,就会用一个SocketTransceiver对这个Socket进行操作;
  • 连接建立后,监听线程再次执行server.accept()方法,继续监听端口并等待下一个连接;
  • 服务器端有一个List<SocketTransceiver>,保存当前连接的每个客户端对应的SocketTransceiver对象,在需要时可取出并进行操作。

TcpServer.class的完整代码见附件。

TcpClient的实现

TcpClient为TCP Socket客户端程序。主要工作是进行Socket的连接,并利用SocketTransceiver对Socket进行操作。TcpClient.class的完整代码见附件。

桌面服务器端的测试代码

完成了上面这三个主要的类,服务器端的实现就非常容易了。服务器端需要使用SocketTransceiver.classTcpServer.class两个类。

服务器端编写了一个桌面版本,测试代码ClsMainServer.class在工程SocketServer-Desktop中,详见附件。在测试代码中,实现了一个服务器程序,监听端口1234并接受客户端连接,每次接收到客户端主动发送的数据,就将数据原样返回给这个客户端。

桌面客户端的测试代码

客户端需要使用SocketTransceiver.classTcpClient.class两个类。

客户端的桌面版本测试代码ClsMainClient.class在工程SocketClient-Desktop中,详见附件。在测试代码中,实现了两个客户端,并交替向服务器发送数据。客户端直接连接本机服务器端,IP地址为127.0.0.1

安卓客户端的实现

同样的代码还可以直接用于安卓客户端。安卓客户端编写了一个简单的界面,输入IP和端口可进行连接,同时可以输入字符串发送至服务器端,另外会将接收到的数据显示出来。

安卓端程序的实现,要注意几点:

  • 网络操作代码不能在主线程(即UI线程)中执行
  • 界面操作不能在非UI线程执行,回调中不能直接执行刷新界面的代码,可以通过向UI线程的Handler发送Runnable对象进行操作,即Handler.post(Runnable
    r)
    方法。
  • 需要在AndroidManifest.xml中添加网络访问权限<uses-permission
    android:name="android.permission.INTERNET" />

测试可用的代码详见附件中的工程SocketClient-Android

网络连接相关问题

如果代码编译通过,但是测试不能正常运行,则有可能为网络连接配置相关问题。

  1. 电脑和手机之间的网络连接方式

    • 电脑共享Wifi,用手机连接
    • 手机电脑连在一个局域网
  2. 确保IP地址与端口设置正确

    确保程序中的IP地址和端口设置正确。服务器端代码只需设置端口,最好不要处于0~1024之间,并避免端口被系统其它程序占用;在客户端,使用的端口和服务器相同,IP地址设置如下:

    • 如果客户端和服务器是在同一个设备上,IP地址可以直接用127.0.0.1,也可以用服务器任意网卡的IP地址
    • 如果服务器和客户端处于同一个局域网,IP地址用服务器连接到该局域网的网卡的IP地址
    • 如果客户端连接到服务器共享的Wifi网络,IP地址用服务器共享Wifi的无线网卡的IP地址
  3. 防火墙设置

    在Windows中,需要设置防火墙允许程序访问网络。通常在软件第一次访问网络时,会弹出窗口提示是否允许程序访问网络,勾选允许并点击确定即可。也可以在控制面板设置:控制面板 --> 系统和安全 --> Windows 防火墙 --> 允许程序或功能通过Windows防火墙 --> 更改设置 --> 勾选Java后面的选框并确认即可,如图。

  4. 尝试用管理员权限运行Eclipse

    如果仍然不能连接,可以尝试用管理员权限运行Eclipse。

代码测试

Eclipse窗口操作技巧

  • 默认情况下,多个程序同时在命令行输出时,Eclipse的Console会自动切换到最后输出字符串的程序,点击图钉形状的按钮如图,可以让Console不再切换。
  • 点击右侧的加号按钮,下拉菜单中选择New Console View,可以新建一个Console命令行窗口,设置每个窗口显示不同程序输出的内容。
  • 点击Console中的红色方形按钮可以停止程序的运行。

测试

将工程SocketServer-DesktopSocketClient-Desktop导入Eclipse中,先执行服务器端,再执行客户端。如果先执行客户端,会提示连接失败。

桌面客户端程序启动后会创建两个TcpClient客户端实例同时连接服务器,并交替发送数据。客户端连接和发送数据时,服务器端会输出相应的提示信息。

停止服务器端程序,客户端会提示连接中断,并中止程序执行;停止客户端程序,服务器端会提示客户端断开连接。

将安卓版的Client编译并安装到手机,连接网络让安卓端和计算机端在同一个局域网中。让服务器端程序启动,再点击安卓端的“连接”按钮进行连接,如果网络错误、服务器未启动等原因,安卓端会提示“连接失败”。如果连接成功,服务端会输出客户端连接的提示信息。

同样,可以在安卓客户端向服务器发送数据,服务器会将数据原样返回。

执行结果如图。

本文首发自我的个人主页,转载请注明来源:http://www.hainter.com/java-tcp-socket

时间: 2024-10-11 13:41:20

基于Java的TCP Socket通信详解(计算机端/Android手机端)的相关文章

java网络编程Socket通信详解

Java最初是作为网络编程语言出现的,其对网络提供了高度的支持,使得客户端和服务器的沟通变成了现实,而在网络编程中,使用最多的就是Socket.像大家熟悉的QQ.MSN都使用了Socket相关的技术.下面就让我们一起揭开Socket的神秘面纱. Socket编程 一.网络基础知识(参考计算机网络)            关于计算机网络部分可以参考相关博客:           <TCP/IP协议栈及OSI参考模型详解> http://wangdy.blog.51cto.com/3845563/

Java开发之Socket编程详解

本文从3个方面对Socket编程进行详解: 一,网络编程中两个主要的问题 二,两类传输协议:TCP:UDP 三,基于Socket的java网络编程 一,网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机.而TCP层则提供面向应用的可靠(tcp)的或非可靠(UDP)的数据传输机制,这是网络编程的主要对象,一般不需要

基于多线程的TCP socket通信经典案例

服务器端 package com.thinkvenus.study.socket; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; /** *

基于Java NIO的Socket通信

Java NIO模式的Socket通信,是一种同步非阻塞IO设计模式,它为Reactor模式实现提供了基础. 下面看看,Java实现的一个服务端和客户端通信的例子. NIO模式的基本原理描述如下: 服务端打开一个通道(ServerSocketChannel),并向通道中注册一个选择器(Selector),这个选择器是与一些感兴趣的操作的标识(SelectionKey,即通过这个标识可以定位到具体的操作,从而进行响应的处理)相关联的,然后基于选择器(Selector)轮询通道(ServerSock

Android中实现java与PHP服务器(基于新浪云免费云平台)http通信详解

Android中实现java与PHP服务器(基于新浪云免费云平台)http通信详解 (本文转自: http://blog.csdn.net/yinhaide/article/details/44756989) 前言:现在很多APP都需要云的功能,也就是通过网络与服务器交换数据.有的采用tcp/ip协议,但是你必须拥有一个固定ip的服务器,可以购买阿里云服务器之类的,就是贵了点.如果只是个人的小应用的的话可以采用新浪云平台这种免费的服务器,采用的协议是http协议,具体实现方式如下: 方式一.在线

Java网络编程和NIO详解9:基于NIO的网络编程框架Netty

Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introduction/ netty是基于NIO实现的异步事件驱动的网络编程框架,学完NIO以后,应该看看netty的实现,netty框架涉及的内容特别多,这里只介绍netty的基本使用和实现原理,更多扩展的内容将在以后推出. 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎

Java串口通信详解(转)

Java串口通信详解(转) 作者:denimcc 日期:2007-05-11 序言    说到开源,恐怕很少有人不挑大指称赞.学生通过开源代码学到了知识,程序员通过开源类库获得了别人的成功经验及能够按时完成手头的工程,商家通过开源软件赚到了钱……,总之是皆大欢喜.然而开源软件或类库的首要缺点就是大多缺乏详细的说明文档和使用的例子,或者就是软件代码随便你用,就是文档,例子和后期服务收钱.                                                        

【Java TCP/IP Socket】TCP Socket通信中由read返回值造成的的死锁问题(含代码)

书上示例 在第一章<基本套接字>中,作者给出了一个TCP Socket通信的例子——反馈服务器,即服务器端直接把从客户端接收到的数据原原本本地反馈回去. 书上客户端代码如下: import java.net.Socket; import java.net.SocketException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class TCPEch

Java网络编程和NIO详解3:IO模型与Java网络编程模型

Java网络编程和NIO详解3:IO模型与Java网络编程模型 基本概念说明 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方).操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限.为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间.针对linux操作系统而言,将最高的1G字节(从虚拟地址