您的位置:首页 > 理论基础 > 计算机网络

socket网络编程(2):socket操作相关函数

2014-07-19 10:48 218 查看
使用socket函数应先包含winsock2.h头文件和链接Ws2_32.lib文件:

#include <winsock2.h>

#pragma comment(lib, "Ws2_32.lib")


1、WSAStartup()和WSACleanup()

WSAStartup()用来加载套接字库,函数执行成功返回0,否则返回错误码。

int WSAStartup(
  _In_   WORD wVersionRequested,
  _Out_  LPWSADATA lpWSAData
);

wVersionRequested:要加载的Winsock库版本。其低位字节为主版本号,高位字节为副版本号,最新版本号为2.2,可以用MAKEWORD(x, y)宏来构成这个WORD(unsigned short)类型的值,其中x为低位字节,y为高位字节。

lpWSAData:返回值参数,指向WSAData结构的指针。




可以通过LOBYTE(WORD wValue)和HIBYTE(WORD wValue)分别获得WORD类型值的低位字节值和高位字节值。

WSACleanup()用来结束对Winsock库的使用,释放资源。函数原型:

int WSACleanup(void);


eg:

WORD wVersionRequested = MAKEWORD(1, 1);//指定要加载的Winsock库主版本号和副版本号
	WSADATA wsaData;
	int err;
	
	err = WSAStartup(wVersionRequested, &wsaData);
	if(err != 0)
		return 1;

	if(LOBYTE(wsaData.wVersion)!=1 || HIBYTE(wsaData.wVersion)!=1)
	{
			WSACleanup();
			return 1;
	}
        
        ......
        
        WSACleanup();

        return 0;


2、socket()/closesocket()

socket()用来创建套接字,函数调用成功会返回创建的套接字,失败返回INVALID_SOCKET;

SOCKET WSAAPI socket(
  _In_  int af,
  _In_  int type,
  _In_  int protocol
);

af:地址族。对于TCP/IP协议套接字来说应为AF_INET(也可写成PF_INET)。

type:socket类型。对于TCP协议,应为SOCK_STREAM,表示面向流的传输协议;对于UDP协议,应为SOCK_DGRAM,表示面向数据报的传输协议。

protocol:通常指定为0,让系统自动设置协议。

