linux程序设计,消息队列,套接字以及QT的GUI编程学习

前言
时间一晃有过去了一周,虽然感觉这一周又遇到了不少杂事,但是确实还是学习了不少东西。回过头一看发现已经刷完了《linux程序设计》这本书了。
首先还是来总结回顾一下这一周所学习到了内容吧。


第十四章:信号量、共享内存和消息队列
首先这里使用的信号量与线程间通信使用的信号量略有不同,之前学习的信号量是相对简单的,使用sem_wait(sem_t *sem) 与sem_post(sem_t *sem)这两个函数就能简单的实现控制了,使用的互斥量是pthread_mutex_t的变量类型。是支持POSIX标准的信号量,优点是操作简单,但是只能支持多线程之间的通信,不能应用到多进程的场景下。
这一章中使用的是system V IPC标准的信号量,支持多进程之间的原子操作,使用的方法与线程间的方式相同,V操作使信号量加1, P操作使信号量减1。使用信号量只需要三个函数:

int semget(key_t key, int num_sems, int sem_flags); int semctl(int sem_id, int num_sem, int command, ...); int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

这几个操作函数与文件的打开读取其实非常的类似,首先使用semget,传递一个key进去,返回一个int新的semid,失败返回-1,与文件的open返回值非常的相似。然后使用semctl来SETVAL,给信号量赋予初始值,一般设置为1,然后就可以使用semop来进行PV操作了。同样在进行PV操作的时候程序会阻塞,并不会占用cpu时间。



共享内存同样是IPC标准里的,利用它可以实现两个进程的通信。一个进程在内存里写入数据后,另一个进程立刻可以读到。但是由于两个进程可以同时访问内存,所以如果要保证原子操作的话需要使用信号量进行通信。共享内存使用以下函数来完成:
void *shmat(int shm_id, const void *shm_addr, int shmflg); int shmctl(int shm_id, int cmd, struct shmid_ds *buf); int shmget(key_t key, size_t size, int shmflg); int shmdt(const void *shm_addr);

同样,首先使用一个shmget来获得共享内存,传入key,内存大小,以及访问权限(一般是0666 | IPC_CREAT)。成功返回int型的shm_id,失败返回-1。shmget创建的内存没有链接到进程的地址空间,需要再使用shmat函数完成。传入shm_id,第二个参数是链接到当前进程中的地址位置,通常使用0,让系统自行配置。第三个shmflg,一般设置为0,代表可以读写,也可以设置为SHM_RDONLY只读。调用成功后该函数返回共享内存的第一个字节的指针,失败返回-1。
shmdt使当前进程不可使用共享内存,并未删除。使用shmctl可以删除共享内存。
在测试P499页 的例子时,出现了一个小bug,两个进程使用同一个shm_id,调用shmat函数,书上给出的运行结果是相同的,但我自己运行返回的地址居然不相同,但是确实能够做到内存共享了。在网上查找得到的答案说是这个返回值不是实际上的物理地址,而是进程运行时的虚拟地址,我也不太确定是不是这样,我想应该是程序运行时的逻辑地址上加的offset量,这样想应该是一个合理的解释。


消息队列:
学完了消息队列之后我就完全不想使用管道了,虽然管道的好处多多,特别是没有限制的数据量已经传输速度上。
使用消息队列可以完全独立于进程,就好像把内容写入了文件,然后又从文件里读出来一样。
它使用四个函数:
msgctl msgget msgrcv msgsnd

使用方法类似,首先msgget得到msqid,标志位权限加上IPC_CREAT创建新的消息队列。
然后使用msgsnd与msgrcv发送与接收,使用时传递的消息必须以一个长整形变量message_type开始,第三个参数是传递消息的长度,但是不包括长整形变量,标志位如果使用IPC_NOWAIT则在消息队列满了的时候不阻塞,直接返回-1,否则等待消息队列里的消息被读走再发送。
msgctl主要用来删除消息队列。
使用消息队列之后可以很容易的完成对管道的CD应用程序的修改。


第十五章:套接字
这一部分本科的时候也编写过一些使用套接字的程序,不过那个时候并没有真正的想去弄清楚套接字编程的细节,所以其实还是一无所知。
【linux程序设计,消息队列,套接字以及QT的GUI编程学习】套接字的内容比较多,我感觉一时半会也不能总结的太清楚,所以先尽量的说一说。
首先说说服务器和客户端编程的过程吧,而不是TCPIP通信协议的运行过程。
首先是创建套接字,服务器端过程如下:
int socket(int domain, int type, int protocol)

一般使用的参数是(AF_INET, SOCK_STREAM/SOCK_DGRAM, 0)协议使用0代表默认。
然后定义套接字的地址,是一个结构体,AF_UNIX域与AF_INET不同,定义好了地址之后,服务器端使用bind函数给套接字命名,调用成果返回0,失败-1。然后服务器端使用listen函数监听套接字,再调用accept函数来与客户端建立连接,并且创建一个新的套接字用来通信。accept函数在没有客户连接的时候会阻塞,返回值是新的套接字描述符。


然后是客户端的过程,同样先使用socket函数创建套接字,但不用命名,然后使用connect函数来连接服务器端,地址参数要写服务器端的地址,使用的套接字是开始创建的未命名套接字。连接成返回0,失败返回-1。


至此客户端和服务器端的连接已经建立,然后可以使用read和write函数从套接字里写入和读取数据了。
特别要注意的是,在传递IP地址结构体的时候,需要用到几个函数:
inet_addr htonl htons ntohl ntohs

用于转换IP地址从字符串格式变成可用的格式。其他几个用于网络参数在本地模式和网络模式下切换。


以上这样的方式服务器一次只能服务一个客户端,还需要再改进。第一种方式是使用fork函数,创建新的进程来服务客户端。做法是在accept返回了新的套接字描述符了之后,调用fork函数,子进程与客户端通信,主进程直接调用close函数关闭套接字描述符即可。这里涉及到的问题就是套接字描述符是否可以这样简单的关闭?会不会影响到另一个进程的处理?这个问题最开始的时候让我就结了 了挺久,最后在一篇博客上找到了答案。具体的解答我会在另一篇文章里再来说。


书上提到的另一种实现的方法则是使用select函数。使用select函数可以把文件描述符放在一个集合里,然后调用select函数可以知道他们的读写错误状态,状态发生改变之后返回,然后判断是哪一个变化了,再根据结果做下一步处理。这个方法我感觉实际上可用程度不大,实现多客户端应该使用线程的方式去完成。


第十六章:用GTK+进行GHOME编程
第十七章:用Qt进KDE编程
十六章、十七章实际上讲的内容都是关于GUI编程的,由于GTK使用C语言实现的,我敲了两个代码之后发现对于用户界面来说,还是面向对象的语言实现方便的多,于是写了两个代码之后跳过了GTK部分,直接开始学习第十七章的内容。
很快,照着代码就实现了主窗口的创建,文字输入栏的创建,以及按钮的创建。学习这一部分感觉最有用的思想就是信号和槽了SIGNAL &SLOT ,使用connect函数链接起来就可以使用,方法完全是面向对象的思想,四个接口,信号发出者,发出者发出的信号,接受者,接受者接到之后做的处理。这样的话就可以很顺利的利用已有的模板来实现各种需要的功能。
第二个思想就是把相关的基础模板放在一个类里,比如实现一个搜索,一个输入文字模板(LineEdit),加上一个按钮(PushButton),就可以在一个类里通过类的成员函数很简单的实现需要的功能,并且能很好的复用。
本科的时候一直没有写过gui的程序,一直误以为写一个用户界面特别的厉害,现在发现其实都是调用别人写好的库而已了。真的不难,熟悉之后几天就能写一个界面了。真正难 的是设计好一个好的用户界面。


总结
到这里这步书我也全部的学习完了。收获感觉很大,见识到了以前从来没见识过的各种技术以及编程的发展。也了解了许多基本的标准库。下一步就是unix网络编程了。希望自己能更加努力,一步一步的进步。让自己的研究生日子过得充实并且毫不后悔。

    推荐阅读