套接字API函数:
1、socket函数:The socket function creates a socket that is bound to a specific transport service provider.
2、bind函数:The bind function associates a local address with a socket.(一般用于服务器)
3、listen函数:The listen function places a socket in a state in which it is listening for an incoming connection.(一般用于服务器)
4、connect函数:The connect function establishes a connection to a specified socket.(用户客户端)
5、accept函数:The accept function permits an incoming connection attempt on a socket.(用于服务器)
6、send和recv,sendto和recvfrom函数:send and recv data!
下面我们就来挨个讲解:
socket:
函数原型是:
SOCKET WSAAPI socket( __in int af, __in int type, __in int protocol );socket函数来创建一个能够进行网络通信的套接字!
第一个参数af指的是给创建的套接字制定一个地址族!这些值已经在windows头文件中给你定义好了!一般形式是AF_或者PF_!MSDN中有一句话是Note that the values for the AF_ address family and PF_ protocol family constants are identical (for example, AF_INETand PF_INET), so either constant can be used.
AF_INET:表示IPV4网络协议;
AF_INET6:表示IPV6网络协议;
AF_UNIX:表示本地套接字(使用一个文件)
第二个参数type指的是要创建的套接字的类型,他的值也是在头文件中一定定义好的!
SOCK_STREAM: TCP进行传输
SOCK_DGRAM: UDP进行传输
SOCK_RAW: 在网络层上的原始套接字,这个socket类型使用ICMP公共协议
SOCK_SEQPACKET: 可靠地连续数据包服务
最后一个参数protocol指定的是实际使用的传输协议!他的值也是定义在 Winsock2.h and Wsrm.h header files.。 如果在这里设为0的话,那么我们就是使用前两个参数指定的缺省协议!
如果函数失败,就返回INVALID_SOCKET。也就是-1,否则,函数会返回一个代表新分配的描述符的整数。
bind函数:
函数原型是:
int bind( __in SOCKET s, __in const struct sockaddr *name, __in int namelen );他为一个套接字分配地址,你用socket函数创建了套接字后,你只是指定了协议,并没有分配地址,只是用默认的地址和端口号。在他进行其他操作之前,服务器必须先要用bind函数为他绑定上地址和端口号!而客户端就不要这样做了。第一个参数s是一个socket的描述符,你要把你生成的套接字传进来!注意,这个socket是未绑定的。
第二个参数是一指向sockaddr类型的指针。
有两个简单的结构:
struct sockaddr { ushort sa_family; char sa_data[14]; }; struct sockaddr_in { short sin_family;//置AF_INET u_short sin_port;//指明端口号 struct in_addr sin_addr; char sin_zero[8]; };其中sa_family是地址族。对TCP/IP的协议族我们通常传入AF_INET。在用TCP/IP进行绑定的时候,我们通常使用下面的sockaddr_in结构。下面说一下sin_addr: sin_addr结构体中只有一个唯一的字段s_addr,表示IP地址,该字段是一个整数,一般用函数inet_addr()把字符串形式的IP地址转换成unsigned long型的整数值后再置给s_addr。 我们也可以把htonl(INADDR_ANY)置给s_addr,这样做的好处是不论哪个网段上的客户程序都能与该服务程序通信;
用0来填充sin_zero数组,目的是让sockaddr_in结构的大小与sockaddr结构的大小一致。
上面还有最后一个参数namelen没有说,相信应该知道了,这是用来指定socketaddr的长度的。
struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(8888); saddr.sin_addr.s_addr = htonl(INADDR_ANY);
listen函数:
函数原型是:
int listen( __in SOCKET s, __in int backlog );服务器调用listen函数,使得套接字socket处于监听状态!监听链接请求!只能在可靠数据流中使用,如:SOCK_STREAM
,SOCK_SEQPACKET;并
且处于监听状态的流套接字s将维护一个客户连接请求队列,该队列最多容纳backlog个客户连接请求。
函数调用成功返回0,如果失败就返回SOCKET_ERROR;
connect函数:
accept函数:函数原型是:
int connect( __in SOCKET s, __in const struct sockaddr *name, __in int namelen );客户端调用connect函数使得socket s与监听于name所指定的计算机的特定端口上的服务Socket进行连接。对于一些无连接的UDP协议,链接默认发送和接收数据的主机由给定的地址确定。函数成功返回0,失败则返回-1 (SOCKET_ERROR);
struct sockaddr_in daddr; memset((void *)&daddr,0,sizeof(daddr)); daddr.sin_family=AF_INET; daddr.sin_port=htons(8888); daddr.sin_addr.s_addr=inet_addr("127.0.0.1"); connect(socket,(struct sockaddr *)&daddr,sizeof(daddr));
send和recv函数:函数原型是:
SOCKET accept( __in SOCKET s, __out struct sockaddr *addr, __inout int *addrlen );服务器通过调用accept函数从处于监听listen状态的socket的客户队列中取出排在最前面的请求!并且创建一个新的套接字,并且从监听队列中移除这个链接!函数调用成功就返回套接字描述符,否则失败就返回INVALID_SOCKET -1;
s:监听的套接字描述符;
addr:指向sockaddr结构的指针。客户机地址信息。
addrlen:确定客户机结构的大小;
struct sockaddr_in SocketAddr; int addrlen; addrlen=sizeof(SocketAddr); ServerSocket=accept(Socket,(struct sockaddr *)&SocketAddr,&addrlen);
函数原型是:
int send( __in SOCKET s, __in const char *buf, __in int len, __in int flags );不论是客户还是服务器应用程序都用send函数来向TCP链接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答;
s:指定发送端套接字描述符;
buf:一个存放着应用程序要发送数据的缓冲区;
len:实际要发送的数据的字节数;
flags:标志位,影响函数行为,一般置为0;
注意:每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回 SOCKET_ERROR
int recv( __in SOCKET s, __out char *buf, __in int len, __in int flags );不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据;s:指定接收端套接字描述符;
buf:指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
len:指明buf的长度;
flags:标志位,影响函数的行为,一般为0;
如果recv函数出错就会返回SOCKET_ERROR,如果recv函数在等待协议接受数据时网络中断了,就会返回0;
send分为阻塞和非阻塞:
阻塞模式下,如果正常的话,会直到把你所需要发送的数据发完再返回;
非阻塞模式下,会根据你的socket在底层的可用缓冲区的大小来将你的缓冲区当中的数据拷贝过去,有多大的缓冲区就拷贝多少。缓冲区满了就立即返回。这个时候的返回值只表示拷贝到缓冲区多少数据,但是并不代表发送多少数据。同时剩下的部分需要你再次调用send才会再一次拷贝到底层缓冲区;
2012/8/15
jofranks 于南昌