SOCKET ListenSocket = INVALID_SOCKET;
    ListenSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (ListenSocket == INVALID_SOCKET) {
        wprintf(L"socket function failed with error: %u\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }


closesocket()用来关闭套接字,从而关闭连接。closesocket()的一些行为受setsockopt()影响。

3、bind()

bind()用来将套接字绑定到本机的某个地址和端口上,函数执行成功返回0,否则返回SOCKET_ERROR。

int bind(
  _In_  SOCKET s,
  _In_  const struct sockaddr *name,
  _In_  int namelen
);

s:要绑定的套接字。

name:sockaddr地址结构指针,其包含了要绑定过的本机地址信息,在TCP/IP socket编程中,可以用sockaddr_in地址结构来替代此结构,以方便我们填写地址信息。

struct sockaddr_in {
             short   sin_family;       //地址族,对于TCP/IP来说应为AF_INET
             u_short sin_port;         //端口号
             struct  in_addr sin_addr; //IP地址
             char    sin_zero[8];      //为与sockaddr结构长度相同增加的填充值,无意义
      };

其中,sin_addr成员实际上是一个联合,故直接向成员s_addr赋值即可。

struct in_addr {

union {

struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;

struct { u_short s_w1,s_w2; } S_un_w;

u_long S_addr;

} S_un;

#define s_addr S_un.S_addr

.......

}

inet_addr()可以将一个点分十进制格式的IPV4地址转换为一个32位网络字节序IP地址。

inet_ntoa()可以将一个in_addr结构体类型转换为点分十进制格式IP地址。

htons()可以将端口号转换为网络字节序。

ntohs()可以将网络字节序的端口号转化为主机字节序。

namelen:为地址结构长度。

eg:

sockaddr_in service;
    service.sin_family = AF_INET;
    service.sin_addr.s_addr = inet_addr("127.0.0.1");
    service.sin_port = htons(27015);

    //----------------------
    // Bind the socket.
    iResult = bind(ListenSocket, (SOCKADDR *) &service, sizeof (service));
    if (iResult == SOCKET_ERROR) {
        wprintf(L"bind failed with error %u\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }


4、setsockopt()/getsockopt()

setsockopt()/getsockopt()用来设置/获得socket套接字的选项值,函数原型:

int setsockopt(
  _In_  SOCKET s,
  _In_  int level,
  _In_  int optname,
  _In_  const char *optval,
  _In_  int optlen
);

s:要设置的套接口描述字。
level:选项所定义的层次协议,SOL_SOCKET: socket层

IPPROTO_TCP: TCP层

IPPROTO_IP: IP层

.......

optname:要设置的选项名。

optval:要设置的选项值。

optlen:optval缓冲区的长度。

有两种类型的套接口选项:一种是布尔型选项,允许或禁止一种特性,允许一个布尔型选项,则将optval指向非零整形数,禁止一个选项将optval指向一个等于零的整形数。另一种是整形或结构选项,optval指向包含所需选项的整形数或结构,而optlen则为整形数或结构的长度。

socket层选项:

SO_BROADCAST BOOL 发送广播信息。

SO_DEBUG BOOL 启用调试输出。

SO_DONTLINER BOOL 允许该项则closesocket()调用立即返回,但是,如果可能,排队的数据将在套接口关闭前发送。

SO_DONTROUTE BOOL 禁止路由选径;直接传送。

SO_KEEPALIVE BOOL 发送“保持活动”包;保持连接。

SO_LINGER struct linger 如关闭时有未发送数据,则等待。

为了允许SO_LINGER选项,应将linger成员l_onoff设为非零,将成员l_linger设为零或需要的超时值(以秒为单位),将成员l_linger设为零即则 closesocket()不被阻塞立即执行,不论是否有排队数据未发送或未被确认。

SO_OOBINLINE BOOL 在常规数据流中接收带外数据。

SO_RCVBUF int socket接收缓冲区大小。

SO_REUSEADDR BOOL 允许套接口和一个已在使用中的地址捆绑(参见bind());地址重用。

SO_RCVTIMEO int 接收超时时间。

SO_SNDBUF int socket发送缓冲区大小

SO_SNDTIMEO int 发送超时时间。

SO_MAX_MSG_SIZE unsigned
int 发送的数据包的最大长度,仅对应面向数据报UDP的情况下。

........

setsockopt()使用举例:

(以下转自http://bbs.csdn.net/topics/60430131)

2.在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:

int nNetTimeout=1000;//1秒

//发送时限

setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));

//接收时限

setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
3.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节

(异步);系统默认的状态发送和接收缓冲区为8K;在实际的过程中发送数据

和接收数据量比较大,可以设置socket缓冲区:

// 接收缓冲区

int nRecvBuf=32*1024;//设置为32K

setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

//发送缓冲区

int nSendBuf=32*1024;//设置为32K

setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

4. 如果在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响

程序的性能:

int nZero=0;

setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));

5.同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):

int nZero=0;

setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));

6.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:

BOOL bBroadcast=TRUE;

setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));

7.在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可

以设置connect()延时,直到accpet()被呼叫(本函数设置只有在非阻塞的过程中有显著的

作用,在阻塞的函数调用中作用不大)

BOOL bConditionalAccept=TRUE;

setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));

8.如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们

一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序满足具体

应用的要求(即让没发完的数据发送出去后再关闭socket):

struct linger {

u_short l_onoff; //调用closesocket()后是否允许逗留

u_short l_linger; //逗留时间

};

linger m_sLinger;

m_sLinger.l_onoff=1;//(在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)

m_sLinger.l_linger=5;//(容许逗留的时间为5秒)

setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));

SO_LINGER选项:

l_onoffl_lingerType of closeWait for close?
zeroDo not care优雅关闭:closesocket()调用立即返回,但是,如果可能,排队的数据将在套接口关闭前发送No
nonzerozero强制关闭:closesocket()调用立即返回,不论是否有排队数据未发送或未被确认No
nonzerononzeroclosesocket()调用直到所剩数据发送完毕或超时才返回。

优雅关闭:如果排队数据在l_linger指定的时间内被全部发送

强制关闭:如果排队数据在l_linger指定的时间内没有被全部发送
1.调用closesocket()一般不会立即关闭socket,而经历TIME_WAIT的过程。如果要已经处于连接状态的soket在调用closesocket()后强制关闭,不经历TIME_WAIT的过程:

BOOL bDontLinger = FALSE;
setsockopt( s, SOL_SOCKET, SO_DONTLINGER, ( const char* )&bDontLinger, sizeof( BOOL ) );
以上使用SO_DONTLINER相当于使用SO_LINGER的时候结构体成员l_onoff设为0(l_linger不关心)。

5、listen()

listen()设置socket为监听模式,并为socket建立一个未决连接的队列。listen()会将到来的但还未来得及accept()的连接请求放到此队列中,一旦连接被accept()那么就从这个队列中删除这个未决连接。函数执行成功返回0,否则返回SOCKET_ERROR。

int listen(
  _In_  SOCKET s,
  _In_  int backlog
);

s:套接字。

backlog:未决连接队列的最大长度,如果连接数超过这个最大长度,则连接请求会被拒绝。backlog的最大值是由下层协议提供程序决定的,还不存在合理最大值的标准,SOMAXCONN为下层服务自动设置此值为合理的一个最大值。

eg:

if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR)
        wprintf(L"listen function failed with error: %d\n", WSAGetLastError());


6、accept()

accept()接收客户端的连接请求,函数执行成功返回一个新的已连接的socket,使用这个socket同客户端进行通信,而原来的监听socket可以接收其它客户的连接请求。运行失败返回INVALID_SOCKET。

SOCKET accept(
  _In_     SOCKET s,
  _Out_    struct sockaddr *addr,
  _Inout_  int *addrlen
);

s:已设置为监听状态的套接字。

addr:返回值参数,包含客户端的IP地址和端口。

addrlen:返回值参数,地址信息的长度。

eg:

SOCKET AcceptSocket;
    sockaddr_in addrClient;
    int len = sizeof(SOCKADDR);
    AcceptSocket = accept(ListenSocket, (SOCKADDR*)&addrClient, &len);
    if (AcceptSocket == INVALID_SOCKET) {
        wprintf(L"accept failed with error: %ld\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }else{
	 char sendBuf[100];
	 sprintf(sendBuf, "Welcome %s:%d!", inet_ntoa(addrClient.sin_addr), ntohs(addrClient.sin_port));
    }
7、send()和recv()

send和recv一般用于TCP连接。

send()向一个已经连接的套接字发送数据。如果没有错误发生,则返回已经发送的数据的大小,其值可能小于len,否则返回SOCKET_ERROR。经我测试:如果对方已经优雅的关闭连接,而此时再调用send()向对方发送数据的话返回值也不会是0,而是实际发送的数据大小,如果是对方异常退出而未调用closesocket()则此时send()会返回SOCKET_ERROR。

int send(
  _In_  SOCKET s,
  _In_  const char *buf,
  _In_  int len,
  _In_  int flags
);

s:一个已经连接的套接字。

buf:要发送的数据缓冲区。

len:buf的大小。

flags:0或以下值及其组合

MSG_DONTROUTE 指明数据不选径,WINDOWS套接字供应商可以忽略此标志。

MSG_OOB
发送带外数据(仅适用于SOCK_STREAM)。

eg:

int send_bytes;
	    char buf[] = "welcome to TCP test server(version 0.1).";
	    int send_total = 0;
	    while(send_total < strlen(buf))
	    {
		 send_bytes = send(conect_socket, buf, strlen(buf), 0);
		 if(send_bytes == SOCKET_ERROR)
		 {
		    wprintf(L"shutdown failed with error: %d\n", WSAGetLastError());
       	    closesocket(ConnectSocket);
                  break;
		 }
		 else
		 {
		     send_total += send_bytes;
		 }
	    }
recv()从一个已经连接的套接字接收数据。如果没有错误发生,函数返回接收到数据的大小,如果对方已经将连接优雅的关闭则返回0,超时或者发生错误返回SOCKET_ERROR。

int recv(
  _In_   SOCKET s,
  _Out_  char *buf,
  _In_   int len,
  _In_   int flags
);

s:一个已经连接的套接字。

buf:接收数据的缓冲区。

len:buf的大小

flags:可以为0或以下值,

MSG_PEEK:读取当前系统缓冲区中数据,但系统缓冲区中数据不会被删除,读取的数据大小可能与缓冲区中数据大小不同。

MSG_OOB:处理带外数据。


MSG_WAITALL:直到buf已满或者连接关闭或者出现错误函数才返回。如果底层传输不支持该选项或socket已经被设置成非阻塞的则函数会失败,错误为WSAEOPNOTSUPP。如果同时指定了 MSG_OOB或MSG_PEEK函数也会出错。

eg:

#define DEFAULT_BUFLEN 512
    char recvbuf[DEFAULT_BUFLEN] = "";
    int iResult;

    //接收数据直到对方关闭连接
    do {

        iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
        if ( iResult > 0 )
            wprintf(L"Bytes received: %d\n", iResult);
        else if ( iResult == 0 )
            wprintf(L"Connection closed\n");
        else
            wprintf(L"recv failed with error: %d\n", WSAGetLastError());

    } while( iResult > 0 );
8、connect()

connect()将套接字与远端地址进行连接。如果没有错误发生函数返回0,否则返回SOCKET_ERROR。

int connect(
  _In_  SOCKET s,
  _In_  const struct sockaddr *name,
  _In_  int namelen
);

s:本地套接字,是即将在其上建立连接的套接字。

name:对方地址。

namelen:地址信息长度。

eg:

int iResult;
    sockaddr_in clientService;
    clientService.sin_family = AF_INET;
    clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
    clientService.sin_port = htons(27015);

    iResult = connect(ConnectSocket, (SOCKADDR *) & clientService, sizeof (clientService));
    if (iResult == SOCKET_ERROR) {
        wprintf(L"connect function failed with error: %ld\n", WSAGetLastError());
        iResult = closesocket(ConnectSocket);
        if (iResult == SOCKET_ERROR)
            wprintf(L"closesocket function failed with error: %ld\n", WSAGetLastError());
    }
9、sendto()和recvfrom()

sendto和recvfrom一般用于UDP连接,但也能用于TCP连接,其比send和recv多了对方地址信息和长度,剩余参数和返回值意义同send和recv。

需要注意的地方是对于面向数据报的socket,比如面向数据报的UDP传输,在发送数据的时候必需注意发送的数据包大小不应超过底层数据包的最大长度,底层数据包的最大长度可以通过getsockopt()函数获得(参数 optname传入SO_MAX_MSG_SIZE)。

int sendto(
  _In_  SOCKET s,
  _In_  const char *buf,
  _In_  int len,
  _In_  int flags,
  _In_  const struct sockaddr *to,//对方地址信息
  _In_  int tolen//对方地址信息长度
);

eg:

//UDP下发送数据
    int iResult;
    sockaddr_in RecvAddr;
    RecvAddr.sin_family = AF_INET;
    RecvAddr.sin_port = htons(Port);
    RecvAddr.sin_addr.s_addr = inet_addr("192.168.1.1");

    wprintf(L"Sending a datagram to the receiver...\n");
    iResult = sendto(SendSocket,
                     SendBuf, BufLen, 0, (SOCKADDR *) & RecvAddr, sizeof (RecvAddr));
    if (iResult == SOCKET_ERROR) {
        wprintf(L"sendto failed with error: %d\n", WSAGetLastError());
        closesocket(SendSocket);
    }


int recvfrom(
  _In_         SOCKET s,
  _Out_        char *buf,
  _In_         int len,
  _In_         int flags,
  _Out_        struct sockaddr *from,//对方地址信息
  _Inout_opt_  int *fromlen//对方地址信息长度
);

eg:

//UDP下接受数据
    int iResult = 0;
    sockaddr_in SenderAddr;
    int SenderAddrSize = sizeof (SenderAddr);
    
    wprintf(L"Receiving datagrams...\n");
    iResult = recvfrom(RecvSocket,
                       RecvBuf, BufLen, 0, (SOCKADDR *) & SenderAddr, &SenderAddrSize);
    if (iResult == SOCKET_ERROR) {
        wprintf(L"recvfrom failed with error %d\n", WSAGetLastError());
      }
10、WSASend()/WSARecv()、WSASendto()/WSARecvfrom()

WSASend和WSARecv(WSASendto和WSARecvfrom)用于完成端口中异步IO。

11、shutdown()

shutdown()用来关闭读或写。

12、ioctlsocket()

ioctlsocket()用来设置套接字的IO模式。

int ioctlsocket(
  _In_     SOCKET s,
  _In_     long cmd,
  _Inout_  u_long *argp
);

s:一个标识套接口的描述字。

cmd:对套接口s的操作命令。

FIONBIO:允许或禁止套接字非阻塞。默认情况下套接字为阻塞模式,若无数据,send/recv会发生阻塞。而WSAAsyncSelect和WSAEventSelect会自动将套接字设为非阻塞模式。如果已对一个套接口进行了WSAAsynSelect() 操作,则任何用ioctlsocket()来把套接口重新设置成阻塞模式的试图将以WSAEINVAL失败,为了把套接口重新设置成阻塞模式,必须首先用WSAAsynSelect()调用(IEvent参数置为0)来禁止WSAAsynSelect()。

eg:将套接字设为非阻塞模式: u_long Len=1; ioctlsocket(SocketServer,FIONBIO,&Len);

将套接字设为阻塞模式: u_long Len=0; ioctlsocket(SocketServer,FIONBIO,&Len);

FIONREAD:使用该参数的意义是获得系统读缓冲区中数据大小。

SIOCATMARK:确实是否所有的带外数据都已被读入。这个命令仅适用于SOCK_STREAM类型的套接口,且该套接口已被设置为可以在线接收带外数据(SO_OOBINLINE)。

argp:指向cmd命令所带参数的指针。

13、WSAAsyncSelect()和WSAEventSelect()

WSAAsyncSelect()和WSAEventSelect()是异步IO事件通知模型,不同的是WSAAsyncSelect是与窗口句柄关联在一起的,必须要窗口才行,而WSAEventSelect是与事件对象关联的。WSAAsyncSelect的基本思路是将WSAAsyncSelect与某个窗口关联,当所关心的网络事件发生时,发送消息到该窗口。WSAEventSelect模型的基本思路是为感兴趣的一组网络事件创建一个事件对象,再调用WSAEventSelect函数将网络事件和事件对象关联起来。当网络事件发生时,winsock使响应的事件对象受信,在事件对象上等待的函数就会立即返回。之后调用WSAEnumNetworkEvents函数便可获得到底发生了什么网络事件。

关于WSAAsyncSelect的使用,可以参考:http://blog.csdn.net/tangaowen/article/details/5780622

http://blog.csdn.net/cloud95/article/details/7688786
http://www.360doc.com/content/12/0712/13/2150347_223766668.shtml
关于WSAEventSelect的使用,可以参考:http://blog.csdn.net/wanjingwei/article/details/4306609

http://blog.csdn.net/wangjieest/article/details/7042108
http://wenku.baidu.com/link?url=GeYw9a1CLmidI112Ff5mFUR_spC7CW7MIsURlM1n8s6WGbsbtiCdibikV1cgltmK0CMPj-NcFubX77q-MIC6h2wHlzQ2s392WHzuGQWm_Oa
参考:《孙鑫VC++深入详解》

http://baike.baidu.com/view/569202.htm?fr=aladdin

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: