日志标签 ‘C/S’

基于Socket API的C/S通信:以树形结构显示服务器端目录(1)

2011年8月10日

在基于socket API的C/S模型中,有时候需要这样的需求:

1.客户端可以即时获取服务器指定目录下的文件列表及属性;
2.在当前目录下点击某个子目录,可以进入并获取该子目录下的文件列表及其属性;
3.在当前目录下可以返回到上一级目录;

如果C/S模型基于FTP协议,那么这个需求就很容易实现,不过需要事先将服务器端的主机设置为FTP服务器。如果C/S模型是基于TCP/IP协议,那么实现起来就有一些麻烦。

本文所描述的C/S模型即基于TCP/IP协议,服务器程序采用Linux C编写,运行在Linux主机上;而客户端采用Qt以及原生的Socket API编写,以友好的交互界面运行在Windows或LInux主机上。

1.服务器程序实现概述

概括的说,服务器和客户端之间的通信总是“客户端发送请求–服务器接受请求并发送数据–客户端接受数据并解析”这样的循环过程。也就是说,服务器需要一直监听客户端发送的数据,根据发送数据的不同服务器作出相应的操作。客户端发送的数据均为字节流,但是根据其功能不同,我们可以将这些字节流分为两种:第一种为指令型数据,这些数据并不是客户和服务器之间真正交互的数据,而是客户端指定服务器做出相应操作的命令;第二种为请求数据,这类数据才是客户端和服务器用来进行数据交互的。

根据上面的理解,服务器和客户之间的交互模型大致是这样的:服务器和客户连接成功后,服务器首先将指定目录下的文件名及文件属性发送至客户端,客户端进行解析生成文件列表;当用户在客户端点击相应子目录(或上级目录名)后,客户端给服务器发送读取子目录(或上级目录)的指令;服务器收到该命令后,读取相应子目录(或上级目录)下的所有文件并发送至客户端。

在上述过程中,服务器对应的代码模型如下:

int server_dir(int client_fd)
{
	sendDir(client_fd, "/home/edsionte/");

	while (1) {
		memset(recvbuf, 0, CONFIGSTRUCT_LENGTH);
          	if (recv(client_fd, recvbuf, CONFIGSTRUCT_LENGTH, 0) < 0) {
	          	my_error("recv", errno, __LINE__);
           	}

		if (strcmp(recvbuf, "command1") == 0) {
			do_command1();
		} else if (strcmp(recvbuf, "cd") == 0) {
			sendNextDir(client_fd);
		} else if (strcmp(recvbuf, "command2") == 0) {
			do_command2();
		}
                …………
                else {
			do_anotherthing();
		}

	return 1;
}

比如客户端请求获得/home/edsionte/目录下的所有文件列表,那么一开始先通过sendDir函数获得该目录下的所有文件。接着服务器通过进入一个死循环而实现无限监听客户端到功能,服务器根据从客户端接受的不同“指令”从而做出相应处理。比如当服务器检测到客户端发送的数据为“cd”时,则进入sendNextDir函数,该函数即获得/home/edsionte/下某个子目录下的所有文件。

2.服务器的具体实现

在实现服务器的过程中主要涉及三个函数,下面将依次对所涉及的函数进行说明。

readDir()读取pathname目录下的所有文件,将文件以其相关属性保存到dirbuf中,将该目录下的文件数保存到num中。该函数具体的实现过程容易理解:先打开pathname目录,再利用循环依次读取该目录下的所有文件及相应的文件属性。并且在读取的过程中,依次将文件名和文件属性按照一定的格式拼接成一个字符流。

int readDir(char *pathname, char *dirbuf)
{
	DIR *mydir;
	struct dirent *myentry;
	struct stat mystat;
	char filepath[DATA_MAX];
	int num = 1;

	if ((mydir = opendir(pathname)) == NULL) {
		my_error("opendir", errno, __LINE__);
	}

	if (pathname[strlen(pathname) - 1] != '/')
		pathname[strlen(pathname)] = '/';
	strcpy(dirbuf, pathname);
	strcat(dirbuf, "\n");
	strcat(dirbuf, "..:1\n");

	while ((myentry = readdir(mydir)) != NULL) {	
		if ((strcmp(myentry->d_name, ".") == 0) || (strcmp(myentry->d_name, "..") == 0))
			continue;
		memset(filepath, 0, DATA_MAX);
        	strcpy(filepath, pathname);
		strcat(dirbuf, myentry->d_name);

		strcat(filepath, myentry->d_name);
		if (lstat(filepath, &mystat) == -1) {
			my_error("stat", errno, __LINE__);
		}
	
		if (S_ISDIR(mystat.st_mode)) {
			strcat(dirbuf, ":1");
		} else {
			strcat(dirbuf, ":0");
		}

		strcat(dirbuf, "\n");
		num++;
	}//while

	return num;
}

sendDir()先开辟一定大小的内存空间dirbuf,再通过readDir()获得相应字节流;再通过客户端套接字client_fd,将pathname目录下的文件数和该目录下的数据字节流发送至客户端。

void sendDir(int client_fd, char *pathname)
{
	int num;

	char *dirbuf = NULL;
	if ((dirbuf = (char *)malloc(DATA_MAX * 10)) == NULL) {
		my_error("malloc", errno, __LINE__);
	}
	memset(dirbuf, 0, DATA_MAX * 10);
	num = readDir(pathname, dirbuf);
	if (send(client_fd, &num, sizeof(num), 0) == -1) {
		my_error("send", errno, __LINE__);
	}

	if (send(client_fd, dirbuf, strlen(dirbuf), 0) == -1) {
		my_error("send", errno, __LINE__);
	}
	free(dirbuf);
}

当服务器接收到客户端“进入下级目录”的命令(程序中用字节流“cd”表示)时就掉用sendNextDir()。该函数先接收客户端发送来的下级目录的完整路径,再调用sendDir()。

void sendNextDir(int client_fd)
{
	char pathname[DATA_MAX];
	memset(pathname, 0, DATA_MAX);
	if (recv(client_fd, pathname, DATA_MAX, 0) == -1) {
		my_error("recv", errno, __LINE__);
	}

	sendDir(client_fd, pathname);
}

通过上述四个函数就可以基本实现本文一开始提出的需求。

QTableWidget基本功能总结

2011年5月7日

QTableWidget类提供了一种基于条目(item)的表格视图模型,在该部件中数据以item为基本单位,每条数据(item)对应一个QTableWidgetItem类的对象,所有数据形成的item组成整个表格。接下来我们创建一个用来显示学生信息的表格,以此为例说明TableWidget的一些常用功能。

1.创建QTableWidget

首先创建studentInfo类,然后在Qt Desinger模式下创建一个QTableWidget部件,对其命名为stuTableWidget。通过在该部件上点击右键创建列项表头,创建完毕后也就同时指定了该表格的列项数。如下:

2.初始化

除了直接在设计模式下创建行数和列数外,我们还可以通过调用相应的方法来设定。比如我们通过setRowCount方法在studentInfo类的构造方法中即可指定行项数。

ui->stuTableWidget->setRowCount(30);

该方法在创建行的同时会自动创建一个用来显示行号的列项,如下:

如果我们的表格只用来显示信息,并不涉及对相应信息的修改,则可以通过下述方法将表格设置为只读模式:

 ui->stuTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);

setEditTriggers()是QAbstractItemView类中的一个方法,通过向该方法传递相应的参数以设置item的一些属性,比如NoEditTriggers参数可将item设置为只读,DoubleClicked代表双击时item可修改。而QTableWidget继承了QAbstractItemView方法,因此它可以使用该函数。

3.信息显示

