📄 基于 linux 和 minigui 的嵌入式系统软件开发指南(六).htm
字号:
border=1><TBODY>
<TR>
<TD><PRE><CODE>
#define MAX_SYS_REQID 0x0010
#define MAX_REQID 0x0018
/*
* Register user defined request handlers for server
* Note that user defined request id should larger than MAX_SYS_REQID
*/
typedef int (* REQ_HANDLER) (int cli, int clifd, void* buff, size_t len);
BOOL GUIAPI RegisterRequestHandler (int req_id, REQ_HANDLER your_handler);
REQ_HANDLER GUIAPI GetRequestHandler (int req_id);
</CODE></PRE></TD></TR></TBODY></TABLE></P>
<P>服务器可以通过调用RegisterRequestHandler 函数注册一些请求处理函数。注意请求处理函数的原型由REQ_HANDLER
定义。还要注意系统定义了MAX_SYS_REQID 和 MAX_REQID 这两个宏。MAX_REQID 是能够注册的最大请求 ID 号,而
MAX_SYS_REQID 是系统内部使用的最大的请求 ID 号,也就是说,通过RegisterRequestHandler 注册的请求 ID
号,必须大于 MAX_SYS_REQID 而小于或等于 MAX_REQID。</P>
<P>作为示例,我们假设服务器替客户计算两个整数的和。客户发送两个整数给服务器,而服务器将两个整数的和发送给客户。下面的程序段在服务器程序中运行,在系统中注册了一个请求处理函数:
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
typedef struct TEST_REQ
{
int a, b;
} TEST_REQ;
static int send_reply (int clifd, void* reply, int len)
{
MSG reply_msg = {HWND_INVALID, 0};
/* 发送一个空消息接口给客户,以便说明这是一个请求的应答 */
if (sock_write (clifd, &reply_msg, sizeof (MSG)) < 0)
return SOCKERR_IO;
/* 将结果发送给客户 */
if (sock_write (clifd, reply, len) < 0)
return SOCKERR_IO;
return SOCKERR_OK;
}
static int test_request (int cli, int clifd, void* buff, size_t len)
{
int ret_value = 0;
TEST_REQ* test_req = (TEST_REQ*)buff;
ret_value = test_req.a + test_req.b;
return send_reply (clifd, &ret_value, sizeof (int));
}
...
RegisterRequestHandler (MAX_SYS_REQID + 1, test_request);
...</CODE></PRE></TD></TR></TBODY></TABLE></P>
<P>而客户程序可以通过如下的程序段向客户发送一个请求获得两个整数的和:
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
REQUEST req;
TEST_REQ test_req = {5, 10};
int ret_value;
req.id = MAX_SYS_REQID + 1;
req.data = &rest_req;
req.len_data = sizeof (TEST_REQ);
cli_request (&req, &ret_value, sizeof (int));
printf ("the returned value: %d\n", ret_value); /* ret_value 的值应该是 15 */
</CODE></PRE></TD></TR></TBODY></TABLE></P>
<P>读者已经看到,通过这种简单的请求/应答技术,MiniGUI-Lite
客户程序和服务器程序之间可以建立一种非常方便的进程间通讯机制。但这种技术也有一些缺点,比如受到 MAX_REQID
大小的影响,通讯机制并不是非常灵活,而且请求只能发送给MiniGUI-Lite 的服务器程序(即 mginit)处理等等。</P>
<P><SPAN class=atitle3>3.2 复杂的 UNIX Domain Socket
封装</SPAN><BR>为了解决上述简单请求/应答机制的不足,MiniGUI-Lite 也提供了经过封装的 UNIX Domain Socket
处理函数。这些函数的接口原型如下(include/minigui.h):
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
/* Used by server to create a listen socket.
* Name is the name of listen socket.
* Please located the socket in /var/tmp directory. */
/* Returns fd if all OK, -1 on error. */
int serv_listen (const char* name);
/* Wait for a client connection to arrive, and accept it.
* We also obtain the client's pid and user ID from the pathname
* that it must bind before calling us. */
/* returns new fd if all OK, < 0 on error */
int serv_accept (int listenfd, pid_t *pidptr, uid_t *uidptr);
/* Used by clients to connect to a server.
* Name is the name of the listen socket.
* The created socket will located at the directory /var/tmp,
* and with name of '/var/tmp/xxxxx-c', where 'xxxxx' is the pid of client.
* and 'c' is a character to distinguish diferent projects.
* MiniGUI use 'a' as the project character.
*/
/* Returns fd if all OK, -1 on error. */
int cli_conn (const char* name, char project);
#define SOCKERR_IO -1
#define SOCKERR_CLOSED -2
#define SOCKERR_INVARG -3
#define SOCKERR_OK 0
/* UNIX domain socket I/O functions. */
/* Returns SOCKERR_OK if all OK, < 0 on error.*/
int sock_write_t (int fd, const void* buff, int count, unsigned int timeout);
int sock_read_t (int fd, void* buff, int count, unsigned int timeout);
#define sock_write(fd, buff, count) sock_write_t(fd, buff, count, 0)
#define sock_read(fd, buff, count) sock_read_t(fd, buff, count, 0)
</CODE></PRE></TD></TR></TBODY></TABLE></P>
<P>上述函数是 MiniGUI-Lite 用来建立系统内部使用的 UNIX Domain Socket
并进行数据传递的函数,是对基本套接字系统调用的封装。这些函数的功能描述如下:
<UL class=n01>
<LI>serv_listen:服务器调用该函数建立一个监听套接字,并返回套接字文件描述符。建议将服务器监听套接字建立在 /var/tmp/
目录下。
<LI>serv_accept:服务器调用该函数接受来自客户的连接请求。
<LI>cli_conn:客户调用该函数连接到服务器,其中 name 是客户的监听套接字。该函数为客户建立的套接字将保存在 /var/tmp/
目录中,并且以 -c 的方式命名,其中 c 是用来区别不同套接字通讯用途的字母,由 project 参数指定。MiniGUI-Lite
内部使用了 'a',所以由应用程序建立的套接字,应该使用除 'a' 之外的字母。
<LI>sock_write_t:在建立并连接之后,客户和服务器之间就可以使用 sock_write_t 函数和 sock_read_t
函数进行数据交换。sock_write_t 的参数和系统调用 write 类似,但可以传递进入一个超时参数,注意该参数以 10ms
为单位,为零时超时设置失效,且超时设置只在 mginit 程序中有效。
<LI>sock_read_t:sock_read_t 的参数和系统调用 read类似,但可以传递进入一个超时参数,注意该参数以 10ms
为单位,为零时超时设置失效,且超时设置只在 mginit 程序中有效。</LI></UL>
<P></P>
<P>下面的代码演示了作为服务器的程序如何利用上述函数建立一个监听套接字:
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
#define LISTEN_SOCKET "/var/tmp/mysocket"
static int listen_fd;
BOOL listen_socket (HWND hwnd)
{
if ((listen_fd = serv_listen (LISTEN_SOCKET)) < 0)
return FALSE;
return RegisterListenFD (fd, POLL_IN, hwnd, NULL);
}</CODE></PRE></TD></TR></TBODY></TABLE></P>
<P>当服务器接收到来自客户的连接请求是,服务器的 hwnd 窗口将接收到 MSG_FDEVENT 消息,这时,服务器可接受该连接请求:
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
int MyWndProc (HWND hwnd, int message, WPARAM wParam, LPARAM lParam)
{
switch (message) {
...
case MSG_FDEVENT:
if (LOWORD (wParam) == listen_fd) { /* 来自监听套接字 */
pid_t pid;
uid_t uid;
int conn_fd;
conn_fd = serv_accept (listen_fd, &pid, &uid);
if (conn_fd >= 0) {
RegisterListenFD (conn_fd, POLL_IN, hwnd, NULL);
}
}
else { /* 来自已连接套接字 */
int fd = LOWORD(wParam);
/* 处理来自客户的数据 */
sock_read_t (fd, ...);
sock_write_t (fd, ....);
}
break;
...
}
}</CODE></PRE></TD></TR></TBODY></TABLE></P>
<P>上面的代码中,服务器将连接得到的新文件描述符也注册为监听描述符,因此,在 MSG_FDEVENT 消息的处理中,应该判断导致
MSG_FDEVENT 消息的文件描述符类型,并做适当的处理。</P>
<P>在客户端,当需要连接到服务器时,可通过如下代码:
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
int conn_fd;
if ((conn_fd = cli_conn (LISTEN_SOCKET, 'b')) >= 0) {
/* 向服务器发送请求 */
sock_write_t (fd, ....);
/* 获取来自服务器的处理结果 */
sock_read_t (fd, ....);
}</CODE></PRE></TD></TR></TBODY></TABLE></P>
<P><A name=4><SPAN class=atitle2>4 编写可移植代码</SPAN></A><BR>我们知道,许多嵌入式系统所使用的
CPU 具有和普通台式机 CPU
完全不同的构造和特点。但有了操作系统和高级语言,可以最大程度上将这些不同隐藏起来。只要利用高级语言编程,编译器和操作系统能够帮助程序员解决许多和
CPU 构造及特点相关的问题,从而节省程序开发时间,并提高程序开发效率。然而某些 CPU
特点却是应用程序开发人员所必须面对的,这其中就有如下几个需要特别注意的方面:
<UL class=n01>
<LI>字节顺序。一般情况下,我们接触到的 CPU 在存放多字节的整数数据时,将低位字节存放在低地址单元中,比如常见的 Intel x86 系列
CPU。而某些 CPU 采用相反的字节顺序。比如在嵌入式系统中使用较为广泛的 PowerPC 就将低位字节存放在高地址单元中。前者叫
Little Endian 系统;而后者叫 Big Endian 系统。
<LI>在某些平台上的 Linux 内核,可能缺少某些高级系统调用,最常见的就是与虚拟内存机制相关的系统调用。在某些 CPU 上运行的
Linux 操作系统,因为 CPU 能力的限制,无法提供虚拟内存机制,基于虚拟内存实现的某些 IPC 机制就无法正常工作。比如在某些缺少 MMU
单元的 CPU 上,就无法提供 System V IPC 机制中的共享内存。 </LI></UL>
<P></P>
<P>为了编写具有最广泛适应性的可移植代码,应用程序开发人员必须注意到这些不同,并且根据情况编写可移植代码。这里,我们将描述如何在 MiniGUI
应用程序中编写可移植代码。</P>
<P><SPAN class=atitle3>4.1 理解并使用 MiniGUI 的 Endian 读写函数
</SPAN><BR>为了解决上述的第一个问题,MiniGUI 提供了若干 Endian 相关的读写函数。这些函数可以划分为如下两类:
<UL class=n01>
<LI>用来交换字节序的函数。包括ArchSwapLE16、ArchSwapBE16 等。
<LI>用来读写标准I/O 流的函数。包括MGUI_ReadLE16、MGUI_ReadBE16 等。</LI></UL>
<P></P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -