二、Socket通信的实现
为CreateSocketConnection填充内容,建立socket连接:
- Python资源共享群:626017123
public class MainActivity extends AppCompatActivity {
// 创建全局变量方便重复使用
Socket socket;
InputStream is;
OutputStream os;
// ...
// 省略之前的内容
// ...
public boolean CreateSocketConnection() {
try {
// 1. 构建socket
String host = "192.168.0.104";
int port = 8000;
socket = new Socket(host, port);
// 2. 构建I/O
is = socket.getInputStream();
os = socket.getOutputStream();
// 3.1 向服务器端发送信息
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
String str = "Hello!"
bw.write(str);
bw.flush();
// 3.2 读取服务器返回的消息
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String return_str = br.readLine(); // *readLine方法
System.out.println(return_str)
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
public boolean RemoveSocketConnection() {
try {
// 4.1 关闭I/O
is.close();
os.close();
// 4.2 关闭socket
socket.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
}
注意几点:
1.
构建socket
与python的几乎没有差别,直接new一个Socket对象就成。
2.
构建I/O
使用socket对象的getInputStream, getOutputStream方法,获取到InputStream, OutputStream类型的字节流
3.
包装字节流对象
由于不想自己手动实现字节流的转换,所以我对得到的流利用BufferedWriter, BufferedReader进行包装,并利用其中的方法便捷地写入/读取字符串数据。这里实现方法很多,我最后选择了这种BufferedWriter的写法。
了解更多方法可参阅这篇文章:Java中将InputStream读取为String, 各种方法的性能对比[1]。
注意:
readLine方法必须要求发送来的字符串以换行符结尾。
4.
关闭对象
自己产生的对象自己关掉,不要乱丢垃圾。操作也很简单,直接调用对象的close方法即可。
这样就实现了完整的一轮通信:先打开服务端,再在手机端点击链接按钮,你就会看到两端的信息已经交换。
三、进一步完善:添加连接验证
这一步不是必要的,但是对于理解和利用通信机制很有好处,因此我添加了这一节。
我们的目标是,客户端在点击连接后:
1.产生一个5位数验证码并弹窗显示
2.发送验证码到服务器
3.服务端要求输入验证码,以确定是本人操作。
实现如下:
1. Python验证:
创建一个弹窗类:
import tkinter as tk # 引入tkinter图形界面包
class MessageBox:
def __init__(self, code, address):
self.code = code # 传入要验证的密码
self.address = address # 传入客户端的地址信息
self.root = tk.Tk() # 创建主窗口
self.root.geometry("320x180") # 设置窗口大小
# 创建窗口上的元素,并布局
self.label = tk.Label(self.root, text="请在60秒内输入校验码", font=("Microsoft YaHei", 18))
self.label.place(x=30, y=30)
self.entry = tk.Entry(self.root, width=28)
self.entry.place(x=60, y=100)
# 绑定事件,响应回车
self.root.bind("<Return>", self.retrieve_input)
# 设置最长等待时间
self.root.after(60000, self.timeout_destroy)
# 打开主窗口事件循环
self.root.mainloop()
def timeout_destroy(self):
self.root.destroy() # 关闭窗口
raise TimeoutError
def retrieve_input(self, event):
# self.entry.get()方法可获取entry中输入的字符串
if self.entry.get() == self.code:
self.root.destroy()
# 添加了一个全局变量link_dict,其中存储要建立的连接和状态
link_dict[self.address] = True
创建该对象后显示的结果是:
调整之前写的server部分的结构,包装成一个类:
class Server:
def __init__(self):
# 设置一个flag判断这个连接是否通过了验证
self.checked = False
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.address = ('0.0.0.0', 8000)
self.server.bind(self.address)
self.server.listen()
while True:
try:
client, address = self.server.accept()
threading.Thread(target=self.init_connection, args=(client, address)).start()
threading.Thread(target=self.io_operation, args=(client,)).start()
except Exception as e:
print(e)
break
# 关闭socket服务器
self.server.close()
# 处理数据
def parsing_data(self, data):
# 具体的处理客户端命令的部分,先空着
return ""
# I/O操作
def io_operation(self, current_client):
while True:
# 检测是否完成了初始化
if not self.checked:
continue
try:
# 从客户端接收数据
data = current_client.recv(1024).decode()
re_data = "idlen"
if data:
print(data)
re_data = self.parsing_data(data)
# 向客户端发送数据
current_client.sendall(re_data.encode())
except ConnectionError:
print("连接中断")
break
except WindowsError:
print("出现错误")
break
# 初始化连接
def init_connection(self, current_client, current_address):
# 初始化连接检查
link_dict[current_address] = False
# 从客户端接收数据
data = current_client.recv(1024).decode()
re_data = "failn"
if data:
data_list = data.split()
# 处理连接标识码 *1
if data_list[0] == "code":
def create_msg_box():
MessageBox(data_list[1], current_address)
try:
threading.Thread(target=create_msg_box).start()
except TimeoutError:
print("连接超时")
while True:
if link_dict[current_address]:
self.checked = True
# 返回检验完毕的信息 *2
re_data = "checkn"
print(current_address, "已连接")
break
else:
print("非法连接")
# 向客户端发送数据
current_client.sendall(re_data.encode())
*1: 约定在客户端采用code xxxxx的字符串格式传入验证码
*2: 约定在服务端检测成功后返回check,否则返回fail或无返回
2. Java验证:
// 连接标志码
int connection_code = (int) (Math.random() * 90000) + 10000;
public boolean CreateSocketConnection() {
try {
handler.post(new Runnable() {
@Override
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("连接标识码");
builder.setMessage(connection_code + "");
builder.setPositiveButton("好的", null);
builder.setCancelable(false);
builder.show();
}
});
} catch (Exception e) {
e.printStackTrace();
}
try {
String host = "192.168.0.104";
int port = 8000;
socket = new Socket(host, port);
is = socket.getInputStream();
os = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
bw.write("code " + connection_code); // 发送校验码
bw.flush();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg = br.readLine();
switch (msg) {
case "check": {
System.out.println("连接成功");
return true;
}
case "fail": {
System.out.println("连接失败");
return false;
}
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
先使用AlertDialog类创建一个简单的提示框:
然后根据之前的约定修改判定条件即可。