表格视图中数据的获取随用途的不同而不同。如果使用于C/S模型的客户端,那么表格中的信息需要从服务器端发送到本地,再相应解析;如果使用在数据库中,则需要从数据库中获取相应信息。这里假定数据已经到达本地,我们通过下面的方法来显示数据信息。

void studentInfo::showInfo()
{
    QTableWidgetItem *tmpItem;

    tmpItem = new QTableWidgetItem(QString("04065061"));
    ui->stuTableWidget->setItem(0, 0, tmpItem);

    tmpItem = new QTableWidgetItem(QString("edsionte"));
    ui->stuTableWidget->setItem(0, 1, tmpItem);

    tmpItem = new QTableWidgetItem(QString("1988.01.28"));
    ui->stuTableWidget->setItem(0, 2, tmpItem);

    tmpItem = new QTableWidgetItem(QString("male"));
    ui->stuTableWidget->setItem(0, 3, tmpItem);

    tmpItem = new QTableWidgetItem(QString("Xi'an Institute of Posts and Telecommunications"));
    ui->stuTableWidget->setItem(0, 4, tmpItem);
}

上述的showInfo方法为第一行设定了相应信息,我们可以看到表格的一行中每个具体的列项都对应一个QTableWidgetItem对象,并通过在setItem方法中指定行号和列号将该item对象设置到表格的具体位置。在上述的showInfo方法中,我们分别通过该方法创建了第一行的第一到第五列的数据(行列下表从0开始)。

4.为表格数据添加右键菜单

有时候我们想通过点击鼠标右键对表格数据进行一些其他操作,比如复制、查看详情等,我们可以按照下面的方法来实现。为了实现点击右键弹出菜单这个功能,我们必须在类studentInfo类中声明一个菜单变量popMenu和一个菜单选项变量action。

class studentInfo : public QMainWindow
{
…………
private:
    Ui::studentInfo *ui;
    QMenu *popMenu;
    QAction *action;

private slots:
    void on_stuTableWidget_customContextMenuRequested(QPoint pos);
…………
};

声明完毕后,我们在studentInfo类的构造函数中对其进行初始化,如下:

    ui->stuTableWidget->setContextMenuPolicy(Qt::CustomContextMenu);
    popMenu = new QMenu(ui->stuTableWidget);
    action = new QAction("Copy", this);

setContextMenuPolicy方法用来设置widget菜单项的显示方法,而CustomContextMenu是唯一与邮件菜单有关的参数,因此这里我们将菜单显示方法设置为该类型。如果widget设置为CustomContextMenu时,当在数据上点击右键时就会发送customContextMenuRequested ( const QPoint & pos )信号,该信号还会捕捉到点击右键的位置,并用pos参数来存储。与此信号关联的槽函数我们定义如下:

void studentInfo::on_stuTableWidget_customContextMenuRequested(QPoint pos)
{
    popMenu->addAction(action);
    popMenu->exec(QCursor::pos());
}

我们首先将菜单选项action添加到邮件弹出菜单popMenu中,再通过exec方法在pos()位置显示该邮件菜单,pos()返回的位置即为点击鼠标的位置。

现在,如果点击右键菜单选项并不会发生任何动作,这是因为我们并没有关联相应的槽函数。由于具体的菜单选项不同,其函数的实现也不同,这里我们只给出框架,如下:

void studentInfo::rightClickedOperation()
{
    //do something
}

定义好槽函数,最关键的是与相应的信号连接。对于上述两个槽函数,我们可以使用两种方法进行信号和槽的关联:在Qt Desinger模式下添加或手动进行connect关联。对于customContextMenuRequested信号,我们使用前种方法实现信号和槽的关联;对于右键菜单选项的功能实现,我们可以通过connect函数实现,如下:

connect(action, SIGNAL(triggered()), this, SLOT(rightClickedOperation()));

C/S模型中的并发服务器

2011年4月20日

