在写一个C/S的客户端时,想要把收和发分开处理,于是很自然的想到了要用到线程。
在用C语言写的时候代码可以调试出来,因为是结构化程序设计,所以函数与函数之间耦合性不是很强,线程函数也就没有与其他函数起冲突。但是当我想要把代码改为C++实现时,出现问题了。
把函数封装在类里,写成成员函数的形式,体现内聚性。我们写的代码应该尽量的高内聚、低耦合。
一旦使用类,所有函数都封装作为类的成员函数,那么,创建线程时,必然会创建新的对象。接着执行新的对象的构造函数,执行线程函数,递归,程序会崩溃。。也尝试把线程函数写为类的友元函数 (类中函数声明前加friend),可是创建线程的函数 pthread_create() 的参数依然是个问题,如果传入套接字,那么谁来接收?如果是这个类的另一个对象接收,势必会执行默认的拷贝构造函数。所以还是会出现上述问题。
当然,如果形参写的是引用的话,不会多拷贝一份实参,这个方法我还没有尝试。
想到的另一种方式是,建立两个类。一个用于接收,另一个用于发送。此外,把线程函数的声明放在两个类的外面。
当发送类与服务器建立连接时,执行线程函数,在线程函数中创建接收类的对象。把发送类与服务器建立的套接字,作为线程函数的参数传递进去。接着,线程函数中,此参数作为结束类的构造函数参数传入。在结束类中就可以实现与服务器的通信。同时,实现了发送与结束的分离,即实现多线程。
下面是部分代码,供以参考:
//构造函数,实现初始化的操作
TellerSend::TellerSend()
{
sfd = -1;
tcpConnect("127.0.0.1", 9999);
if (-1 == sfd)
{
return;
}
cout << "connnect to server success!" << endl;
//开线程(只用于接收服务器的应答消息)
int ret = -1;
int newfd = this->sfd; //将此套接字作为线程参数传递
pthread_t rcvThread;
ret = pthread_create(&rcvThread, NULL, handleRcv, (void *)newfd);
if (-1 == ret)
{
printf("子线程退出!\n");
newfd = -1;
}
handleSnd(); //向服务器发送请求(主线程)
}
接着是线程函数:
//接收服务器的应答消息
void *handleRcv(void *arg)
{
//创建新类的对象,参数为已经与服务器建立连接的套接字
int newsfd = (int)arg;
TellerRecv trecv(newsfd);
return 0;
}
然后是接收类的构造函数:
//构造函数,只用于接收(根据接收的包头信息的不同,执行相应的成员函数)
TellerRecv::TellerRecv(int newsfd = -1)
{
this->sfd = newsfd;
cout << "this->sfd = " << newsfd << endl;
int ret = -1;
QueryHead qhead;
while (1)
{
//此套接字sfd 是新类的私有成员
ret = recv(sfd, (char *)&qhead, sizeof(qhead), 0);
if (-1 == ret)
{
close(sfd);
exit(-1);
}
//根据接收到的包头中的命令,接收之后不同的包体
switch ( qhead.cmd )
{
case 0:
{
close(sfd);
exit(0);
}break;
case 1:
{
//....
}break;
//...
default:
break;
}
}
}
最后给出头文件中类的声明:
//发送类
class TellerSend
{
public:
TellerSend(); //构造函数,实现初始化的操作
~TellerSend(){};
void tcpConnect(const char *ip, const int port); //连接服务器
void handleSnd();
int getCommand(); //菜单项
void LoginSnd(); //柜员注册
void LandSnd(); //柜员登陆
void QuitSnd(); //客户端退出
protected:
private:
int sfd; //连接服务器的套接字
int cmd; //接收菜单项输入(命令)
};
//接收类
class TellerRecv
{
public:
TellerRecv(int sfd); //构造函数的参数通过线程参数传递(实际上是前一个类的私有成员)
~TellerRecv(){};
void LoginRcv();
void QuitRcv();
void LandRcv();
protected:
private:
int sfd;
int cmd;
};
extern int LandFlag; //登陆成功标志
extern void *handleRcv(void *arg); //线程函数(在此线程函数中创建新的对象)
extern void LoginInput(RequestLogin &arg); //注册信息录入
#endif
如果读者有更好的方式处理线程和类的问题,欢迎指正。