Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1520552
  • 博文数量: 230
  • 博客积分: 474
  • 博客等级: 下士
  • 技术积分: 1955
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-19 18:40
文章分类

全部博文(230)

文章存档

2020年(3)

2019年(3)

2018年(12)

2017年(13)

2016年(11)

2015年(55)

2014年(74)

2013年(39)

2012年(2)

2011年(18)

我的朋友

分类:

2011-08-04 10:30:30

简单点说:

阻塞就是干不完不准回来,   
非组赛就是你先干,我现看看有其他事没有,完了告诉我一声

我们拿最常用的send和recv两个函数来说吧...
比如你调用send函数发送一定的Byte,在系统内部send做的工作其实只是 把数据传输(Copy)到TCP/IP协议栈的输出缓冲区,它执行成功并不代表数据已经成功的发送出去了,如果TCP/IP协议栈没有足够的可用缓冲区来 保存你Copy过来的数据的话...这时候就体现出阻塞和非阻塞的不同之处了:对于阻塞模式的socket send函数将不返回直到系统缓冲区有足够的空间把你要发送的数据Copy过去以后才返回,而对于非阻塞的socket来说send会立即返回 WSAEWOULDDBLOCK告诉调用者说:"发送操作被阻塞了!!!你想办法处理吧..."
对于recv函数,同样道理,该函数的内部工作 机制其实是在等待TCP/IP协议栈的接收缓冲区通知它说:嗨,你的数据来了.对于阻塞模式的socket 来说如果TCP/IP协议栈的接收缓冲区没有通知一个结果给它它就一直不返回:耗费着系统资源....对于非阻塞模式的socket该函数会马上返回,然 后告诉你:WSAEWOULDDBLOCK---"现在没有数据,回头在来看看"

在进行网络编程时,我们常常见到同步、异步、阻塞和非阻塞四种调用方式。这些方式彼此概念并不好理解。下面是我对这些术语的理解。

同步
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。按照这个定义,其实绝大多数函数都是同步调用(例如 sin, isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。最常见的例子就是 SendMessage(JAMBOL注:PostMessage是异步机制)。该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。 当对方处理完毕以后,该函数才把消息处理函数所返回的LRESULT值返回给调用者。

异步
异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来 通知调用者。以CAsycSocket类为例(注意,CSocket从CAsyncSocket派生,但是起功能已经由异步转化为同步),当一个客户端通 过调用Connect函数发出一个连接请求后,调用者线程立刻可以朝下运行。当连接真正建立起来以后,socket底层会发送一个消息通知该对象。
这 里提到执行部件和调用者通过三种途径返回结果:状态、通知和回调。可以使用哪一种依赖于执行部件的实现,除非执行部件提供多种选择,否则不受调用者控制。 如果执行部件用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是 一种很严重的错误)。如果是使用通知的方式,效率则很高,因为执行部件几乎不需要做额外的操作。至于回调函数,其实和通知没太多区别。

阻塞
阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。
有人也许会把阻塞调用和同步调用等同起 来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。例如,我们在CSocket中调用 Receive函数,如果缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息。如果主窗口和调用函 数在同一个线程中,除非你在特殊的界面操作函数中调用,其实主界面还是应该可以刷新。
socket接收数据的另外一个函数recv则是一个阻塞调用的例子。当socket工作在阻塞模式的时候,如果没有数据的情况下调用该函数,则当前线程就会被挂起,直到有数据为止。

非阻塞
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

对象的阻塞模式和阻塞函数调用
对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但是并不是一一对应的。阻塞对象上可以有非阻塞的 调用方式,我们可以通过一定的API去轮询状态,在适当的时候调用阻塞函数,就可以避免阻塞。而对于非阻塞对象,调用特殊的函数也可以进入阻塞调用。函数 select就是这样的一个例子。


Socket编程实例

   代码实例中的服务器通过socket连接向客户端发送字符串"Hello, you are connected!"。只要在服务器上运行该服务器软件,在客户端运行客户软件,客户端就会收到该字符串。

   该服务器软件代码如下:

#include
#include
#include
#include
#include
#include
#include
#include
#define SERVPORT 3333    /*服务器监听端口号 */
#define BACKLOG 10    /* 最大同时连接请求数 */

