Socket(套接字)是一种通信机制,可以实现单机或跨网络进行通信,其创建需要明确的区分C(客户端)/S(服务器端),支持多个客户端连接到同一个服务器。
在安卓中,直接采用Socket通信应该是我们遇到的最低级的网络运用。尽管已经作了很大程度的抽象,但是纯粹的Socket通信,仍然给开发者留下很多细节需要处理,尤其在服务器端,开发者需要处理多线程以及数据缓冲等的设计问题。相对而言,处于更高抽象层的HTTP等,已经对Socket通信中需要处理的技术细节进行了很好的封装,开发者无须关心,因此,HTTP在网络开发中通常具有决定性的优势。
ServerSocket(int aport):创建一个绑定到本机指定端口的服务端Socket;aport就是指定的本机端口。与上述客户端Socket对应,通过TCP连接时,ServerSocket创建后需要在aport端口上进行监听,等待客户端的连接。
package net.hw.chat;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 功能:聊天服务窗口类
* 作者:华卫
* 日期:2021年01月01日
*/
public class ChatServerWindow extends JFrame {
/**
* 定义端口号常量
*/
static final int PORT = 8888;
private JButton btnClose;
private JButton btnSend;
/**
* 数据输入流
*/
private DataInputStream netIn;
/**
* 数据输出流
*/
private DataOutputStream netOut;
private JScrollPane panContent;
private JScrollPane panInput;
private JPanel panel1;
private JPanel panel2;
/**
* 服务器端套接字
*/
private static ServerSocket ss;
/**
* 客户端套接字
*/
private static Socket socket;
/**
* 聊天信息列表
*/
private JTextArea txtChatMessageList;
/**
* 聊天信息输入框
*/
private JTextArea txtInputMessage;
/**
* 来自客户端的消息
*/
private static String clientMsg;
/**
* 服务器端的消息
*/
private static String serverMsg;
/**
* 线程循环控制变量
*/
private static boolean isRunning;
public static void main(String[] args) {
new ChatServerWindow();
}
/**
* 构造方法
*/
public ChatServerWindow() {
super("聊天服务器端");
initUI();
try {
// 创建服务器端套接字
ss = new ServerSocket(PORT);
txtChatMessageList.append("服务器已启动...\n");
txtChatMessageList.append("等待客户请求...\n");
isRunning = true;
new Thread(new Runnable() {
@Override
public void run() {
while (isRunning) {
try {
// 监听其它设备的连接请求,处于阻塞状态
socket = ss.accept();
if (!txtChatMessageList.getText().toString()
.contains("连接了一个客户端。")) {
txtChatMessageList.append("连接了一个客户端。\n");
}
netIn = new DataInputStream(socket.getInputStream());
netOut = new DataOutputStream(socket
.getOutputStream());
// 初始化服务器端消息
if (null == serverMsg || serverMsg.equals("")) {
serverMsg = "欢迎您,新朋友! ";
}
// 获取输出流(套接字输出流-->数据输出流)
netOut = new DataOutputStream(socket
.getOutputStream());
// 向客户端输出信息
netOut.writeUTF(serverMsg);
// 清空输出流缓冲数据
netOut.flush();
// 获取客户端消息
displayClientMsg();
} catch (IOException e) {
}
}
}
}).start();
} catch (IOException e1) {
}
/* 给各个控件注册监听器,编写事件代码 */
btnSend.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
serverMsg = txtInputMessage.getText();
if (!serverMsg.trim().equals("")) {
txtChatMessageList.append("服务器>>>" + serverMsg + "\n");
if (netOut != null) {
netOut.writeUTF(serverMsg);
}
} else {
JOptionPane.showMessageDialog(null, "不能发送空信息!", "服务器",
JOptionPane.WARNING_MESSAGE);
}
txtInputMessage.setText("");
txtInputMessage.requestFocus();
} catch (IOException ie) {
}
}
});
// 给关闭按钮注册监听器
btnClose.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
releaseResource();
System.exit(0);
}
});
// 给窗口注册监听器
addWindowListener(new WindowAdapter() {
public void windowActivated(WindowEvent e) {
txtInputMessage.requestFocus();
}
public void windowClosing(WindowEvent e) {
releaseResource();
System.exit(0);
}
});
}
/**
* 释放资源
*/
private void releaseResource() {
isRunning = false;
try {
if (netIn != null && netOut != null) {
netIn.close();
netOut.close();
}
if (socket != null && !socket.isClosed()) {
socket.close();
}
if (ss != null && !ss.isClosed()) {
ss.close();
}
} catch (IOException e) {
}
}
/**
* 初始化用户界面
*/
private void initUI() {
// 创建组件
panel1 = new JPanel();
panel2 = new JPanel();
txtChatMessageList = new JTextArea(15, 60);
txtInputMessage = new JTextArea(3, 60);
panContent = new JScrollPane(txtChatMessageList,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
panInput = new JScrollPane(txtInputMessage,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
btnClose = new JButton("关闭");
btnSend = new JButton("发送");
// 添加组件
getContentPane().add(panContent, "Center");
getContentPane().add(panel1, "South");
panel1.setLayout(new GridLayout(0, 1));
panel1.add(panInput);
panel1.add(panel2);
panel2.add(btnSend);
panel2.add(btnClose);
// 设置组件属性
txtChatMessageList.setEditable(false);
txtChatMessageList.setFont(new Font("宋体", Font.PLAIN, 13));
txtInputMessage.setFont(new Font("宋体", Font.PLAIN, 15));
txtChatMessageList.setLineWrap(true);
txtInputMessage.setLineWrap(true);
txtInputMessage.requestFocus();
setSize(450, 350);
setLocation(50, 200);
setResizable(false);
setLocationRelativeTo(null);
setVisible(true);
}
// 显示客户端信息
void displayClientMsg() {
try {
clientMsg = netIn.readUTF();
txtChatMessageList.append("客户端>>>" + clientMsg + "\n");
} catch (IOException e) {
}
}
}
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/receive" android:state_pressed="false"/>
<item android:drawable="@drawable/receive_pressed" android:state_pressed="true"/>
selector>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/send" android:state_pressed="false"/>
<item android:drawable="@drawable/send_pressed" android:state_pressed="true"/>
selector>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background"
android:orientation="vertical"
android:padding="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tvHost"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/host"
android:textColor="#0000ff"
android:textSize="20sp" />
<EditText
android:id="@+id/edtHost"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="#ffffff"
android:padding="5dp"
android:singleLine="true"
android:textColor="#000000"
android:textSize="20sp" />
LinearLayout>
<Button
android:id="@+id/btnReceiveMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@drawable/btn_receive_selector" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/edtMessage"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="4"
android:hint="@string/input_message"
android:paddingLeft="5dp"
android:singleLine="true"
android:textColor="#000000"
android:textSize="20sp" />
<Button
android:id="@+id/btnSendMessage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginTop="5dp"
android:layout_weight="1"
android:background="@drawable/btn_send_selector"
android:textSize="18sp" />
LinearLayout>
<EditText
android:id="@+id/edtMessageList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="false"
android:gravity="left|top"
android:inputType="textMultiLine|none"
android:scrollbars="vertical"
android:textSize="18sp">
<requestFocus />
EditText>
LinearLayout>
<resources>
<string name="app_name">聊天安卓客户端string>
<string name="host">服务器地址:string>
<string name="input_message">请输入聊天内容string>
resources>
package net.hw.chat_android_client;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 功能:安卓聊天客户端
* 作者:华卫
* 日期:2021年01月01日
*/
public class MainActivity extends AppCompatActivity {
/**
* 发送信息按钮
*/
private Button btnSendMessage;
/**
* 接收信息按钮
*/
private Button btnReceiveMessage;
/**
* 消息编辑框
*/
private EditText edtMessage;
/**
* 客户端套接字
*/
private Socket socket;
/**
* 消息处理器(发送与处理消息)
*/
private Handler handler;
/**
* 服务器端口号
*/
private static final int PORT = 8888;
/**
* 服务器端主机地址
*/
private String host;
/**
* 初始化网络连接的线程
*/
private Thread initNetworkThread;
/**
* 聊天消息构建器
*/
private StringBuilder chatMesssageBuilder;
/**
* 聊天信息列表编辑框
*/
private EditText edtMessageList;
/**
* 服务器地址
*/
private EditText edtHost;
/**
* 数据输出流
*/
private static DataOutputStream netOut;
/**
* 数据输入流
*/
private static DataInputStream netIn;
/**
* 来自客户端的消息
*/
private static String clientMsg;
/**
* 服务器端的消息
*/
private static String serverMsg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局资源文件设置用户界面
setContentView(R.layout.activity_main);
// 通过资源索引获得界面控件实例
btnSendMessage = findViewById(R.id.btnSendMessage);
btnReceiveMessage = findViewById(R.id.btnReceiveMessage);
edtMessage = findViewById(R.id.edtMessage);
edtMessageList = findViewById(R.id.edtMessageList);
edtHost = findViewById(R.id.edtHost);
// 设置服务器地址
edtHost.setText("192.168.1.5");
// 实例化聊天消息构建器
chatMesssageBuilder = new StringBuilder();
// 给发送按钮注册监听器
btnSendMessage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendMessage(); // 发送消息
}
});
// 给接收按钮注册监听器
btnReceiveMessage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
receiveMessage(); // 接收消息
}
});
// 给消息编辑框注册监听器
edtMessage.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
sendMessage(); // 收发消息
}
return false;
}
});
// 创建消息处理器
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0x001) {
// 设置聊天信息列表内容
edtMessageList.setText(chatMesssageBuilder.toString());
// 清空输入框
edtMessage.setText("");
}
}
};
}
/**
* 接收消息:接收来自服务器端的消息
*/
private void receiveMessage() {
new Thread() {
@Override
public void run() {
// 获取主机
host = edtHost.getText().toString();
// 非空校验
if (host.length() == 0) {
Toast.makeText(MainActivity.this, "请输入服务器地址!", Toast.LENGTH_LONG);
return;
}
// 采用短连接,接收一次消息,立马断开连接
try {
// 创建客户端套接字
socket = new Socket(host, PORT);
// 接收服务器端发送的消息
netIn = new DataInputStream(socket.getInputStream());
// 从数据输入流读取内容
serverMsg = netIn.readUTF();
// 在聊天信息列表里添加服务器端的信息
chatMesssageBuilder.append("服务器>>>" + serverMsg + "\n");
// 发送消息
handler.sendEmptyMessage(0x001);
// 关闭输入流
netIn.close();
// 关闭客户端套接字
socket.close();
} catch (UnknownHostException e) {
Toast.makeText(MainActivity.this, "未知的主机异常!",
Toast.LENGTH_LONG).show();
} catch (IOException e) {
Toast.makeText(MainActivity.this, "输入输出异常!",
Toast.LENGTH_LONG).show();
}
}
}.start();
}
/**
* 发送消息:向服务器端发送消息
*/
private void sendMessage() {
// 启动子线程,执行发送聊天内容
new Thread() {
@Override
public void run() {
// 获取主机
host = edtHost.getText().toString();
// 非空校验
if (host.length() == 0) {
Toast.makeText(MainActivity.this, "请输入服务器地址!",
Toast.LENGTH_LONG);
return;
}
// 采用短连接,发送一次消息,立马断开连接
try {
// 创建客户端套接字
socket = new Socket(host, PORT);
// 向服务器端发送信息
netOut = new DataOutputStream(socket.getOutputStream());
// 获取客户端消息
clientMsg = edtMessage.getText().toString();
// 不允许发送空消息给服务器端
if (clientMsg.length() == 0) {
return;
}
// 向服务器端发送消息
netOut.writeUTF(clientMsg);
// 清空输出流缓冲数据
netOut.flush();
// 添加客户端信息
chatMesssageBuilder.append("客户端>>>" + clientMsg + "\n");
// 发送消息
handler.sendEmptyMessage(0x001);
// 关闭输出流
netOut.close();
// 关闭客户端套接字
socket.close();
} catch (UnknownHostException e) {
Toast.makeText(MainActivity.this, "未知的主机异常!",
Toast.LENGTH_LONG).show();
} catch (IOException e) {
Toast.makeText(MainActivity.this, "输入输出异常!",
Toast.LENGTH_LONG).show();
}
}
}.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (socket != null) {
try {
// 关闭套接字
socket.close();
} catch (Exception e) {
System.out.println("异常:无法关闭Socket!");
}
}
}
}