API SOCKET基础

来源:互联网 发布:拒绝网络暴力作文高中 编辑:程序博客网 时间:2024/06/10 01:50

  原作者及网址不详,感谢作者!

(一) TCP建立连接并通信

2010-08-02 15:46

写这篇日志,并不是要记录令人眼前一亮的算法,只是为了本人健忘的脑袋做一点准备。

要进行网络通信编程,就要用到socket(套接字),下面以TCP为例展示如何利用socket通信。

要进行socket编程,首先要为工程链接导入库文件 ws2_32.lib ,然后添加头文件#include <Winsock2.h>,然后在App类的InitInstance()函数里面加载套接字库,加载套接字库的代码可查看MSDN里WSAStartup函数页面下端example的代码,在加载套接字库的代码里面有一句wVersionRequested = MAKEWORD( 2, 2 );这句是指定采用2.2版本的套接字库,可根据需要修改为其他版本的套接字库。

1.TCP下的socket通信:

   TCP是面向链接的通信,通信的socket双方中必须有一个是服务器端socket,另一端是客户端socket。下面用代码来展示服务器端socket和客户端socket是如何建立链接并通信的。

服务器端连接过程:

1. 使用socket函数创建一个服务器端socket:

   SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);

   如上用socket函数创建了一个名字叫sockSrv的socket,socket函数的第二个参数指定了这个是什么类型的socket,如果是TCP类型的socket则为SOCK_STREAM,如果是UDP类型的socket则为SOCK_DGRAM。

2.创建一个地址结构体,为地址结构体指定地址,然后使用bind函数把socket和地址绑定:


SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);

bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

如上,创建了一个名字叫addrSrv的地址结构体,addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
是指定IP地址为本机的IP地址,addrSrv.sin_port=htons(6000);是指定端口为6000端口,一定要使用1024以上的端口号,1024以下的端口后是系统保留的。在这里有两个函数htonl和htons,这两个函数的作用以后再说。然后使用bind函数把sockSrvt和addrSrv绑定起来。

3.设置socket为监听模式:

listen(sockSrv,5);

使用listen函数把sockSrv设为监听模式,第二个参数是等待连接队列的最大长度。如果设置为SOMAXCONN,那么将这个套接字设置为最大的合理值。这个值不是在一个端口上同时可以进行连接的数目,例如:如果把参数设置为2,当有3个连接请求同时到来时,前两个连接请求被放到等待请求连接队列中,然后程序依次为这些请求服务,而第三个连接请求就被拒绝了。对多个连接请求的处理不是同时进行的,必须完成请求连接队列中一个连接请求的连接,才能开始进行请求连接队列中下一个连接请求的连接。

4.等待客户端的连接请求到来:

SOCKADDR_IN addrClient;

int len=sizeof(SOCKADDR);

SOCKET sockConn=accept (sockSrv,(SOCKADDR*) &addrClient ,&len);

首先创建一个地址结构体addrClient ,当有客户端请求连接sockSrv,accept 函数就会执行,建立连接并把客户端的IP地址和端口信息记录到地址结构体addrClient 里。必须注意到,accept函数的返回值是一个socket,在上面的代码中是SOCKET sockConn,这个名字叫sockConn(名字可由程序员随便取)的socket有什么用呢?其实当成功完成连接后,与客户端socket连接的是sockConn,而不是sockSrv,以后与客户端socket进行数据传送的socket也是sockConn,而不是sockSrv,简单的说,服务器端socket sockSrv只负责接收连接请求和进行连接操作,当连接操作完成后,与客户端socket连接的是accpet函数返回的socket,以后与客户端socket进行数据传输的也是这个accpet函数返回的socket。

再来看TCP客户端是怎样发起连接请求的。

客户端编程也是用到socket,因此链接导入库文件,包含头文件,加载套接字库也必须先在客户端进行。

客户端请求连接过程:

1.使用socket函数创建一个客户端socket:

SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);

2.向服务器端socket发出连接请求:

SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);

connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

上面创建了一个名字叫addrSrv的地址结构体,然后把服务器端socket的IP地址(假设服务器端IP地址是127.0.0.1)和端口来设置这个地址结构体,然后通过connect函数向服务器端socket发出连接请求。在设置addrSrv地址结构体的IP地址时用了一个inet_addr函数,这个函数的作用以后再说。

由此可见,客户端socket并不需要绑定IP地址和端口,为什么服务器端socket需要绑定而客户端socket不用绑定呢?如果服务器端socket不绑定IP和端口,客户端socket又哪知道向哪个IP地址和端口发起连接请求呢?因此服务器端socket是必须绑定IP端口的。而客户端socket不需要用bind函数绑定端口,系统会自动为socket绑定一个随机的端口,服务器只要用accept函数返回的socket与客户端socket交换数据就行了,如果服务器需要查询客户端socket的IP和端口,可以查看accept函数的第二个参数记录的客户端socket地址信息。

连接的操作在这里完成了,然后是进行数据的传输:

发送数据使用send函数:

int send(

SOCKET s,                    //要发送数据的socket,例如设置为服务器端的sockConn,则数据会发送到

                                          与sockConn相连接的客户端socket sockClient上去。相反若设置为sockClient,

                                          则数据发送到与sockClient相连接的服务器端socket sockConn上去。

const char FAR *buf,     //要发送数据buf的地址。

int len,                           //要发送数据buf的长度

int flags                         //一般设置为0即可。

);

接收数据使用recv函数:

int recv(

SOCKET s,               //要接收数据的socket,例如设置为服务器端的sockConn,则接收与sockConn相

                                     连接的客户端socket sockClient发送的数据。相反若设置为sockClient,则接收

                                     与sockClient相连接的服务器端socket sockConn发送的数据。

char FAR *buf,         //要发送数据buf的地址。

int len,                     //要接收数据buf的长度

int flags                    //一般设置为0即可。

);

socket使用完毕后调用closesocket()函数关闭一个socket以回收资源。在程序关闭之前,必须调用WSACleanup函数终止对套接字库的使用,注意必须在App类(应用程序类)的析构函数中调用WSACleanup函数。

接上一篇,建立连接后,服务器端的sockConn与客户端的sockClient就连接起来并且可以互相传输数据了,使用closesocket函数关闭一个socket,socket被关闭,连接也就断开了。下面是断开连接后的各种情况。

情况1:关闭服务器端sockConn--closesocket(sockConn)之后

关闭服务器端sockConn后,对sockConn使用recv函数接收数据,recv函数会马上返回SOCKET_ERROR,对sockConn使用send函数发送数据,send函数也会马上返回SOCKET_ERROR。

之后对客户端sockClient的情况分析就有点复杂了:

1.sockClient使用send函数发送数据。第一次send函数能成功返回发送数据的大小,并不会返回SOCKET_ERROR 。虽然sockClient成功send了数据,但sockConn是无法接收到的。但sockClient使用send函数发送数据成功仅限于第一次send,之后使用send函数返回的都是SOCKET_ERROR 。

2.sockClient使用recv函数接收数据。如果sockClient使用recv函数之前没有使用过send函数,那么recv函数的返回值总是0(感觉好像很奇怪,recv函数返回0,难道还有成功接收到0个字节的数据这种说法?不明白)。直到sockClient调用过send函数之后,recv函数的返回值总是SOCKET_ERROR。

情况2:关闭客户端sockClient--closesocket(sockClient)之后

这时结果就和情况1相反:

关闭客户端sockClient后,对sockClient使用recv函数接收数据,recv函数会马上返回SOCKET_ERROR,对sockClient使用send函数发送数据,send函数也会马上返回SOCKET_ERROR。

之后对服务器端sockConn的情况分析也一样:

1.sockConn使用send函数发送数据。第一次send函数能成功返回发送数据的大小,并不会返回SOCKET_ERROR 。虽然sockConn成功send了数据,但sockClient是无法接收到的。但sockConn使用send函数发送数据成功仅限于第一次send,之后使用send函数返回的都是SOCKET_ERROR 。

2.sockConn使用recv函数接收数据。如果sockConn使用recv函数之前没有使用过send函数,那么recv函数总是返回0。直到sockConn调用过send函数之后,recv函数的返回值总是SOCKET_ERROR。

知道上面两个断开连接的情况后就要考虑客户端和服务器端如何协调关闭连接。TCP连接的双方Asocket和Bsocket,Asocket想要关闭连接,Asocket的计算机除了closesocket(Asocket)之外,还要另外通知Bsocket的计算机连接已经断开了,Bsocket的计算机收到通知后closesocket(Bsocket)来回收资源,避免Bsocket还在哪里傻傻的接收/发送数据,如果不想这样明显通知Bsocket的计算机连接已断开,Bsocket也可以尝试自己判断,如果Bsocket多次调用send函数总是返回SOCKET_ERROR或者Bsocket多次调用recv函数总是返回0或SOCKET_ERROR,那就要意识到连接很可能已经断开了。

如果数据的流向是单向的,例如说数据只从Asocket流向Bsocket(类似文件传输就是这样),那么Asocket只会调用send函数,而Bsocket只会调用recv函数,这时候如果其中一方要停止数据的传输,就会有两种情况出现:

1.如果Asocket的计算机不想发送数据而closesocket(Asocket),由于Bsocket从来不调用send函数,因此Bsocket的recv函数总是返回0,那么Bsocket的计算机就要意识到Asocket很可能已经关闭了,让Bsocket的计算机closesocket(Bsocket)。

2.如果Bsocket的计算机不想接收数据而closesocket(Bsocket),这时Asocket继续调用send函数发送数据,第一次send还是成功的,但从第二次send开始就总会返回SOCKET_ERROR,但是Asocket的计算机无法判断send函数返回SOCKET_ERROR是由Bsocket关闭造成的还是Asocket关闭造成的(因为Asocket关闭后Asocket调用send函数也是返回SOCKET_ERROR),因此Asocket的计算机无法判断Asocket关闭了没有,简单的解决方法是如果Bsocket的计算机不想接收数据,先不要关闭Bsocket,而是发通知给Asocket的计算机告诉它我不想收数据了,Asocket的计算机收到通知后关闭Asockt,这样情形就回到上面情况1去了,而且也知道Asocket调用send函数返回SOCKET_ERROR肯定是由于Asockt关闭造成的而不是由Bsocket关闭造成的。


(二) UDP通信

2010-08-02 15:56

UDP是不面向连接的通信,通信过程如下:

首先链接导入库文件,包含头文件,加载套接字库。

服务端:

SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);

SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);

bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

客户端:

SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);

发送和接收数据:

int sendto(

SOCKET s,                        //发送数据的socket

const char FAR *buf,          //要发送数据的buf地址  

int len,                             //要发送数据的buf长度

int flags,                           //一般设置为0

const struct sockaddr FAR *to,       //目的地址结构体指针

int tolen                                         //地址结构体长度

);

int recvfrom(

SOCKET s,                    //接收数据socket

char FAR* buf,              //保存接收数据的buf地址

int len,                          //保存接收数据的buf长度

int flags,                       //一般设置为0

struct sockaddr FAR *from,    //地址结构体指针,用来保存数据发送端地址信息

int FAR *fromlen                    //地址结构体长度

);

服务端socket必须绑定IP端口,这样客户端socket才能知道服务端socket的IP端口从而向服务端socket发送数据。

char IPaddr[16]="127.0.0.1";

SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr(IPaddr);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);

int len=sizeof(SOCKADDR);

sendto(sockClient,"data to send",strlen("data to send")+1,0,(SOCKADDR*)&addrSrv,len);