main()
{
    int sock_fd,client_fd;    /*sock_fd:监听socket;client_fd:数据传输socket */
    int sin_size;
    struct sockaddr_in my_addr;    /* 本机地址信息 */
    struct sockaddr_in remote_addr;    /* 客户端地址信息 */
    if((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket创建出错!");
        exit(1);
    }
    my_addr.sin_family=AF_INET;
    my_addr.sin_port=htons(SERVPORT);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(my_addr.sin_zero),8);
    if(bind(sock_fd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
        perror("bind出错!");
        exit(1);
    }
    if(listen(sock_fd, BACKLOG) == -1) {
        perror("listen出错!");
        exit(1);
    }
    while(1) {
        sin_size = sizeof(struct sockaddr_in);
        if((client_fd = accept(sock_fd, (struct sockaddr *)&remote_addr, &sin_size)) == -1) {
            perror("accept出错");
            continue;
        }
        printf("received a connection from %s\n", inet_ntoa(remote_addr.sin_addr));
        if(!fork()) {    /* 子进程代码段 */
            if(send(client_fd, "Hello, you are connected!\n", 26, 0) == -1) {
                perror("send出错!");
            }
            close(client_fd);
            exit(0);
        }
        close(client_fd);
    }
}

  服务器的工作流程是这样的:

  首先调用socket函数创建一个Socket,然后调用bind函数将其与本机地址以及一个本地端口号绑定,然后调用listen在相应的 socket上监听,当accpet接收到一个连接服务请求时,将生成一个新的socket。服务器显示该客户机的IP地址,并通过新的socket向客 户端发送字符串"Hello,you are connected!"。最后关闭该socket。

  代码实例中的fork()函数生成一个子进程来处理数据传输部分,fork()语句对于子进程返回的值为0。所以包含fork函数的if语句是子进程代码部分,它与if语句后面的父进程代码部分是并发执行的。

  客户端程序代码如下:

#include
#include
#include
#include
#include
#include
#include
#include
#define SERVPORT 3333
#define MAXDATASIZE 100    /*每次最大数据传输量 */

main(int argc, char *argv[])
{
    int sock_fd, recvbytes;
    char buf[MAXDATASIZE];
    struct hostent *host;
    struct sockaddr_in serv_addr;
    if(argc< 2) {
        fprintf(stderr,"Please enter the server's hostname!\n");
        exit(1);
    }
    if((host=gethostbyname(argv[1])) == NULL) {
        herror("gethostbyname出错!");
        exit(1);
    }
    if((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket创建出错!");
        exit(1);
    }
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_port=htons(SERVPORT);
    serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
    bzero(&(serv_addr.sin_zero),8);
    if(connect(sock_fd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1) {
        perror("connect出错!");
        exit(1);
    }
    if((recvbytes=recv(sock_fd, buf, MAXDATASIZE, 0)) == -1) {
        perror("recv出错!");
        exit(1);
    }
    buf[recvbytes] = '\0';
    printf("Received: %s",buf);
    close(sock_fd);
}

  客户端程序首先通过服务器域名获得服务器的IP地址,然后创建一个socket,调用connect函数与服务器建立连接,连接成功之后接收从服务器发送过来的数据,最后关闭socket。

  函数gethostbyname()是完成域名转换的。由于IP地址难以记忆和读写,所以为了方便,人们常常用域名来表示主机,这就需要进行域名和IP地址的转换。函数原型为:

   struct hostent *gethostbyname(const char *name);

   函数返回为hosten的结构类型,它的定义如下:

   struct hostent {
     char *h_name;          /* 主机的官方域名 */
char **h_aliases;          /* 一个以NULL结尾的主机别名数组 */
int h_addrtype;         /* 返回的地址类型,在Internet环境下为AF-INET */
int h_length;           /* 地址的字节长度 */
char **h_addr_list;           /* 一个以0结尾的数组,包含该主机的所有地址*/
};
#define h_addr h_addr_list[0]       /*在h-addr-list中的第一个地址*/

   当 gethostname()调用成功时,返回指向struct hostent的指针,当调用失败时返回-1。当调用gethostbyname时,你不能使用perror()函数来输出错误信息,而应该使用herror()函数来输出。

  无连接的客户/服务器程序,在原理上和连接的客户/服务器是一样的,两者的区别在于无连接的客户/服务器中的客户一般不需要建立连接,而且在发送接收数据时,需要指定远端机的地址。

阻塞和非阻塞

  阻塞函数在完成其指定的任务以前不允许程序调用另一个函数。例如,程序执行一个读数据的函数调用时,在此函数完成读操作以前将不会执行下一程序 语句。当服务器运行到accept语句时,而没有客户连接服务请求到来,服务器就会停止在accept语句上等待连接服务请求的到来。这种情况称为阻塞 (blocking)。而非阻塞操作则可以立即完成。比如,如果你希望服务器仅仅注意检查是否有客户在等待连接,有就接受连接,否则就继续做其他事情,则 可以通过将Socket设置为非阻塞方式来实现。非阻塞socket在没有客户在等待时就使accept调用立即返回。

   #include
#include
……
sockfd = socket(AF_INET,SOCK_STREAM,0);
fcntl(sockfd,F_SETFL,O_NONBLOCK);
……

  通过设置Socket为非阻塞方式,可以实现"轮询"若干Socket。当企图从一个没有数据等待处理的非阻塞Socket读入数据时,函数将 立即返回,返回值为-1,并置errno值为EWOULDBLOCK。但是这种"轮询"会使CPU处于忙等待方式,从而降低性能,浪费系统资源。而调用 select()会有效地解决这个问题,它允许你把进程本身挂起来,而同时使系统内核监听所要求的一组文件描述符的任何活动,只要确认在任何被监控的文件 描述符上出现活动,select()调用将返回指示该文件描述符已准备好的信息,从而实现了为进程选出随机的变化,而不必由进程本身对输入进行测试而浪费 CPU开销。select函数原型为:

   int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

   其中readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集合。如果你希望确定是否可以从 标准输入和某个socket描述符读取数据,你只需要将标准输入的文件描述符0和相应的sockdtfd加入到readfds集合中;numfds的值是 需要检查的号码最高的文件描述符加1,这个例子中numfds的值应为sockfd+1;当select返回时,readfds将被修改,指示某个文件描 述符已经准备被读取,你可以通过FD_ISSSET()来测试。为了实现fd_set中对应的文件描述符的设置、复位和测试,它提供了一组宏:

   FD_ZERO(fd_set *set)----清除一个文件描述符集;
FD_SET(int fd,fd_set *set)----将一个文件描述符加入文件描述符集中;
FD_CLR(int fd,fd_set *set)----将一个文件描述符从文件描述符集中清除;
FD_ISSET(int fd,fd_set *set)----试判断是否文件描述符被置位。

   Timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout长时间后没有文件描述符准备好即返回。struct timeval数据结构为:

  struct timeval {
int tv_sec; /* seconds */
int tv_usec; /* microseconds */
};

POP3客户端实例

  下面的代码实例基于POP3的客户协议,与邮件服务器连接并取回指定用户帐号的邮件。与邮件服务器交互的命令存储在字符串数组POPMessage中,程序通过一个do-while循环依次发送这些命令。

#include
#include
#include
#include
#include
#include
#include
#include
#define POP3SERVPORT 110
#define MAXDATASIZE 4096

main(int argc, char *argv[]){
int sock_fd;
struct hostent *host;
struct sockaddr_in serv_addr;
char *POPMessage[]={
"USER userid\r\n",
"PASS password\r\n",
"STAT\r\n",
"LIST\r\n",
"RETR 1\r\n",
"DELE 1\r\n",
"QUIT\r\n",
NULL
};
int iLength;
int iMsg=0;
int iEnd=0;
char buf[MAXDATASIZE];

if((host=gethostbyname("your.server"))==NULL) {
perror("gethostbyname error");
exit(1);
}
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket error");
exit(1);
}
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(POP3SERVPORT);
serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
bzero(&(serv_addr.sin_zero),8);
if (connect(sock_fd, (struct sockaddr *)&serv_addr,sizeof(struct sockaddr))==-1){
perror("connect error");
exit(1);
}

do {
send(sock_fd,POPMessage[iMsg],strlen(POPMessage[iMsg]),0);
printf("have sent: %s",POPMessage[iMsg]);

iLength=recv(sock_fd,buf+iEnd,sizeof(buf)-iEnd,0);
iEnd+=iLength;
buf[iEnd]='\0';
printf("received: %s,%d\n",buf,iMsg);

iMsg++;
} while (POPMessage[iMsg]);

close(sockfd);
}

阅读(2137) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~