当前位置:网站首页>QT self-made soft keyboard is the most perfect and simple, just like its own virtual keyboard
QT self-made soft keyboard is the most perfect and simple, just like its own virtual keyboard
2022-07-28 14:10:00 【Source guest V】
One 、 Features of this self-made virtual keyboard
1. The keyboard interface remains at the top of all interfaces .
2. Clicking the keyboard button does not change the focus of the underlying text input box .
3. Complete keyboard input text information by simulating keyboard click events .
4. Including various keyboard built-in symbol input .
5. Long press the key to input the contents of the keyboard repeatedly .
6. Support win7、win10、Linux And so on .
7. Nice interface .
In short, it's the same as a real virtual keyboard , There will be no discomfort
Two 、windows Open the system's own soft keyboard
QDesktopServices::openUrl(QUrl("osk.exe", QUrl::TolerantMode));The traditional method of opening the system's own virtual keyboard is as follows , Just one line of code , But the virtual keyboard of the system is not necessarily easy to use , Some buttons are too small , Some computers may not have their own soft keyboard , Just write one .
3、 ... and 、 Keep the keyboard interface at the top 、 Do not change the focus of the underlying interface
Many homemade keyboards don't know how to keep the keyboard at the top , It is found that after clicking the keyboard interface, the system focus is on the keyboard interface , The cursor is not in the bottom row input box , After input, click OK to transfer the keyboard content to the bottom interface , It doesn't look like it nice, In fact, these two problems are very simple and can be solved , The code is as follows
this->setWindowFlags(Qt::WindowStaysOnTopHint | Qt::WindowDoesNotAcceptFocus);Qt::WindowStaysOnTopHint Set window top Qt::WindowDoesNotAcceptFocus Set unfocused window
Four 、 Press and hold the key to input the contents of the keyboard repeatedly
Especially when we click the backspace delete button , There are more texts. When we use a real keyboard, we will press the backspace key to delete more than ten texts one by one , When using the virtual keyboard, you need to press the backspace key more than ten times , It doesn't look like it nice. So we use QPushButton Of setAutoRepeat by true, You can press and hold the key , Set the repetition delay to 500ms About the same , Press for longer than 500ms After that, execute the key slot function again .
pbtn->setAutoRepeat(true); // Allow automatic repetition
pbtn->setAutoRepeatDelay(500);// Set the delay of repeated operation 5、 ... and 、 Simulate keyboard click event to complete virtual keyboard input
Some self-made virtual keyboards complete keyboard input in the same way as passing text to the input box , Don't talk much , Just not nice. Clicking the virtual key directly sends the corresponding key click event nice, In this way, you won't die too much , Even Chinese input is ok , We wrote the keyboard , Not writing Chinese input method , Those who download the library containing Chinese input methods on the Internet don't have to , Input method is what your system uses . If you want to input Chinese , The system downloads Sogou input method , Just press your keyboard ctrl+shift Just switch the input method , Instead of realizing the function of Chinese input method in the keyboard . That's not nice, The simulated sending button click event code is as follows .
QPushButton* pbtn = (QPushButton*)sender();
if (pbtn->text() >= 'a' && pbtn->text() <= 'z') {
QKeyEvent keyPress(QEvent::KeyPress, int(pbtn->text().at(0).toLatin1()) - 32, Qt::NoModifier, pbtn->text());
QKeyEvent keyRelease(QEvent::KeyRelease, int(pbtn->text().at(0).toLatin1()) - 32, Qt::NoModifier, pbtn->text());
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}adopt QApplication::sendEvent Sending a key press and key release event is equivalent to simulating a key press event
QKeyEvent explain :
QKeyEvent::QKeyEvent(QEvent::Type type, int key, Qt::KeyboardModifiers modifiers, const QString &text = QString(), bool autorep = false, ushort count = 1)
The type parameter must be QEvent::KeyPress、QEvent::KeyRelease or QEvent::ShortcutOverride.
Int key It is the event loop that should listen Qt:: key Code for . If key by 0, Then the event is not the result of a known key ; for example , It may be the result of combining sequences or keyboard macros . Modifiers include keyboard modifiers , The given text is key generated Unicode Text . If autorep It's true ,isAutoRepeat() Will be true . Count Is the number of keys involved in the event .
QKeyEvent Here we use 4 Just one parameter , The second parameter Qt:: key The corresponding button , The fourth parameter is the text to be sent , Nothing can be entered without this parameter .
QApplication::sendEvent The first parameter of is very important , Is a control that receives keyboard input , For example, the bottom interface lineEdit.
6、 ... and 、 Keyboard symbol input
m_mapSymbolKeys.insert("~", Qt::Key_AsciiTilde);
m_mapSymbolKeys.insert("`", Qt::Key_nobreakspace);
m_mapSymbolKeys.insert("-", Qt::Key_Minus);
m_mapSymbolKeys.insert("_", Qt::Key_Underscore);
m_mapSymbolKeys.insert("+", Qt::Key_Plus);
m_mapSymbolKeys.insert("=", Qt::Key_Equal);
m_mapSymbolKeys.insert(",", Qt::Key_Comma);
m_mapSymbolKeys.insert(".", Qt::Key_Period);
m_mapSymbolKeys.insert("/", Qt::Key_Slash);
m_mapSymbolKeys.insert("<", Qt::Key_Less);
m_mapSymbolKeys.insert(">", Qt::Key_Greater);
m_mapSymbolKeys.insert("?", Qt::Key_Question);
m_mapSymbolKeys.insert("[", Qt::Key_BracketLeft);
m_mapSymbolKeys.insert("]", Qt::Key_BracketRight);
m_mapSymbolKeys.insert("{", Qt::Key_BraceLeft);
m_mapSymbolKeys.insert("}", Qt::Key_BraceRight);
m_mapSymbolKeys.insert("|", Qt::Key_Bar);
m_mapSymbolKeys.insert("\\", Qt::Key_Backslash);
m_mapSymbolKeys.insert(":", Qt::Key_Colon);
m_mapSymbolKeys.insert(";", Qt::Key_Semicolon);
m_mapSymbolKeys.insert("\"", Qt::Key_QuoteLeft);
m_mapSymbolKeys.insert("'", Qt::Key_Apostrophe);
QKeyEvent keyPress(QEvent::KeyPress, m_mapSymbolKeys.value(symbol), Qt::NoModifier, symbol);
QKeyEvent keyRelease(QEvent::KeyRelease, m_mapSymbolKeys.value(symbol), Qt::NoModifier, symbol);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);It is important to find... When inputting specific symbols on the keyboard QKeyEvent The key value corresponding to the second parameter is ok . The values in the keys are all in qnamespace.h In the header file of enum Key Enumeration type .
7、 ... and 、 Interface

8、 ... and 、 Header file code
#pragma once
#pragma execution_character_set("utf-8")
#include <QDialog>
#include "ui_frmKeyBoard.h"
#include "moveWidget.h"
#include <QPushButton>
#include <QKeyEvent>
#include <QDebug>
#include <QStyle>
class frmKeyBoard : public QDialog
{
Q_OBJECT
public:
frmKeyBoard(QWidget *parent = nullptr);
~frmKeyBoard();
void initFocusWidget(QWidget*);
private slots:
void slotKeyButtonClicked();
void slotKeyLetterButtonClicked();
void slotKeyNumberButtonClicked();
private:
Ui::frmKeyBoardClass ui;
void initFrm();
void initStyleSheet();
QWidget* m_focusWidget; // Keyboard input main window
QVector<QPushButton*> m_letterKeys;
QVector<QPushButton*> m_NumberKeys;
QMap<QString, Qt::Key> m_mapSymbolKeys;
};
Nine 、 Source code
#include "frmKeyBoard.h"
frmKeyBoard::frmKeyBoard(QWidget *parent)
: QDialog(parent)
{
ui.setupUi(this);
this->setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus);
this->setWindowTitle(" On screen keyboard ");
this->setWindowModality(Qt::WindowModal);
this->setAttribute(Qt::WA_DeleteOnClose);
MoveWidget* moveWidget = new MoveWidget();
moveWidget->setWidget(this);
this->initFrm();
this->initStyleSheet();
}
frmKeyBoard::~frmKeyBoard()
{
}
void frmKeyBoard::initFocusWidget(QWidget* widget)
{
m_focusWidget = widget;
}
void frmKeyBoard::initFrm()
{
ui.pushButton_closeKeyboard->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton));
m_letterKeys.clear();
m_NumberKeys.clear();
QList<QPushButton*> pbtns = this->findChildren<QPushButton*>();
foreach(QPushButton * pbtn, pbtns) {
pbtn->setAutoRepeat(true); // Allow automatic repetition
pbtn->setAutoRepeatDelay(500);// Set the delay of repeated operation
if (pbtn->text() >= 'a' && pbtn->text() <= 'z') {
connect(pbtn, &QPushButton::clicked, this, &frmKeyBoard::slotKeyLetterButtonClicked);
m_letterKeys.push_back(pbtn);
}
else if (pbtn->text().toInt() > 0 && pbtn->text().toInt() <= 9 || pbtn->text() == "0") {
connect(pbtn, &QPushButton::clicked, this, &frmKeyBoard::slotKeyNumberButtonClicked);
m_NumberKeys.push_back(pbtn);
}
else{
connect(pbtn, &QPushButton::clicked, this, &frmKeyBoard::slotKeyButtonClicked);
}
}
m_mapSymbolKeys.insert("~", Qt::Key_AsciiTilde);
m_mapSymbolKeys.insert("`", Qt::Key_nobreakspace);
m_mapSymbolKeys.insert("-", Qt::Key_Minus);
m_mapSymbolKeys.insert("_", Qt::Key_Underscore);
m_mapSymbolKeys.insert("+", Qt::Key_Plus);
m_mapSymbolKeys.insert("=", Qt::Key_Equal);
m_mapSymbolKeys.insert(",", Qt::Key_Comma);
m_mapSymbolKeys.insert(".", Qt::Key_Period);
m_mapSymbolKeys.insert("/", Qt::Key_Slash);
m_mapSymbolKeys.insert("<", Qt::Key_Less);
m_mapSymbolKeys.insert(">", Qt::Key_Greater);
m_mapSymbolKeys.insert("?", Qt::Key_Question);
m_mapSymbolKeys.insert("[", Qt::Key_BracketLeft);
m_mapSymbolKeys.insert("]", Qt::Key_BracketRight);
m_mapSymbolKeys.insert("{", Qt::Key_BraceLeft);
m_mapSymbolKeys.insert("}", Qt::Key_BraceRight);
m_mapSymbolKeys.insert("|", Qt::Key_Bar);
m_mapSymbolKeys.insert("\\", Qt::Key_Backslash);
m_mapSymbolKeys.insert(":", Qt::Key_Colon);
m_mapSymbolKeys.insert(";", Qt::Key_Semicolon);
m_mapSymbolKeys.insert("\"", Qt::Key_QuoteLeft);
m_mapSymbolKeys.insert("'", Qt::Key_Apostrophe);
}
void frmKeyBoard::initStyleSheet()
{
QString qss;
qss += "QWidget{ background-color:rgb(26,26,26)}";
qss += "QPushButton{ color:white; background-color:rgb(51,51,51); height:60px; font-size:bold 15pt; border:1px solid rgb(26,26,26); border-radius: 0px; min-width:50px;}";
qss += "QPushButton:hover{background-color:rgb(229,229,229); color:black;}";
qss += "QPushButton:pressed,QPushButton:checked{background-color:rgb(0,118,215);}";
qss += "#pushButton_closeKeyboard{background-color:rgba(0,0,0,0); border:0px}";
qss += "#pushButton_closeKeyboard:hover{background-color:#b30220;}";
qss += "#pushButton_space{min-width:500px;}";
qss += "#pushButton_backspace,#pushButton_shift{min-width:100px;}";
qss += "#pushButton_enter{min-width:120px;}";
qss += "#pushButton_tab,#pushButton_ctrl{min-width:70px;}";
qss += "#pushButton_capsLock{min-width:80px;}";
qss += "#pushButton_up{min-width:150px;}";
this->setStyleSheet(qss);
}
void frmKeyBoard::slotKeyButtonClicked()
{
QPushButton* pbtn = (QPushButton*)sender();
QString objectName = pbtn->objectName();
if (objectName == "pushButton_closeKeyboard") {
this->close();
return;
}
if (pbtn->text().contains("Backspace")) {
QKeyEvent keyPress(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier);
QKeyEvent keyRelease(QEvent::KeyRelease, Qt::Key_Backspace, Qt::NoModifier);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
else if (pbtn->text().contains("Caps")) {
if (pbtn->isChecked()) {
for (auto pbtnKey : m_letterKeys) {
pbtnKey->setText(pbtnKey->text().toUpper());
}
}
else {
for (auto pbtnKey : m_letterKeys) {
pbtnKey->setText(pbtnKey->text().toLower());
}
}
}
else if(pbtn->text() == "Space") {
QKeyEvent keyPress(QEvent::KeyPress, Qt::Key_Space, Qt::NoModifier, " ");
QKeyEvent keyRelease(QEvent::KeyRelease, Qt::Key_Space, Qt::NoModifier, " ");
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
else if (pbtn->text().contains("Tab")) {
QKeyEvent keyPress(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier);
QKeyEvent keyRelease(QEvent::KeyRelease, Qt::Key_Tab, Qt::NoModifier);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
else if (pbtn->text().contains("Enter")) {
QKeyEvent keyPress(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier);
QKeyEvent keyRelease(QEvent::KeyRelease, Qt::Key_Enter, Qt::NoModifier);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
else if (pbtn->text().contains("Shift")) {
if (pbtn->isChecked()) {
for (auto pbtnKey : m_letterKeys) {
pbtnKey->setText(pbtnKey->text().toUpper());
}
}
else {
for (auto pbtnKey : m_letterKeys) {
pbtnKey->setText(pbtnKey->text().toLower());
}
}
QKeyEvent keyPress(QEvent::KeyPress, Qt::Key_Shift, Qt::NoModifier);
QKeyEvent keyRelease(QEvent::KeyRelease, Qt::Key_Shift, Qt::NoModifier);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
else if (pbtn->text().contains("Ctrl")) {
QKeyEvent keyPress(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier);
QKeyEvent keyRelease(QEvent::KeyRelease, Qt::Key_Control, Qt::NoModifier);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
else if (pbtn->text().contains("Win")) {
QKeyEvent keyPress(QEvent::KeyPress, Qt::Key_Menu, Qt::NoModifier);
QKeyEvent keyRelease(QEvent::KeyRelease, Qt::Key_Menu, Qt::NoModifier);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
else if (pbtn->text().contains("Alt")) {
QKeyEvent keyPress(QEvent::KeyPress, Qt::Key_Alt, Qt::NoModifier);
QKeyEvent keyRelease(QEvent::KeyRelease, Qt::Key_Alt, Qt::NoModifier);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
else if (pbtn->text().contains("↑")) {
QKeyEvent keyPress(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier);
QKeyEvent keyRelease(QEvent::KeyRelease, Qt::Key_Up, Qt::NoModifier);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
else if (pbtn->text().contains("↓")) {
QKeyEvent keyPress(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier);
QKeyEvent keyRelease(QEvent::KeyRelease, Qt::Key_Down, Qt::NoModifier);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
else if (pbtn->text().contains("←")) {
QKeyEvent keyPress(QEvent::KeyPress, Qt::Key_Left, Qt::NoModifier);
QKeyEvent keyRelease(QEvent::KeyRelease, Qt::Key_Left, Qt::NoModifier);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
else if (pbtn->text().contains("→")) {
QKeyEvent keyPress(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier);
QKeyEvent keyRelease(QEvent::KeyRelease, Qt::Key_Right, Qt::NoModifier);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
else {
QString symbol;
if (ui.pushButton_shift->isChecked())
symbol = pbtn->text().split("\n").at(0);
else
symbol = pbtn->text().split("\n").at(1);
QKeyEvent keyPress(QEvent::KeyPress, m_mapSymbolKeys.value(symbol), Qt::NoModifier, symbol);
QKeyEvent keyRelease(QEvent::KeyRelease, m_mapSymbolKeys.value(symbol), Qt::NoModifier, symbol);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
// Press the cancel key combination
if (!pbtn->text().contains("Shift") && !pbtn->text().contains("Ctrl") && !pbtn->text().contains("Win") && !pbtn->text().contains("Alt")) {
if (ui.pushButton_shift->isChecked()) {
ui.pushButton_shift->setChecked(false);
for (auto pbtnKey : m_letterKeys) {
pbtnKey->setText(pbtnKey->text().toLower());
}
}
if (ui.pushButton_ctrl->isChecked())
ui.pushButton_ctrl->setChecked(false);
if (ui.pushButton_win->isChecked())
ui.pushButton_win->setChecked(false);
if (ui.pushButton_alt->isChecked())
ui.pushButton_alt->setChecked(false);
}
}
void frmKeyBoard::slotKeyLetterButtonClicked()
{
QPushButton* pbtn = (QPushButton*)sender();
if (pbtn->text() >= 'a' && pbtn->text() <= 'z') {
QKeyEvent keyPress(QEvent::KeyPress, int(pbtn->text().at(0).toLatin1()) - 32, Qt::NoModifier, pbtn->text());
QKeyEvent keyRelease(QEvent::KeyRelease, int(pbtn->text().at(0).toLatin1()) - 32, Qt::NoModifier, pbtn->text());
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
else if (pbtn->text() >= 'A' && pbtn->text() <= 'Z') {
QKeyEvent keyPress(QEvent::KeyPress, int(pbtn->text().at(0).toLatin1()), Qt::NoModifier, pbtn->text());
QKeyEvent keyRelease(QEvent::KeyRelease, int(pbtn->text().at(0).toLatin1()), Qt::NoModifier, pbtn->text());
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
}
// Press the cancel key combination
if (ui.pushButton_shift->isChecked()) {
ui.pushButton_shift->setChecked(false);
for (auto pbtnKey : m_letterKeys) {
pbtnKey->setText(pbtnKey->text().toLower());
}
}
if (ui.pushButton_ctrl->isChecked())
ui.pushButton_ctrl->setChecked(false);
if (ui.pushButton_win->isChecked())
ui.pushButton_win->setChecked(false);
if (ui.pushButton_alt->isChecked())
ui.pushButton_alt->setChecked(false);
}
void frmKeyBoard::slotKeyNumberButtonClicked()
{
QPushButton* pbtn = (QPushButton*)sender();
QKeyEvent keyPress(QEvent::KeyPress, pbtn->text().toInt() + 48, Qt::NoModifier, pbtn->text());
QKeyEvent keyRelease(QEvent::KeyRelease, pbtn->text().toInt() + 48, Qt::NoModifier, pbtn->text());
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyPress);
QApplication::sendEvent(m_focusWidget->focusWidget(), &keyRelease);
// Press the cancel key combination
if (ui.pushButton_shift->isChecked()) {
ui.pushButton_shift->setChecked(false);
for (auto pbtnKey : m_letterKeys) {
pbtnKey->setText(pbtnKey->text().toLower());
}
}
if (ui.pushButton_ctrl->isChecked())
ui.pushButton_ctrl->setChecked(false);
if (ui.pushButton_win->isChecked())
ui.pushButton_win->setChecked(false);
if (ui.pushButton_alt->isChecked())
ui.pushButton_alt->setChecked(false);
}
11、 ... and 、 effect

Qt Self made virtual keyboard
Twelve 、 Download resources
边栏推荐
- ES6 what amazing writing methods have you used
- 正则表达式
- R language uses LM function to build multiple linear regression model, writes regression equation according to model coefficient, and uses conflict function to give 95% confidence interval of regressi
- 安全保障基于软件全生命周期-Istio的认证机制
- 每日一题——奖学金
- [util] redis tool class: change the value serializer of redis to genericjackson2jsonredisserializer, and the return value can be object or collection
- Websocket chat
- Deploy application delivery services in kubernetes (Part 1)
- 安全保障基于软件全生命周期-NetworkPolicy应用
- P1797 heavy transportation problem solution
猜你喜欢

Security assurance is based on software life cycle -psp application

Record a fake login of cookie

Multi level cache scheme

Qt5 development from introduction to mastery -- the first overview

30 day question brushing plan (IV)

走进音视频的世界——FLV视频封装格式

对“Image Denoising Using an Improved Generative Adversarial Network with Wasserstein Distance“的理解

Qt5开发从入门到精通——第一篇概述

在centos中安装mysql5.7.36

Understanding of "image denoising using an improved generic advantageous network with Wasserstein distance"
随机推荐
regular expression
Istio IV fault injection and link tracking
83.(cesium之家)cesium示例如何运行
vite在项目中配置路径别名
The strongest distributed locking tool: redisson
作为一个程序员,如何高效的管理时间?
论文研读--Masked Generative Distillation
Understand BFC features and easily realize adaptive layout
Understanding of "image denoising using an improved generic advantageous network with Wasserstein distance"
Istio四之故障注入和链路追踪
Jmeter安装教程及登录增加token
【Util】redis工具类:把redis的value序列化器修改为GenericJackson2JsonRedisSerializer,就支持返回值为对象或集合了
Long closed period private placement products reappearance industry insiders have different views
【翻译】盐业公司来Linkerd公司是为了负载平衡,留下来是为了效率、可靠性和性能。...
Several efficient APIs commonly used in inventory operation URL
IntersectionObserver交叉观察器
软件测试技术之如何编写测试用例
[try to hack] hfish honeypot deployment
webSocket聊天
Three cases of thread blocking.