客户端socket不需要绑定IP端口,而让系统随机绑定一个端口。因为服务端socket永远不会先发数据给客户端socket,而在服务端socket recvfrom客户端发来的数据时,recvfrom函数里就有参数能保存发数据来的客户端socket的地址信息。

char pathBuf[100];

SOCKADDR_IN addrClient;
      int len=sizeof(SOCKADDR);
      recvfrom(sockSrv,pathBuf,100,0,(SOCKADDR*)&addrClient,&len);

socket使用完毕后调用closesocket()函数关闭一个socket以回收资源。在程序关闭之前调用WSACleanup函数终止对套接字库的使用,注意必须在App类(应用程序类)的析构函数中调用WSACleanup函数。

(三)网络字节序与主机字节序的转换

2010-08-02 16:02

在对IP地址结构体SOCKADDR_IN赋值的时候,经常会用到下列的函数htonl,htons,inet_addr,与之相对应的函数是ntohl,ntohs,inet_ntoa。查看这些函数的解析,会发现这些函数其实是与主机字节序和网络字节序之间转换有关。就是什么网络字节序,什么是主机字节序呢?下面我写出他们之间的转换:

用IP地址127.0.0.1为例:

第一步   127     .         0         .         0         .        1                 把IP地址每一部分转换为8位的二进制数。

第二步 01111111     00000000     00000000     00000001      =   2130706433  (主机字节序)

然后把上面的四部分二进制数从右往左按部分重新排列,那就变为:

第三步 00000001     00000000     00000000    01111111        =   16777343        (网络字节序)

然后解析上面提到的函数作用就简单多了,看以下代码:

SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(2130706433);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);

先是定义了一个IP地址结构体addrSrv,然后初始化它的IP时addrSrv.sin_addr.S_un.S_addr必须是赋值IP地址的网络字节序,htonl函数的作用是把一个主机字节序转换为网络字节序,也就是上面转换过程中第二步转换为第三步的作用,127.0.0.1的主机字节序是2130706433,把主机字节序2130706433转换为网络字节序就是htonl(2130706433)=16777343,所以如果你知道网络字节序是16777343的话,addrSrv.sin_addr.S_un.S_addr=htonl(2130706433);与addrSrv.sin_addr.S_un.S_addr=16777343;是完全一样的。

addrSrv.sin_addr.S_un.S_addr=htonl(2130706433);这句还可以写为:

addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1"); 结果是完全一样的。

可见inet_addr函数的转换作用就是上面的第一步到第三步的转换。

下面再看端口的主机字节序与网络字节序的转换。以6000端口为例。

第一步     00010111         01110000            =           6000 (主机字节序)

端口号其实就已经是主机字节序了,首先要把端口号写为16位的二进制数,分前8位和后8位。

第二步      01110000          00010111          =            28695 (网络字节序)

然后把主机字节序的前八位与后八位调换位置组成新的16位二进制数,这新的16位二进制数就是网络字节序的二进制表示了。

因此,如果你知道6000端口的网络字节序是28695的话。 addrSrv.sin_port=htons(6000);可以直接写为 addrSrv.sin_port=28695;结果是一样的,htons的作用就是把端口号主机字节序转换为网络字节序。

与htonl,htons,inet_addr,与之相对应的函数是ntohl,ntohs,inet_ntoa,不难看出,ntohl,ntohs,inet_ntoa,这三个函数其实就是执行与他们相对应函数的相反转换,在这里就不详细解析了。

(四) TCP SOCKET recv的内存情况

2010-08-02 16:22

使用TCP SOCKET 成功连接以后,使用recv函数接收数据,内存的情况是怎样的呢?不做测试真的不知道内幕原来如此:

一个已经取得连接的TCP socket sockConn 两次接收数据的代码如下:

char buf1[16];
recv(sockConn,buf1,16,0);
MessageBox(buf1);


char buf2[16];
recv(sockConn,buf2,16,0);
MessageBox(buf2);

与sockConn连接的另外一个TCP socket sockClient 两次发送数据的代码如下:

send(sockClient,"sendmessage1",strlen("sendmessage1")+1,0);    //包含字符串结束符,共发送13字节。

send(sockClient,"sendmessage2",strlen("sendmessage2"),0);        //不包含字符串结束符,共发送12字节。

sockConn的第一个messageBox(buf1);结果是sendmessage1,而第二个MessageBox(buf2);结果是sendmessage2烫烫sendmessage1。这时我就估计到内存的情况应该如下图所示:

API SOCKET基础(5)TCP SOCKET recv的内存情况 - 钢琴上的猫 - 鋼琴↑dě貓

也就是说buf2的内存空间刚好排在前一个接收数据容器buf1的前面,我估计这就是所谓的TCP数据流的意义吧,接收到的数据会连在一起,流式的。当使用MessageBox(buf2)输出buf2的时候,就一直输出,直到遇到后面buf1第13个字节的结束符/0为止,所以就看到上面的输出结果了。

看回sockConn第二次接收数据的语句,recv(sockConn,buf2,16,0);参数16是控制接收数据的字节数,其实这个参数并不一定就要是buf的大小,它可以取任何大于0的整数值,如果我把sockClient第二次发送的语句改为:

send(sockClient,"sendmessage2ABCDEF",strlen("sendmessage2ABCDEF"),0);        //不包含字符串结束符,共发送18字节。

哪么MessageBox(buf2);的结果就是sendmessage2ABCDsendmessage1,因为recv(sockConn,buf2,16,0);的参数16规定了最多只接收16个字节的数据,所以把数据“EF”丢弃掉。如果把recv(sockConn,buf2,16,0);改为recv(sockConn,buf2,18,0);哪么MessageBox(buf2);的结果就是sendmessage2ABCDEFndmessage1,因为buf2只有16个字节的大小,接收到的第17、18个字节“EF”就跟着buf2的末端继续写入内存,于是就把buf1的“se”改为“EF”了。如果改为recv(sockConn,buf2,20,0);哪么MessageBox(buf2);的结果还是sendmessage2ABCDEFndmessage1,buf1的第三第四个字节空间里面的数据“nd”不会被改写,因为接收到的数据还用不到这里的空间。

简单的说,recv函数的第三个参数就像是一个闸门,接收到的数据写入内存时禁止越过这个闸门,如果闸门前的内存不够写入全部接收到的数据,那就把后面无法写入的哪部分数据丢弃。如果闸门前的内存让全部接收到的数据写入后还有多余的,那多余的内存会保留原本的值,并不会被改写。

甚至还可以改为recv(sockConn,buf2,40,0);buf1加上buf2总共才32个字节,而我把recv的第三个参数改为40,如果sockClient发送了50个字节,前40个字节的内容能成功写入内存。要注意的是,在buf1后面新开辟出来的8个字节内存空间,是在buf2+buf1不够内存写入数据的时候才根据需要开辟的,例如如果sockClient只发送了30个字节,那么buf1后面是不会开辟任何内存空间的。而如果sockClient只发送了37个字节,那么buf1后面只开辟5个字节的内存空间而不是开辟8个字节的内存空间的。

(五) 异步套接字

2010-08-02 16:24

前面介绍的socket操作中,有一个非常大的缺陷,当一个socket运行accept,recv,recvfrom等网络操作的时候,程序就要等待函数成功返回一个值后才能继续往下执行。譬如说,一个tcp socket用recv函数接收数据,那么程序的执行就停留在recv语句上一直等待数据的到来,这就是所谓的同步套接字。有米办法让程序不会卡在accept,recv,recvfrom这样的网络操作上呢?简单的方法是把这样的操作放到一个新开的线程上,但这不是一个好方法,会令代码变得复杂和线程增多难以管理。为了解决这个问题异步套接字就产生了。异步套接字的使用流程如下:

1.为工程链接导入库文件 ws2_32.lib ,然后添加头文件 #include <Winsock2.h> ,然后在App类的InitInstance()函数里面加载套接字库,注意必须加载2.0或以上版本的套接字库。

