上文中对面向连接的C/S模型中的服务器进行了简单描述,本文将说明如何编写一个客户端应用程序。与服务器程序不同,客户端程序要求有友好的交互界面,因此本文所示的客户端程序采用Qt和linux C共同完成。客户端的界面部分采用Qt完成,而与服务器间具体的通信部分则通过linux C语言完成。
1.界面设计
通过Qt Designer可快速设计好客户端界面,并对四个按钮所对应的信号和槽函数进行连接。客户端所对应的类为ClientUI。
2.与服务器间通信的编程
由于Qt是对C++的一种扩展,因此我们必须将使用Linux C编写的客户端通信程序封装成类,这里我们将其定义为ClientConnection。
我们已经对客户端的界面和通信部分分别定义了两个类,但是如何将两者结合在一起?方法很简单。将ClientConnection类的对象cli作为ClientUI类的成员,然后在具体的槽函数中对应相应的通信函数。和上文分析服务器编程的方法不同,本文将以分析客户端对应的槽函数的方式来说明如何编写客户端程序。
2.1连接按钮的槽函数
在连接按钮对应的槽函数中,首先创建客户端套接字描述符;然后从输入框中获得服务器的IP地址;再通过connect函数创建一个连接。
connect函数的原型如下:
#include < sys/socket.h > int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
connect函数用于在sockfd套接字上创建一个连接,sockfd即客户端套接字;addr即为服务器端的套接字地址;addrlen为addr的长度。我们将connect函数封装成ClientConnection类中的connectingSocket函数,该成员函数在连接按钮所对应的槽函数中被调用。:
int ClientConnection::connectingSocket() { if (::connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1) { return 0; } return 1; }
在该槽函数中,可以这样使用连接函数:
if (cli.connectingSocket() == 1) { ui->statusLabel->setText("connecting to server success!"); } else { ui->statusLabel->setText("ERROR:connecting to server fail!"); //exit(1); }
当客户端连接至服务器成功后,客户端就可以跟服务器端进行数据的传送。
2.2 修改按钮的槽函数
该槽函数的工作比较简单,使得文本编辑区可被编辑。
2.3 应用按钮的槽函数
在该槽函数中,将文本编辑区的数据发送至服务器端;或从服务器接受数据显示在这个文本编辑区中。发送和接受数据的API仍然是send和recv函数,不过这里我们将这两个函数封装成ClientConnection类中的sendData成员和recvData成员:
int ClientConnection::recvData(char *buf) { if ((recvbytes = recv(sockfd, buf, MAX_DATA_NUM, 0)) == -1) { return 0; } buf[recvbytes] = '\0'; return 1; } int ClientConnection::sendData(char *msg, int len) { if (send(sockfd, msg, len, 0) == -1) { printf("send error!\n"); return 0; } return 1; }
这两个成员函数在该槽函数的适当位置被调用。
2.4退出按钮的槽函数
退出按钮的槽函数中只需断开客户端和服务器之间的连接即可。
参考:
1.Qt Assistant
2.Linux C编程实战;童永清 著;人民邮电出版社;
能告诉我具体实现的过程和代码吗??
我用Linux C实现了一个简单的端口扫描器,然后又要求用Qt实现一个图形界面。
具体的代码和界面怎吗连接呢??
急急……拜托了!!
[回复一下]
edsionte 回复:
4月 26th, 2011 at 23:19
@ada wu, Qt是对C++的图形扩展,因此必须使用C++的socket接口与服务器通信。
只要你C,S两端的数据格式一致,那么通信就不是问题。
具体你可以一步步的进行,先进行连接测试,如果成功再试着发送一个整数给客户端等。
总之,慢慢来,要有耐心。
[回复一下]
ada wu 回复:
4月 28th, 2011 at 09:44
@edsionte, 恩!!我是有点心急,谢谢你的提点!^0^
[回复一下]
edsionte 回复:
4月 28th, 2011 at 10:47
@ada wu, 请问你是怎么来到我的博客的?
[回复一下]
ada wu 回复:
4月 28th, 2011 at 16:21
@edsionte, 呵呵……我是只超级大菜鸟呐!正在做毕业设计,在
命令行模式实现了个简单的端口扫描器,想着做个图形界面演示,
网上说Qt很好用,因为我的代码是纯粹的C嘛,我就在网上搜——
qt和C怎样连接,很荣幸就进了你的博客啊!!
[回复一下]
edsionte 回复:
4月 28th, 2011 at 17:19
@ada wu, 希望你的毕设顺利,你是那个学校滴?
我的qq:541105115,加我再聊哦!
[回复一下]
请问你封装自己的类的时候,网络链接的close(sockfd)怎么弄?求交流
qq:553716434
[回复一下]
edsionte 回复:
11月 23rd, 2011 at 11:47
@moonzixing, 内部封装close的时候,直接::close() 即可。::符号可以直接使用Linuxc中的close。不知道你是否想要这个效果。
[回复一下]
moonzixing 回复:
11月 23rd, 2011 at 14:04
@edsionte, 是的,已经解决这个了,thank you
[回复一下]
你好!想请教一下您!
我原来在LInux的命令行下做了一个很简单的界面,现在想用Qt界面,服务器端使用纯C来写的,客户端也是的。就是想把客户端的相关函数代码加到Qt界面的工程中,但编译时老是在调用原有的c函数时出现invalid use of member 这种错误,但是也不是所有情况都是这样的,在构造函数中调用相关的函数没有任何问题,但是在槽对应的函数中调用.h中定义的文件就会出现这种问题。目前主要就是登陆界面这块。
[回复一下]
edsionte 回复:
5月 14th, 2012 at 14:01
@zbn2008, 理论上是可以的。而且我之前也做过实验。因为QT是基于C++的,因此你在QT工程里可以引用C的头文件,也就相应的可以引用C函数。
[回复一下]
zbn2008 回复:
5月 14th, 2012 at 21:17
@edsionte, 非常感谢你的回复,我也是这样想的,但是在调用相关的C函数的时候就出现invalid use of member 这种错误,查了很多资料也咩有解决。还请您多多指点。如果可以的话,可以吧相关的文件贴出来,或者传给你看一下
[回复一下]
edsionte 回复:
5月 14th, 2012 at 22:56
@zbn2008, 你是用到了socket还是?具体你能定位到某个函数么?
[回复一下]
zbn2008 回复:
5月 15th, 2012 at 12:05
@edsionte, 写的代码中,就出现过这种问题,代码如下
result = connect_tracker_server(&sockfd);
if(result != 0)
{
logError(“File:%s,line:%d.Fail to connect_tracker_server!\n”,\
__FILE__,__LINE__);
QMessageBox::information(this, tr(“Error:”), tr(“Fail to connect the tracker server!”));
goto end;
}
还有一点需要说明的是,connect_tracker_server这个函数不是类的成员函数,只是在类的成员函数中调用了这个函数,其实他是在一个sockopt.h这个头文件中定义的
[回复一下]
zbn2008 回复:
5月 15th, 2012 at 12:07
@zbn2008, connect_tracker_server函数实现如下
int connect_tracker_server(int *sock)
{
int result;
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(pTrackerServerInfo->port);
result = inet_aton(pTrackerServerInfo->ip_addr,&server_addr.sin_addr);
if(result == 0)
{
return -1;
}
result = connect(*sock,(const struct sockaddr*)&server_addr,\
sizeof(server_addr));
if(result < 0)
{
return -1;
}
return 0;
}
其中pTrackerServerInfo 是一个结构指针,其结构原型为
typedef struct trackerserverinfo
{
char tracker_name[LINE_SIZE];
int port;
char ip_add[DBFS_IPADDR_SIZE];
int timeout;
}TrackerServerInfo;
问题已经解决了,谢谢!
[回复一下]
edsionte 回复:
5月 15th, 2012 at 21:50
@zbn2008, 怎么解决的?
[回复一下]
请教一下,在封装类里,你是把数据定义放在.h里,并且设为私有,在.cpp里,构造函数中,初始化套接字么?
[回复一下]
edsionte 回复:
8月 17th, 2012 at 14:26
@sinoon, 比如QT的UI界面中,有一个连接 按钮,当你点击这个按钮时就有一个点击事件发生,因此将connect接口封装的对这个事件的处理函数(也就是槽函数)中。
[回复一下]