这段时间在学习QT,刚好学习到了QT中的socket网络编程,上网查找相关的资料,大部分都是在局域网中自己和自己玩。关于QT连接广域网、云服务器的资料少的可怜,也许是大家觉得太简单,不值得一写。谁让自己是个菜鸟,唉。接下来我来记录下整个过程,不为啥,只是为了记录下来,做个笔记,可能有些说法什么的不能入大佬的眼,不喜勿喷。
准备工作:
一台联网且带有Ubuntu虚拟机的Windows系统电脑;
腾讯云服务器
Windows版的QT Creator5.8软件
Xshell
Xftp
1.一台联网且带有Ubuntu虚拟机的Windows系统电脑
不知道如何安装虚拟机的小伙伴,请移步:
Ubuntu虚拟机安装
这里不做过多的赘述。
2.腾讯云服务器
刚刚接触这个的小伙伴,可能会有望而生畏的情绪在。觉得应该会很复杂,不可能一下就能使用,其实不然,至少在我们这个小工程里不存在复杂一说。
我之前用过阿里云,相对来说,阿里云的操作稍微要比腾讯云这边的操作要复杂些,也可能是我的错觉,因为这有个熟悉的过程。
腾讯云还是比较亲民的,对于学生来说,只需要10块钱便可让我们学习一个月,所以呢,这一块无需担心什么。
他的购买和注册登录步骤如下:
腾讯云服务器的购买、注册和登录
3.Windows版的QT Creator5.8软件
这个不是重点,请大家自己查阅资料安装。
4.Xshell和Xftp
这是两个软件,可自行到官网下载,前者是用来登录远程操作桌面的,后者是用来给Windows系统与云服务器主机之间传输文件的,他们的软件图标如下:
细心的小伙伴之前应该可以看到,在我们第2小点腾讯云服务器的购买、注册和登录一节中,它默认的实例,是有公网IP和内网IP的,如下:
这个公网IP,在后续的操作中,很重要。
好,在安装好两款软件后,我们双击打开Xshell软件,如图:
文件->新建,如下
主机一栏,填入公网IP号,其他按照上述填写即可,我们选择确定,输入登录密码,OK。如下:
现在,就已经OK了,在这里的一切操作,都是和在网页版的操作都是一样的。
接下来的Xftp操作,和Xshell大同小异,他连接上云服务器后的界面如下:
左边是我们电脑本机的文件,右边是我们服务器主机的文件,默认目录实在/home/ubuntu下,我们可以直接拖动文件完成左右文件的传输。在Xshell可以直接使用ls命令来看看是否传输成功,如下:
5.代码演示部分
我们将下面代码在Windows下编辑保存好,
myselect.c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
void InitServerAddr(struct sockaddr_in* addr)
{
bzero(addr,sizeof(struct sockaddr_in));
addr->sin_addr.s_addr = inet_addr("0.0.0.0");
addr->sin_family = AF_INET;
addr->sin_port = htons(9090);
}
#define prr_exit(x) do{perror(x);return -1;}while(0);
int main(void){
int pic_fd = open("./mypic.jpg",O_RDONLY);
if(pic_fd < 0)
{
perror("[open]");
return -1;
}
int server_sockfd,client_sockfd;
struct sockaddr_in server_addr;
if ((server_sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
prr_exit("[create server socket]");
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("0.0.0.0");//服务器端的IP固定是这个。
server_addr.sin_port = htons(9090);
int opt = SO_REUSEADDR;
setsockopt(server_sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if (bind(server_sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr)) < 0)
prr_exit("[bind]");
listen(server_sockfd,20);
fd_set read_set,all_set;
FD_ZERO(&all_set);
FD_SET(server_sockfd,&all_set);
int client_fd_array[FD_SETSIZE];
for (int i = 0; i < FD_SETSIZE; i++)
client_fd_array[i] = -1;
int maxfd = server_sockfd,nready,nread;
char buf[128];
char buf1[128];
int k = 0;
char num[3] = "";
int flag = -1;
while(1)
{
read_set = all_set;
for (int i = 0; i < FD_SETSIZE; i++)
{
if (maxfd < client_fd_array[i])
maxfd = client_fd_array[i];
}
if ((nready = select(maxfd+1,&read_set,NULL,NULL,NULL)) < 0)
prr_exit("[select]");
if(FD_ISSET(server_sockfd,&read_set))
{
if ((client_sockfd = accept(server_sockfd,NULL,NULL)) < 0){
prr_exit("[accept]");
}
FD_SET(client_sockfd,&all_set);
for (int i = 0; i < FD_SETSIZE; i++)
{
if (client_fd_array[i] < 0)
{
client_fd_array[i] = client_sockfd;
if (maxfd < client_sockfd)
maxfd = client_sockfd;
break;
}
}
if (--nready == 0)
continue;
}
for (int i = 0; i < maxfd+1; i++)
{
if (client_fd_array[i] < 0)
continue;
if (FD_ISSET(client_fd_array[i],&read_set))
{
bzero(buf,128);
bzero(buf1,128);
if ((nread = read(client_fd_array[i],buf,128)) == 0)
{
FD_CLR(client_fd_array[i],&all_set);
client_fd_array[i] = -1;
close(client_fd_array[i]);
}
else if (nread > 0)
{
if(buf[0] != '^' && buf[1] != '|' && buf[2] != '3')
{
for(int j = 0; j < FD_SETSIZE; j++)
{
sprintf(buf1,"[%d]%s",client_fd_array[i],buf);
write(client_fd_array[j],buf1,strlen(buf1));
}
printf ("recv form client = %s\n",buf);
if(buf[0] == '$')
{
bzero(num,sizeof(num));
num[0] = buf[1];
num[1] = buf[2];
for(k = 0;k < FD_SETSIZE; k++)
{
if(client_fd_array[k] == atoi(num))
{
while(1)
{
bzero(buf,sizeof(buf));
int pic_len = read(pic_fd,buf,sizeof(buf));
write(client_fd_array[k],buf,pic_len);
if(pic_len < 128) {flag = 1;break;}
}
}
if(flag == 1)
{
bzero(num,sizeof(num));
lseek(pic_fd,0,SEEK_SET);
flag = -1;
break;
}
if(k == FD_SETSIZE - 1)
{
bzero(num,sizeof(num));
break;
}
}
}
}
bzero(buf,sizeof(buf));
if (--nready == 0)
break;
}
}
}
//for(int i = 0;i )
}
return 0;
}
然后通过Xftp,直接从界面的左边拖到界面的右边,在Xshell登录的Ubuntu终端界面,直接gcc myselect.c -o myselect编译即可,然后./myselect执行。
好,下面是QT端的代码:
main.cpp
#include "tcpclientgui.h"
#include <QApplication>
#include <QTextCodec>
int main(int argc, char *argv[])
{
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
QApplication a(argc, argv);
TcpClientGui w;
w.show();
return a.exec();
}
tcpclientgui.h
#ifndef TCPCLIENTGUI_H
#define TCPCLIENTGUI_H
#include <QWidget>
#include <QListWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QTcpSocket>
#include <QTime>
#include <QTimer>
#include <QHostAddress>
class TcpClientGui : public QWidget
{
Q_OBJECT
public:
TcpClientGui(QWidget *parent = 0);
~TcpClientGui();
QListWidget *listwidget;
QLineEdit *ipedit;
QTextEdit *textedit;
QTcpSocket *tcpsocket;
bool isconnect; //保存是否连接
public slots:
void slotConnect();
void slotSendData();
void slotSlipSendData();
void slotRecvData();
};
#endif // TCPCLIENTGUI_H
tcpclientgui.cpp
#include "tcpclientgui.h"
TcpClientGui::TcpClientGui(QWidget *parent)
: QWidget(parent)
{
this->setWindowTitle("TCP客户端");
isconnect = false;
listwidget = new QListWidget;
ipedit = new QLineEdit;
QPushButton *connect_button = new QPushButton("连接");
this->connect(connect_button,SIGNAL(clicked(bool)),this,SLOT(slotConnect()));
QHBoxLayout *connect_layout = new QHBoxLayout;
connect_layout->addWidget(ipedit);
connect_layout->addWidget(connect_button);
textedit = new QTextEdit;
textedit->setMaximumHeight(50);
QPushButton *send_button = new QPushButton("发 送");
this->connect(send_button,SIGNAL(clicked(bool)),this,SLOT(slotSendData()));
QPushButton *cancel_button = new QPushButton("取 消");
this->connect(cancel_button,SIGNAL(clicked(bool)),this,SLOT(close()));
QHBoxLayout *send_layout = new QHBoxLayout;
send_layout->addStretch();
send_layout->addWidget(send_button);
send_layout->addWidget(cancel_button);
QVBoxLayout *vlayout = new QVBoxLayout(this);
vlayout->addWidget(listwidget);
vlayout->addLayout(connect_layout);
vlayout->addWidget(textedit);
vlayout->addLayout(send_layout);
QTimer* timer = new QTimer;
this->connect(timer,SIGNAL(timeout()),this,SLOT(slotSlipSendData()));/*第一个参数是信号发出者,第二个参数是发出的信号,第三个信号是信号的接收者,第四个参数是调用的函数*/
/*启动定时器,如果不想这个时候启动 ,那下面这句到需要的时候再写*/
timer->start(5000);//时间单位是ms
tcpsocket = new QTcpSocket(this);
}
void TcpClientGui::slotConnect()
{
QString ipstr = ipedit->text(); //获取连接的ip
unsigned short port = 9090;
if(!ipstr.trimmed().isEmpty())
{
tcpsocket->connectToHost(QHostAddress(ipstr),port); //连接到服务器
qDebug() << "connect ok" << endl;
isconnect = true;
this->connect(tcpsocket,SIGNAL(readyRead()),this,SLOT(slotRecvData())); //连接有数据可读信号到接收数据的槽
listwidget->addItem("connected to " + ipstr + " " + QTime::currentTime().toString("hh:mm"));
listwidget->setCurrentRow(listwidget->count() - 1);
}
}
void TcpClientGui::slotSendData()
{
QString mess = "";
QString text = "";
if(isconnect)
{
mess = textedit->toPlainText();
tcpsocket->write(mess.toLatin1());
text = "send to : " + ipedit->text() + " : " + QTime::currentTime().toString("hh:mm") + "\n" + mess;
listwidget->addItem(mess);
listwidget->setCurrentRow(listwidget->count() - 1);
textedit->clear();
}
}
void TcpClientGui::slotSlipSendData()
{
QString mess = "^|3";
QString text = "";
if(isconnect)
{
//mess = textedit->toPlainText();
tcpsocket->write(mess.toLatin1());
text = "send to : " + ipedit->text() + " : " + QTime::currentTime().toString("hh:mm") + "\n" + mess;
// listwidget->addItem(text);
// listwidget->setCurrentRow(listwidget->count() - 1);
//textedit->clear();
}
}
void TcpClientGui::slotRecvData()
{
QString text = "";
QByteArray array = tcpsocket->readAll();
text = "rect from : " + ipedit->text() + " : " + QTime::currentTime().toString("hh:mm") + "\n" + array;
listwidget->addItem(text);
listwidget->setCurrentRow(listwidget->count() - 1);
}
TcpClientGui::~TcpClientGui()
{
tcpsocket->disconnectFromHost();
tcpsocket->deleteLater();
tcpsocket = NULL;
}
QT界面如下:
完成这些后,我们编译运行QT端代码结果如下:
在连接一栏,输入我们云服务器主机的公网IP,点击连接,如下:
TCP客户端输入Hello,云服务器端接收到,我们可以多开两个客户端看看效果。至此,我们完成了我们需要的简易效果-------QT群聊。
几点说明:QT编译运行后生成的.exe文件,放在别的主机上是运行不了的(没有安装配置相关环境)。那么我们可以通过这样的方式来解决:
QT编译后生成的.exe文件在别的主机上运行的方法