2.下面以对话框工程TCP socket为例:

为对话框类添加一个socket类型的成员变量: SOCKET socketrecv;

然后在OnInitDialog()函数中添加以下代码:

   socketrecv=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,0);  

//WSASocket函数是用于创建套接字的扩展函数,与socket函数相比多了后面三个参数,从而使他比socket函数拥有更强大功能,如果不需要用到后三个参数新增的功能,可以直接使用socket函数创建套接字,作用一样。
   if(INVALID_SOCKET==socketrecv)
    {
        closesocket(socketrecv);
        MessageBox("套接字创建失败!");
        return FALSE;
    }

    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY); //本机任意IP地址
    addrSrv.sin_family=AF_INET;
    addrSrv.sin_port=htons(6500); //6500端口
   
    int ret1;
    ret1=bind(socketrecv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//绑定套接字
    if(SOCKET_ERROR==ret1)
    {closesocket(socketrecv);
     MessageBox("套接字绑定失败!");
     return FALSE;
    }

    int ret2;
    ret2=listen(socketrecv,5);                    //开始监听
    if(SOCKET_ERROR==ret2)
    {
        closesocket(socketrecv);
        MessageBox("套接字监听失败!");
        return FALSE;
    }

    if(SOCKET_ERROR==WSAAsyncSelect(socketrecv,m_hWnd,UM_SOCK,FD_ACCEPT))
   {MessageBox("注册网络ACCEPT事件失败!");
   return FALSE;
   }

//WSAAsyncSelect函数用于请求一个网络事件通知,最后的参数是事件类型,本例注册了一个FD_ACCEPT事件,这样,当有socket 请求连接(connect)socketrecv的时候,就会触发FD_ACCEPT事件,系统就会发送UM_SOCK消息(UM_SOCK是用户自定义消息),后面必须在UM_SOCK消息的消息响应函数里进行socketrecv的accept操作。查看MSDN可查询到其他可以注册的网络事件类型。

3.UM_SOCK消息处理:

假定UM_SOCK消息的消息响应函数是void OnSock(WPARAM,LPARAM),注意这个消息响应函数的参数必须一定是(WPARAM,LPARAM)。然后void OnSock(WPARAM,LPARAM)的具体实现如下:

void CXXXDlg::OnSock(WPARAM wParam,LPARAM lParam)  

{
    switch(LOWORD(lParam))        //参数lParam包含了网络事件的类型
    { case FD_ACCEPT:

      if((SOCKET)wParam==socketrecv)    //参数wParam包含了消息是针对哪个socket发出的

        {

        SOCKADDR_IN addrconn;
          int len=sizeof(SOCKADDR);  
         socknec=accept(socketrecv,(SOCKADDR*)&addrconn,&len);  

          //开始接收连接,socknec是对话框类的一个socket类型成员变量,连接成功后负责与客户端socket收送数据
           if(INVALID_SOCKET ==socknec)       
           { closesocket(socknec);
            MessageBox("接收连接失败!");
              break;   //连接失败,跳出switch
           }

           break;    //连接成功,跳出switch

         }

    }

}

socket使用完毕后调用closesocket()函数关闭一个socket以回收资源。程序关闭前也必须使用WSACleanup函数终止对套接字库使用,注意必须在App类(应用程序类)的析构函数中调用WSACleanup函数。

以上的例子只是显示了对TCP socket的accept操作的异步处理,可以通过MSDN查询WSAAsyncSelect函数查看其他能进行异步处理的网络事件。

(六) CAsyncSocket 与 CSocket

2010-08-02 16:57

要使用MFC的socket类,准备工作和使用API的socket类不同,在加载套接字库时,必须在app类的InitInstance()函数里调用AfxSocketInit函数,AfxSocketInit函数内部将调用WSAStartup函数加载套接字库,并且加载的是1.1版本的套接字库,使用AfxSocketInit函数不需要为工程链接ws2_32.lib库文件。如果AfxSocketInit函数调用成功,函数返回非0值,失败则返回0值。使用AfxSocketInit函数加载套接字库的另一个好处是在程序终止前会自动调用WSACleanup函数终止套接字库使用。因为程序调用了AfxSocketInit函数,所以还必须包含相应的头文件,在stdafx.h中添加#include "afxsock.h"。

MFC提供了两个封装socket的类,分别是CAsyncSocket 和 CSocket ,当中CSocket 是CAsyncSocket 的子类,这两个socket类有一个很大的区别,就是CAsyncSocket 是异步套接字而 CSocket是同步套接字,使用MFC的socket类有两个很明显的好处是:

1. 可以从这两个socket类中继承出自己的socket类,从而根据需要增加自己socket类的功能,而且要使用这两

    个socket类,就一定需要从这两个类中继承出自己的类来使用,因为需要修改虚函数才能实现某些基本功能。

2. 可以方便的处理同步与异步问题。

对于第一个好处是显然易懂的,而对于第二个好处,就必须了解这些类的阻塞与非阻塞情况了。这里可以分为两大类:

第一类:从CAsyncSocket类继承下来的子类(不包括CSocket类与从CSocket类继承下来的子类),他们的阻

              塞/非阻塞情况一样。

第二类:从CSocket类继承下来的子类,他们的阻塞/非阻塞情况一样。

具体情况:

继承了CAsyncSocket类的派生类:

由于CSocket类也是由CAsyncSocket类派生而来的,所以这里说的继承了CAsyncSocket类的派生类是自己建立的类,而不包括CSocket类和CSocket的派生类。例如自己建立一个CMySocket类,如下:

class CMySocket : public CAsyncSocket

那么这个CMySocket类的阻塞与非阻塞情况如下:

1.

Connect的时候,Connect()函数会马上返回WSAEWOULDBLOCK的错误码,然后程序继续往下执行,connect的具体操作就放在以后执行,等到connect的具体操作完了以后,无论有没有connect成功,最后都会调用OnConnect()虚函数。 无论连接成功与否还会调用OnSend()虚函数。

2.

当有一个socket要连接CMySocket类的时候,CMySocket类的OnAccept()虚函数会被自动执行,因此要接受连接请求的话,必须在OnAccept()虚函数里进行Accpet()函数操作。连接成功还会调用OnSend()虚函数,连接不成功不会调用OnSend()虚函数。

3.

当CMySocket类使用Send()函数发送数据的时候,OnSend()虚函数不会被调用。

4.

当有一个socket向CMySocket类发送数据的时候,CMySocket类的OnReceive()虚函数会被自动执行,因此要接收数据的话,必须在OnReceive()虚函数里进行Receive()函数操作。

继承了CSocket类的派生类:

例如自己建立一个CYourSocket类,如下:

class CYourSocket : public CSocket

1.

Connect()的时候连接操作会马上执行,直到连接操作执行完毕后,Connect()函数才会返回,连接成功的话函数返回非0值,连接不成功就返回0值,函数返回后程序才继续往下执行。而OnConnect()虚函数不会被调用。无论连接成功与否还会调用OnSend()虚函数。

2.

当有一个socket要连接CYourSocket类的时候,CYourSocket类的OnAccept()虚函数会被自动执行,因此要接受连接请求的话,必须在OnAccept()虚函数里进行Accpet()函数操作。连接成功还会调用OnSend()虚函数,连接不成功不会调用OnSend()虚函数。 情况就和上面的CMySocket类的一样。

3.

当CYourSocket类使用Send()函数发送数据的时候,OnSend()虚函数不会被调用。情况就和上面的CMySocket类的一样。

4.

当有一个socket向CYourSocket类发送数据的时候,CYourSocket类的OnReceive()虚函数会被自动执行,因此要接收数据的话,必须在OnReceive()虚函数里进行Receive()函数操作。情况就和上面的CMySocket类的一样。



原创粉丝点击