网络通信程序中最经典的模型就是客户端/服务器(C/S)模型。该模型中的服务器程序通常处于长时间运行的状态,当客户端程序主动对服务器程序发出网络请求时,服务器程序才做出具体的回应。也就是说,服务器大多数处于等待状态,只有收到客户端的网络请求才被唤醒,当处理完客户端的请求时候,又将处于等待状态。

上述同时只能处理一个客户端请求的服务器被称为迭代服务器(iterative server),这样的C/S模型只能应对那些简单的应用。大多数情况下我们希望服务器可以同时服务多个客户端程序,即服务器程序既能处理客户端发送来的请求同时又能接收其他客户端发送的请求。这样的服务器称为并发服务器(concurrent server)。

Linux中实现并发服务器的方法很简单:每当客户端对服务器有网络请求时,服务器程序就fork一个子服务器进程来处理这个客户的请求,而主服务器程序仍处于等待其他客户端网络请求的状态。基于这个原理,并发服务器的基本模型如下:

	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		my_error("socket", errno, __LINE__);
	}

	if (bind(sockfd, (struct sockaddr *)&my_addr,
				sizeof(struct sockaddr_in)) == -1) {
		my_error("bind", errno, __LINE__);
	}

	if (listen(sockfd, BACKLOG) == -1) {
		my_error("listen", errno, __LINE__);
	}

	while (1) {
		sin_size = sizeof(struct sockaddr_in);
		if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, &sin_size)) == -1) {
			my_error("accept", errno, __LINE__);
			continue;
		}

		if ((pid = fork()) == 0) {
			close(sockfd);
			process_client_request(client_fd);
			close(client_fd);
			exit(0);
		} else if (pid > 0)
			close(client_fd);
		else
			my_error("fork", errno, __LINE__);
	}

每当accept()接收到一个TCP连接时,主服务器进程就fork一个子服务器进程。子服务器进程调用相应的函数,通过client_fd(连接套接字)对客户端发来的网络请求进程处理;由于客户端的请求已被子服务进程处理,那么主服务器进程就什么也不做,通过sockfd(监听套接字)继续循环等待新的网络请求。

这里我们需要特别强调的是,在父子进程中需要关闭相应的套接字描述符。从上述代码中可以看到,子服务进程在处理客户端的网络请求之前关闭了监听套接字,主服务进程则关闭了连接套接字,子服务进程在处理完客户端请求后关闭了连接套接字。最后一种关闭套接字的情形比较合乎常理,这里我们重点关注前两种关闭套接字的情况。

我们的问题是:子服务进程关闭了监听套接字,服务器是否还能监听其他连接请求;父服务进程关闭了连接套接字,服务器是否还能够处理客户端请求。

如果理解了文件的的引用计数,这个问题就会迎刃而解。每个文件都有一个引用计数,该引用计数表示当前系统内的所有进程打开该文件描述符的个数。套接字是一种特殊的文件,当然也有引用计数。

在并发服务器这个例子当中,在accept函数之前,sockfd的引用计数为1;在fork函数执行之前,sockfd和client_fd的引用计数分别为1;当fork执行后,由于子进程复制了父进程的资源,所以子进程也拥有这两个套接字描述符,则此时sockfd和client_fd的引用计数都为2。只有当子进程处理完客户请求时,client_fd的引用计数才由于close函数而变为0。由于父服务器进程的主要任务是监听客户请求,则它关闭了连接套接字client_fd;而子进程的主要任务是处理客户请求,它不必监听其他客户请求,因此子进程关闭了sockfd。由上可知,父子进程关闭相应的套接字并不会影响其所负责的通信功能。

我们可以通过下面的两幅示意图更进一步了解fork函数执行前后的客户端和服务器之间的状态。

父子进程关闭相应套接字后客户端和服务器端的状态如下:

这就是监听套接字和连接套接字两者的最终状态,子进程处理客户请求,父进程监听其他客户请求。这样的并发服务器看似完美,但是会产生许多僵死进程,这是为什么?下文将会分析。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key