一、Socket 基础概念
Socket(套接字)是网络通信的基本操作单元,支持 TCP 和 UDP 两种协议:
1. TCP vs UDP 对比
特性 | TCP (传输控制协议) | UDP (用户数据报协议) |
---|---|---|
连接方式 | 面向连接(三次握手) | 无连接 |
可靠性 | 可靠传输,保证数据顺序 | 不保证可靠性和顺序 |
速度 | 较慢 | 较快 |
数据边界 | 字节流,无边界 | 数据报,有明确边界 |
适用场景 | 文件传输、网页浏览等 | 视频流、实时游戏等 |
二、TCP Socket 实现详解
1. 服务端实现
// 创建ServerSocket
ServerSocket serverSocket = new ServerSocket(8888); // 监听8888端口
// 接受客户端连接(阻塞方法)
Socket clientSocket = serverSocket.accept();
// 获取输入输出流
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(clientSocket.getOutputStream()), true);
// 读取客户端数据
String request = in.readLine();
System.out.println("收到客户端消息: " + request);
// 发送响应
out.println("Hello Client!");
// 关闭连接
clientSocket.close();
serverSocket.close();
2. 客户端实现
// 创建Socket并连接服务器
Socket socket = new Socket("192.168.1.100", 8888); // 服务器IP和端口
// 获取输入输出流
PrintWriter out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream()), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
// 发送请求
out.println("Hello Server!");
// 接收响应
String response = in.readLine();
System.out.println("收到服务端响应: " + response);
// 关闭连接
socket.close();
三、UDP Socket 实现详解
1. 服务端实现
// 创建DatagramSocket
DatagramSocket socket = new DatagramSocket(8888);
// 接收数据包
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet); // 阻塞方法
// 处理数据
String message = new String(packet.getData(), 0, packet.getLength());
System.out.println("收到UDP消息: " + message);
// 发送响应
InetAddress clientAddress = packet.getAddress();
int clientPort = packet.getPort();
byte[] responseData = "UDP Response".getBytes();
DatagramPacket responsePacket = new DatagramPacket(
responseData, responseData.length, clientAddress, clientPort);
socket.send(responsePacket);
// 关闭
socket.close();
2. 客户端实现
// 创建DatagramSocket
DatagramSocket socket = new DatagramSocket();
// 发送数据包
InetAddress serverAddress = InetAddress.getByName("192.168.1.100");
byte[] sendData = "Hello UDP Server".getBytes();
DatagramPacket sendPacket = new DatagramPacket(
sendData, sendData.length, serverAddress, 8888);
socket.send(sendPacket);
// 接收响应
byte[] receiveData = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
socket.receive(receivePacket);
// 处理响应
String response = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("收到UDP响应: " + response);
// 关闭
socket.close();
四、Android 特殊注意事项
1. 网络权限配置
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 如果需要访问非WiFi网络 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
2. 主线程限制
Android 不允许在主线程执行网络操作,必须使用工作线程:
new Thread(new Runnable() {
@Override
public void run() {
// Socket操作代码
runOnUiThread(() -> {
// 更新UI
});
}
}).start();
3. 使用 AsyncTask(已弃用但可参考)
private class SocketTask extends AsyncTask<Void, Void, String> {
protected String doInBackground(Void... params) {
// 执行Socket操作
return result;
}
protected void onPostExecute(String result) {
// 更新UI
}
}
4. 推荐替代方案
// 使用协程
lifecycleScope.launch(Dispatchers.IO) {
// Socket操作
withContext(Dispatchers.Main) {
// 更新UI
}
}
五、高级特性与优化
1. Socket 超时设置
// 连接超时(仅客户端)
Socket socket = new Socket();
socket.connect(new InetSocketAddress("192.168.1.100", 8888), 5000); // 5秒超时
// 读取超时
socket.setSoTimeout(3000); // 3秒超时
2. 保持长连接
// 启用Keep-Alive
socket.setKeepAlive(true);
// 自定义心跳机制
new Timer().schedule(new TimerTask() {
@Override
public void run() {
out.println("HEARTBEAT");
}
}, 0, 30000); // 每30秒发送心跳
3. 数据序列化
推荐使用 Protocol Buffers 或 JSON:
// 使用GSON发送对象
MyData data = new MyData("name", 123);
String json = new Gson().toJson(data);
out.println(json);
// 接收端解析
MyData receivedData = new Gson().fromJson(in.readLine(), MyData.class);
六、常见问题解决方案
1. 连接拒绝
-
检查服务端是否启动
-
检查IP和端口是否正确
-
检查防火墙设置
2. 数据读取阻塞
-
设置合理的超时时间
socket.setSoTimeout()
-
使用特定结束标记(如换行符)
-
指定数据长度前缀
3. Android 9+ 网络限制
-
默认禁止明文传输(HTTP),需:
<application android:usesCleartextTraffic="true">
配置网络安全策略
七、完整示例(TCP 聊天应用)
服务端代码
public class TcpServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器启动,等待连接...");
while (true) {
Socket clientSocket = serverSocket.accept();
new Thread(new ClientHandler(clientSocket)).start();
}
}
static class ClientHandler implements Runnable {
private Socket socket;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream()), true)) {
String clientMessage;
while ((clientMessage = in.readLine()) != null) {
System.out.println("收到: " + clientMessage);
out.println("服务器回复: " + clientMessage);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Android 客户端代码
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val handler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnConnect.setOnClickListener { connectToServer() }
binding.btnSend.setOnClickListener { sendMessage() }
}
private fun connectToServer() {
thread {
try {
val socket = Socket("192.168.1.100", 8888)
val output = PrintWriter(socket.getOutputStream(), true)
val input = BufferedReader(InputStreamReader(socket.getInputStream()))
// 保持连接并监听消息
while (true) {
val message = input.readLine() ?: break
handler.post { binding.tvMessages.append("服务器: $message\n") }
}
} catch (e: Exception) {
handler.post { Toast.makeText(this, "连接失败: ${e.message}", Toast.LENGTH_SHORT).show() }
}
}
}
private fun sendMessage() {
val message = binding.etMessage.text.toString()
if (message.isNotEmpty()) {
thread {
try {
val socket = Socket("192.168.1.100", 8888)
PrintWriter(socket.getOutputStream(), true).println(message)
socket.close()
handler.post { binding.tvMessages.append("我: $message\n") }
} catch (e: Exception) {
handler.post { Toast.makeText(this, "发送失败", Toast.LENGTH_SHORT).show() }
}
}
}
}
}
八、性能优化建议
-
连接池管理:对频繁通信的场景使用连接池
-
缓冲区优化:根据数据量调整缓冲区大小
-
NIO 替代方案:考虑使用
java.nio
包的非阻塞IO -
协议设计:定义明确的应用层协议(如消息头+消息体)
-
流量控制:实现滑动窗口等机制防止数据淹没
通过掌握这些 Socket 编程技术,您可以在 Android 应用中实现高效的网络通信功能。