Posts Tagged ‘linux’

Linux下的socket编程-服务器

15 3 月, 2011

我们都知道,同一台计算机上的进程可以通过IPC(进程间通信)机制进行通信;而不同机算计上运行的进程则通过网络IPC,即套接字(socket)进行通信。Linux下的socket API是基于BSD套接口而是实现的,通过这些统一的API就可以轻松实现进程间的网络通信。此外,socket API即可用于面向连接(TCP)的数据传输,又可用于无连接(UDP)的数据传输。一般使用Client/Server交互模型进行通信。

本文以及下文将实现一个面向连接的C/S通信模型。本文首先介绍服务器端的实现。

1.创建套接字

#include < sys/socket.h >
int socket(int domain, int type, int protocol);

通过socket函数可以创建一个套接字描述符,这个描述符类似文件描述符。通过这个套接字描述符就可以对服务器进行各种相关操作。

该函数包含三个参数,domain参数用于指定所创建套接字的协议类型。通常选用AF_INET,表示使用IPv4的TCP/IP协议;如果只在本机内进行进程间通信,则可以使用AF_UNIX。参数type用来指定套接字的类型,SOCK_STREAM用于创建一个TCP流的套接字,SOCK_DGRAM用于创建UDP数据报套接字。参数protocol通常取0。对于本文所描述的服务器,创建套接字的示例代码如下:

	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

		printf("socket error!\n");
		exit(1);
	}

2.绑定套接字

对于服务器而言,它的IP地址和端口号一般是固定的。服务器的IP即为本地IP,而服务器的端口号则需要显示的指定。通过bind函数可将服务器套接字和一个指定的端口号进行绑定。

在具体介绍绑定函数之前,先说明一下socket中的套接字地址结构。由于套接字是通过IP地址和端口号来唯一确定的,因此socket提供了一种通用的套接字地址结构:

   struct sockaddr {
               sa_family_t sa_family;
               char        sa_data[14];
           }

sa_family指定了套接字对应的协议类型,如果使用TCP/IP协议则改制为AF_INET;sa_data则用来存储具体的套接字地址。不过在实际应用中,每个具体的协议族都有自己的协议地址格式。比如TCP/IP协议组对应的套接字地址结构体为:

struct sockaddr_in {
	short int sin_family; /* Address family */
	unsigned short int sin_port; /* Port number */
	struct in_addr sin_addr; /* Internet address */
	unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};

struct in_addr {
	unsigned long s_addr;
};

该地址结构和sockaddr结构均为16字节,因此通常在编写基于TCP/IP协议的网络程序时,使用sockaddr_in来设置具体地址,然后再通过强制类型转换为sockaddr类型。

绑定函数的函数原型如下:

       #include < sys/socket.h >
       int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数sockfd即服务器的套接字描述符;addr参数指定了将socket绑定到的本地地址;addrlen则为所使用的地址结构的长度。示例代码如下:

	memset(&my_addr, 0, sizeof(struct sockaddr_in));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(SERV_PORT);
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if(bind(sockfd, (struct sockaddr *)&my_addr,
				sizeof(struct sockaddr_in)) == -1) {

		printf("bind error!\n");
		exit(1);
	}

注意在上述代码中,将IP地址设置为INADDR_ANY,这样就既适合单网卡的计算机又适合多网卡的计算机。

3.在套接字上监听

对于C/S模型来说,通常是客户端主动的对服务器端发送连接请求,服务器接收到请求后再具体进行处理。服务器只有调用了listen函数才能宣告自己可以接受客户端的连接请求,也就是说,服务器此时处于被动监听状态。listen函数的原型如下:

       #include < sys/socket.h >
       int listen(int sockfd, int backlog);

sockfd为服务器端的套接字描述符,backlog指定了该服务器所能连接客户端的最大数目。超过这个连接书目后,服务器将拒绝接受客户端的连接请求。示例代码如下:

        #define BACKLOG 10
	if(listen(sockfd, BACKLOG) == -1) {

		printf("listen error!\n");
		exit(1);
	}

4.接受连接

listen函数只是将服务器套接字设置为等待客户端连接请求的状态,真正接受客户端连接请求的是accept函数:

      #include < sys/socket.h >
       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函数中所使用的参数都在上面的API中有所描述。accept函数执行成功后将返回一个代表客户端的套接字描述符,服务器进程通过该套接字描述符就可以与客户端进行数据交换。

5.数据传输

由于socket适用多个数据传输协议,则不同的协议就对应不同的数据传输函数。与TCP协议对应的发送数据和接受数据的函数如下:

       #include < sys/socket.h >
       #include < sys/types.h >
       ssize_t send(int sockfd, const void *buf, size_t len, int flags);
       ssize_t recv(int sockfd, void *buf, size_t len, int flags);

从这两个函数的原型可以看书,socket中的数据传输函数与普通文件的读写函数类似,只不过第一个参数需要传递套接字描述符;buf指定数据缓冲区;len为所传输数据的长度;flag一般取0。示例代码如下:

	while (1) {
		sin_size = sizeof(struct sockaddr_in);
		if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, &sin_size)) == -1) {
			printf("accept error!\n");
			continue;
		}
		/*
                 *进行相应的数据处理;
                 */
	}

如示例代码所示,通过while循环使得服务器对客户端进行持续监听。如果客户端有连接请求则新建一个代表客户端的套接字描述符,进而进行对客户端数据的接受和发送。

上述的几个函数属于网络编程中最基本的也是最关键的几个API,依次通过上述的方法就可以完成服务器端的程序的编写,具体的过程还可以参考下图:

正如上图所示,在处理完客户端的数据传输请求后,必须通过close函数关闭客户端的连接。

参考:

1.Linux C编程实战;童永清 著;人民邮电出版社;

基于Qt的Http编程-基本原理

26 2 月, 2011

在Qt中,使用QNetworkAccessManager类就可以完成基于Http协议的数据上传和下载,该类既可以发送网络请求,也可以接收网络回复。而具体的网络请求是通过QNetworkRequest类发送的,具体的网络回复是通过QNetworkReply类来接收的。

本文将利用上面的几个类实现一个简单的Http客户端,从指定的网址下载数据。

基本原理

由于QNetworkAccessManager类中包含了一组标准的数据请求函数,因此可以通过该类的对象发送数据请求函数;每个请求函数执行完毕时都回返回一个QNetworkReply对象。当所有请求的数据都到达本地后,将引发一个finished()信号,该信号关联了一个处理返回数据的槽函数。具体的实现可参考下述代码:

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    manager = new QNetworkAccessManager(this);
    connect(manager, SIGNAL(finished(QNetworkReply *)),
            this, SLOT(replyFinished(QNetworkReply*)));

    manager->get(QNetworkRequest(QUrl(
            "http://www.kerneltravel.net/")));
}

可以看到,上述的基本原理大部分都在构造函数中完成。首先创建了一个QNetworkAccessManager对象manager;接着将manager所引发的finished()信号与replyFinished()槽进行关联;最后通过get()发送数据请求。

get()用于发送请求并获得目标地址中的数据,具体的数据请求则是通过创建一个QNetworkRequest类的对象而完成的。只要数据请求发送成功,则开始下载数据。当所有的数据下载完成后,就返回一个QNetworkReply类型的对象。同时manager对象将发送一个finished()信号,引发replyFinished槽函数的执行。

当执行上述的槽函数时,就说明目标地址的数据已经下载完毕。此时槽函数要做的就是将这些数据显示出来。这里我们只只对文本数据进行转换。对这些数据的转换动作可参考下述的代码:

void Widget::replyFinished(QNetworkReply *reply)
{
    QTextCodec *codec = QTextCodec::codecForName("utf8");

    QString all = codec->toUnicode(reply->readAll());
    ui->textEdit->setText(all);
    reply->deleteLater();
}

为了能够正确显示中文,我们创建QTextCodec对象。利用readAll函数可以读取数据请求返回的所有数据,并且利用toUnicode函数将这些数据转换成QString类型。最后在用户界面中的文本编辑器中显示出来。

按照上面的方法就可以下载指定地址的数据。如下图:

当返回的数据显示完毕后,利用deleteLater函数将返回的数据删除。

上述的执行过程用数据流图表示如下:

上述内容只是对基于Http协议的数据下载做了简单的描述,并没有对界面设计作过多的介绍,关于界面的设计可见参考1。下文将对这个基本的Http客户端进行改造。

参考:

1. Qt Assistant

2. http://www.yafeilinux.com/?p=734

3. C++ GUI Qt 4编程(第二版); 电子工业出版社;Blanchette,J,Summerfield,M 著;闫锋欣 等译;

VFS中的基本关系图

21 11 月, 2010

分析了VFS中几个基本的数据结构后,根据自己的理解,我画了一个关系图。如下(点击看大图):

关于此图的说明:

1. 图中两个结构之间的虚线箭头表示这两者之间仍有有若干个类似结点相连接;

2. 图中阴影部分所示的进程并不是以实际的关系来表示的;

3. 图中彩色线条示意的场景:三个进程分别打开同一个文件。进程1和进程2打开同一路径的文件,因此两者的打开文件对应同一个目录项;而进程3打开的是一个硬链接文件,因此对应的目录项与前两者不同;

4.索引结点中i_sb_list链表是链接一个文件系统中所有inode的链表,因此相邻的inode之间均会由此链表链接;而i_list链接的是处于同一个状态的所有inode。所以,相邻inode之间并不一定链接在一起。

内核同步方法-原子操作

20 10 月, 2010

Latest Update:2010/10/23

原子操作

原子操作用于执行轻量级、仅执行一次的操作,比如修改计数器,某些条件下的增加值或设置位等。原子操作指的是指令以原子的方式执行。之所以用原子来修饰这种操作方式是因为它们均不可再分。也就是说,原子操作要么一次性执行完毕,要么就不执行,这些操作的执行过程是不可被打断的。原子操作的具体实现取决于体系架构,以下代码如无特别声明,均来自linux/arch/x86/include/asm/atomic.h中

内核中提供了两组原子操作的接口:原子整形操作和原子位操作。前者是一组对整形数据的操作,后者则是针对单独的位操作。通过上面的叙述我们可以知道,这写操作接口完成的动作都是原子的。

原子整形操作

0.数据结构

在linux/include/linux/types.h中有如下定义:

 190typedef struct {
 191        int counter;
 192} atomic_t;

原子类型内部只有一个整型的成员counter。

1.初始化/设置原子变量的值

  15#define ATOMIC_INIT(i)  { (i) }

  35static inline void atomic_set(atomic_t *v, int i)
  36{
  37        v->counter = i;
  38}

初始化宏的源码很明显的说明了如何初始化一个原子变量。我们在定义一个原子变量时可以这样的使用它:

  atomic_t v=ATOMIC_INIT(0);

atomic_set函数可以原子的设置一个变量的值。

2.获取原子变量的值

  23static inline int atomic_read(const atomic_t *v)
  24{
  25        return (*(volatile int *)&(v)->counter);
  26}

返回原子型变量v中的counter值。关键字volatile保证&(v->counter)的值固定不变,以确保这个函数每次读入的都是原始地址中的值。

3.原子变量的加与减

   47static inline void atomic_add(int i, atomic_t *v)
  48{
  49        asm volatile(LOCK_PREFIX "addl %1,%0"
  50                     : "+m" (v->counter)
  51                     : "ir" (i));
  52}

  61static inline void atomic_sub(int i, atomic_t *v)
  62{
  63        asm volatile(LOCK_PREFIX "subl %1,%0"
  64                     : "+m" (v->counter)
  65                     : "ir" (i));
  66}

加减操作中使用了内联汇编语句。

linux中的汇编语句都采用AT&T指令格式。带有C表达式的内联汇编格式为:__asm__ __volatile__(“InstructionList” :Output :Input :Clobber/Modify);其中asm(或__asm__)用来声明一个内联汇编语句;关键字volatile是可选的,选择此关键字意味着向编译器声明“不要动我所写的指令,我需要原封不动的保留每一条指令”,否则,编译器可能会对指令进行优化。InstructionList即我们所加的汇编语句。指令后面的3个部分(Output, Input,Clobber/Modify)是可选的,分别指输出操作数,输入操作数,修改位。

下面我们针对上述加减操作对上述内联汇编语句做简单的解释。

在加函数中,对变量加操作的原子型表现在使用单条的汇编语句完成。指令中的%1,%0分别代表输入操作数和输出操作数,它们用来标示变量。内联汇编语句中的操作数从0开始进行编号,并且优先为输出操作数编号,然后再依次为输入操作数编号。在指令后的输出部分,“+m”中的+表示输出变量是可读可写的,m表示输出操作数存储于内存单元中,而不是在寄存器中。在输入部分,“ir”中的i表示输入操作数是一个直接操作数,r表示存储与寄存器中。输入部分和输出部分中的括号内的C语言格式的变量分别对应汇编语句中的输入操作数和输出操作数。

汇编语句addl %1,%0就是将i加至v->counter上,这个加的过程是原子的。

了解了加操作,那么减操作也就不难理解了,这里不再赘述。

4.原子变量的自加自减

  93static inline void atomic_inc(atomic_t *v)
  94{
  95        asm volatile(LOCK_PREFIX "incl %0"
  96                     : "+m" (v->counter));
  97}

 105static inline void atomic_dec(atomic_t *v)
 106{
 107        asm volatile(LOCK_PREFIX "decl %0"
 108                     : "+m" (v->counter));
 109}

从内联的汇编语句中可以看到,自加自减均采用单条汇编指令,直接在输出数%0上加1或减1。这里的输出数%0的值即是v->counter变量的值。

5.操作并测试

atomic_sub_and_test函数实现的功能是原子的从v减去i,如果结果等于0,则返回1;否则返回0。

 77static inline int atomic_sub_and_test(int i, atomic_t *v)
  78{
  79        unsigned char c;
  80
  81        asm volatile(LOCK_PREFIX "subl %2,%0; sete %1"
  82                     : "+m" (v->counter), "=qm" (c)
  83                     : "ir" (i) : "memory");
  84        return c;
  85}

通过上述源码可以看到,第一条指令实现减操作;接着根据ZF的值设置c变量的值。sete命令的作用是,如果ZF=1,也就是说减的结果为0,将设置c变量为1;否则c的值为0。执行完毕后返回相应的c值。

 119static inline int atomic_dec_and_test(atomic_t *v)
 120{
 121        unsigned char c;
 122
 123        asm volatile(LOCK_PREFIX "decl %0; sete %1"
 124                     : "+m" (v->counter), "=qm" (c)
 125                     : : "memory");
 126        return c != 0;
 127}

 137static inline int atomic_inc_and_test(atomic_t *v)
 138{
 139        unsigned char c;
 140
 141        asm volatile(LOCK_PREFIX "incl %0; sete %1"
 142                     : "+m" (v->counter), "=qm" (c)
 143                     : : "memory");
 144        return c != 0;
 145}

上数两个函数的作用是自减1或自加1后,再测试v值是否为0,如果是0,则返回1;否则返回0。

也许你会认为上述的加/减并测试并不是原子的。我个人的认为是:第一条加/减操作的确是原子的,当此条语句执行完毕后,会产生相应的ZF值。如果此时在执行sete之前产生了中断,那么肯定会保存当前寄存器等值。因此,即便中间产生了中断,返回的也是中断前加/减后的结果。

6.操作并返回

 173static inline int atomic_add_return(int i, atomic_t *v)
 174{
 175        int __i;
 176#ifdef CONFIG_M386
 177        unsigned long flags;
 178        if (unlikely(boot_cpu_data.x86 <= 3))
 179                goto no_xadd;
 180#endif
 181        /* Modern 486+ processor */
 182        __i = i;
 183        asm volatile(LOCK_PREFIX "xaddl %0, %1"
 184                     : "+r" (i), "+m" (v->counter)
 185                     : : "memory");
 186        return i + __i;
 187
 188#ifdef CONFIG_M386
 189no_xadd: /* Legacy 386 processor */
 190        raw_local_irq_save(flags);
 191        __i = atomic_read(v);
 192        atomic_set(v, i + __i);
 193        raw_local_irq_restore(flags);
 194        return i + __i;
 195#endif
 196}

首先if判断语句判断当前的处理器是否为386或386以下,如果是则直接执行no_xadd处开始的代码;否则紧接着判断语句执行。此处的unlikely表示”经常不“。因为现在的处理器大都高于386,所以编译器一般都按照高于386来处理,以达到优化源代码的目的。搞清楚这段代码的逻辑关系,那么接下来的工作就不困难了。

如果CPU高于386,则它将执行xaddl命令,该命令的作用是先交换源和目的操作数,再相加。这里%0,%1分别代表i和v->counter。首先将i值用__i保存;操作数交换后,%0,%1的值依次为v->counter和i;相加并保存到%1;最后%0,%1的结果就是v->counter,i+v->counter,返回的结果i+__i的结果就是i+v->counter。这里涉及到i和v->counter的值时均指初值。

理解了上述过程,再看其他的操作返回函数:

 205static inline int atomic_sub_return(int i, atomic_t *v)
 206{
 207        return atomic_add_return(-i, v);
 208}

可以看到,atomic_sub_return函数是对atomic_add_return函数的封装。而下面两个函数则又是对上述两个函数的封装。

 210#define atomic_inc_return(v)  (atomic_add_return(1, v))
 211#define atomic_dec_return(v)  (atomic_sub_return(1, v))

上述就是对整形原子操作的分析。

grep和正则表达式

16 10 月, 2010

这两天抽空看了一下正则表达式,随之也基本会用grep命令了。关于grep和正则表达式的具体使用,总结成文章如下。

什么是grep?

grep (Global search Regular Expression and Print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。

什么是正则表达式?

正则表达式(Regular Express)就是由一系列特殊字符组成的字符串, 其中每个特殊字符都被称为元字符, 这些元字符并不表示为它们字面上的含义,而会被解释为一些特定的含义。

正则表达式的元字符

1.句点(.)

在正则表达式中,句点(.)可以匹配任何的单个字符。比如:

e.sionte:可以是edsionte,e#sionte,e7sionte等等;

ed…te:可以是edsionte,ed@wdte等

Try:

:%s/  a.d  /@/g

打开任意一个文本文件(本文中的所有命令都是使用这里的文章进行的测试),在vim命令状态下输入上述命令。这条命令的意思是:在所有行(%s代表所有行)中查找符合“  a.d  “的字符串(注意a前面和d后面均有一个空格),并且将符合条件的字符串替换成@;g代表将每一行中符合上述条件的所有字符串都做相应替换,否则只会替换每一行的第一个否和条件的字符串。在上述命令中,属于正则表达式的字符串为两个/之间的部分。

2.^符号

在正则表达式中,^符号表示跟行首进行匹配。比如:

edsionte is a good boy. edsionte likes eating.edsionte likes music.

^edsionte只会跟行首的edsionte进行匹配,而不是和位于行中的其他edsionte进行匹配。

Try:

:%s/^/      /

上述命令在一个文本文件的每行都插入五个空格。对于上述命令,在命令末尾加g和不加g作用是一样的。

3.美元符号($)

与^符号相反,美元符号($)代表跟行尾进行匹配。比如:

edsionte is eating a apple.edsionte will eat that pear.This cup of milk was drunk by edsionte.

edsionte$不会与末尾的edsionte进行匹配,因为这一行最后一个字符是句点(.)。而且它更不会与此行中其他两个edsionte进行匹配。同样的,对于上述命令,在命令末尾加g和不加g作用是一样的。

那么如何匹配行尾的句点?你会想到是这样:.$。不过,这个正则表达式表示的确实匹配行尾的任意的单字符。学过C语言的你,应该知道为什么了吧?我们上面说过正则表达式是由元字符组成,因此句点在正则表达式中不会代表它本身的意思。想要使用句点本身,即得在句点前加\。也就是说,\.$才会真正的匹配行尾的句点。这其实就是转义字符,got it?

Try:

:%s/..$//

删除每行的最后两个字符。

4.[…]结构

在正则表达式中,使用[…]来匹配包含在此结构中的一个字符。比如:

[Tt]he:不管是The还是the都否和匹配要求,这条正则表达式很好的处理了出现在行首的那些单词。但必须注意的是,在字符串“Ttheapple”中,使用上述正则表达式所匹配的是the,而不是Tthe。

[0123456789]:匹配的是任意一个数字,更简单的写法是[0-9]。

[^0-9]:匹配非数字的单字符。请注意这条正则表达式和^[0-9]的差异。

一些特殊的规则是,如果要匹配一个连字符,必须将连字符放在靠近左[的位置。如果方括号内有^必须将^放在所有匹配字符的前方,比如:

[-0-9]:匹配一个数字或者连字符。

[^-0-9]:匹配一个不是连字符或数字的字符。

Try:

:%s/[Hh]arry Potter[0-9]/HARRY POTTER/g

将诸如harry potter3、Harry potter5等这样的字符串都替换成HARRY POTTER。

:%s/HARRY POTTER/*harry potter*/g

将上一步替换后的字符串都换成*harry potter*。可能你会有这样的疑问:*,.,和^等这样的字符不是元字符吗,为什么这里可以直接使用*的本来意思?这是因为元字符只有在搜索串(也就是在正则表达式中)中才会有特殊意义,在替换串中则不会有特殊意义。

5.星号(*)

在正则表达式中,用星号来匹配零到多个紧靠在星号左边的那个字符。比如:

.*:表示零到多个任意字符。因为正则表达式总是寻求最大匹配,因此.*总是匹配一整行。

go*le:可以用来匹配gle,gole,goole,gooooooooooole等。

goo*le:最小的匹配为gole。

t.*t:匹配前后均为t的字符串,而且匹配的是最大长度的字符串。

Try:

:%s/  */ /g

将每一行中的一个到多个空格都全部换成一个空格。

:%s/[a-zA-Z][a-zA-Z]*//g

这个会有什么结果,试试就知道。

6.\{…\}结构

使用\{…\}这样的结构可以匹配精确数目的字符。比如:

t\{1,5\}:匹配1到5个连续的t。

[a-zA-Z]\{4,8\}:匹配4到8个连续的字母。

[0-9]\{8\}:匹配正好8个数字。

[a-z]\{4,\}:匹配至少4个连续的小写字母。

Try:

:%s/[a-z]\{5,\}/@/g

将至少5个连续的小写字母替换成@。

grep的使用

grep命令的基本使用方法是:grep 模式 文件名。文件中符合模式的字符串所在行均会被打印出来。比如:

grep include hello.c:即在hello.c中查找包含字符串include的那些行。

如何将上述我们所熟悉的正规表达式与grep命令结合起来?模式处即为正则表达式,并用单引号包含。比如:

grep ‘[Hh]arry [Pp]otter’ harry.txt:在harry.txt中查找harry potter所在的行,其中H和P是不区分大小写的。

另外,grep命令还有下面一些选项,可以使grep命令使用的更灵活。

-i:模式匹配时忽略大小写的差异;
-l:只输出匹配模式的文件名,而不打印匹配的行;
-q:匹配成功并不将结果在标准输出中显示;
-v:显示不符合模式的行;
-n:在匹配行前显示行号;

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