windows的网络编程接口没有像linux那么丰富,功能也要少很多,下面针对几个主要的接口做一下介绍:
1. int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);
这个函数比较简单,目的是初始化socket,所有其他socket接口调用之前都要调用这个接口,调用成功会返回0,调用失败返回非0值。
例:
WSADATA wsaData;
if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
{ */ printf("WSAStartup failed with error: %d\n", err); return 1; }
2. int WSACleanup(void);
此函数与 WSAStartup 相对,是释放socket库资源,到所有的socket函数后面使用,一般在程序结束的地方调用。
3. SOCKET socket(int af,int type,int protocol);
此函数为创建一个SOCKET对象,针对TCP和UDP传入的参数会有点不一样,调用失败会返回INVALID_SOCKET。
例:TCP:SOCKET s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
UDP:SOCKET s=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
4. int connect(SOCKET s, const struct sockaddr*name, intnamelen);
函数调用失败返回SOCKET_ERROR.
此函数在TCP中的使用:
a.此函数调用成功客户端与服务器之间就建立了一条链接。
b.若TCP客户没有收到SYN分节的响应,则返回ETIMEOUT错误。
c.若TCP客户收到RST响应,则返回ECONNREFUSED错误。
d.若TCP客户connect失败,则必须关闭socket重新创建以后才能再次调用connect函数。
e.调用此函数的TCP状态变化:CLOSED->SYN_SENT->ESTABLISHED。
阻塞模式:
(1)如果TCP服务端没有打开,则connect会立马返回SOCKET_ERROR(-1),调用WSAGetLastError会返回WSAECONNREFUSED(10061)错误。
(2)如果TCP服务端运行正常(服务端必须调用监听函数(listen)以后,TCP/IP内核才会接收TCP客户端的连接),则connect会立马返回0,表示连接成功。
非阻塞模式:
(1)如果TCP服务端没有打开,则connect会立马返回SOCKET_ERROR(-1),调用WSAGetLastError会返回WSAEWOULDBLOCK(10035)错误。
(2)如果TCP服务端运行正常,则connect会立马返回SOCKET_ERROR(-1),调用WSAGetLastError会返回WSAEWOULDBLOCK(10035)错误。
由以上可知,如果将socket设置为非阻塞,通过返回值是没有办法判断connect是否连接成功,此时要判断connect是否调用成功,需要先调用select,发现描述符变成可写以后,则connect成功,否则失败。
例.阻塞模式调用:
struct sockaddr_in clientService; clientService.sin_family = AF_INET; clientService.sin_addr.s_addr = inet_addr("127.0.0.1"); clientService.sin_port = htons(27015); // Connect to server.
connect(s, (SOCKADDR *) & clientService, sizeof (clientService));
非阻塞模式调用:
FD_SET mask; u_long value=1; TIMEVAL timeout;struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(27015);
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
ioctlsocket(sSocket,FIONBIO,&value);//设置为非阻塞
connect(sSocket,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
timeout.tv_sec=2;
timeout.tv_usec=0;
FD_ZERO(&mask);
FD_SET(sSocket,&mask);
value=select(NULL,NULL,&mask,NULL,&timeout);
if(value && value!=SOCKET_ERROR)
{
//连接成功
value = 0;
ioctlsocket(sSocket,FIONBIO,&value);//设置为阻塞
}
else
{
shutdown(sSocket,SD_BOTH);
closesocket(sSocket);
sSocket = 0;
}
此函数在UDP中的使用:
a.UDP中也能调用此函数,但是并没有建立一条真正的链接,只是限定UDP的通信对端的IP和端口。由于UDP有了对端的IP和端口,因此发送和接收数据的时候也可以使用send和recv函数,后面会介绍这2个函数。
5. SOCKET accept(SOCKET s,struct sockaddr *addr,int *addrlen);
此函数为TCP服务器用来接收TCP客户端的连接的。
如果将 s 监听的socket设置阻塞模式的,此函数会一直阻塞到与客户端连接到来为止。
如果将 s 监听的socket设置为非阻塞模式,此函数调用失败会返回INVALID_SOCKET,如果调用WSAGetLastError函数返回WSAEWOULDBLOCK(10035)表示还没有TCP客户端来连接TCP服务器。
如果监听的 s 为非阻塞模式,则收到的socket也为非阻塞模式。
如果监听的 s 为阻塞模式,则收到的socket也为阻塞模式。
例:阻塞模式:SOCKET sockClient=accept(sockListen,NULL,NULL);//此函数返回表示已经有客户端连接到来,所以sockClient不会为INVALID_SOCKET.
非阻塞模式:SOCKETsockClient=accept(sockListen,NULL,NULL);
if(INVALID_SOCKET==sockClient)//表示此次调用没有客户端连接到来
{
printf("TCP_Server accept failed!\n");
continue;
}
6.int listen(SOCKETs,intbacklog);
此函数为TCP服务器用来将 SOCKET s由主动模式变为被动模式。 backlog 为TCP协议栈中已经完成3次握手和正在进行3次握手的连接数。比如通常将backlog设置成5,则最多同一时间只能有5个客户端连接过来。等其中有一连接已经被accept接口取出以后,又可以有新的客户端连接进来。
函数调用失败返回:SOCKET_ERROR。
例:if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR)
printf("listen function failed with error: %d\n", WSAGetLastError());
7.int bind(SOCKETs,const struct sockaddr*name,intnamelen);
此函数为 SOCKET s 关联一个本地地址。调用失败,返回SOCKET_ERROR。
如果不调用bind函数,当调用connect(TCP客户端)和listen(TCP服务端)时,内核就要为相应的套接字选择一个临时端口。指定端口0为未指定端口。bind被调用时选择一个临时端口,若未指定IP地址(INADDR_ANY),则内核将等到套接字已经连接(TCP)或已在套接字上发出数据报(UDP)时选择IP。例:
struct 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) {}
8.intsendto(SOCKET s,const char * buf,int len,int flags,const struct sockaddr * to,int tolen);
int recvfrom(SOCKET s,char * buf,int len,int flags,struct sockaddr * from,int * fromlen);
int send(SOCKET s,const char * buf,int len,int flags);
阻塞模式:
(1)如果对端没有接收,并且TCP/IP缓冲区也已经满,则send函数会阻塞,直到有缓冲区存放为止。
(2)如果对端关闭socket,则send函数返回SOCKET_ERROR(-1),调用WSAGetLastError函数返回WSAECONNABORTED(10053)或者WSAECONNRESET(10054)错误。
非阻塞模式:
(1)如果对端没有接收,并且TCP/IP缓冲区也已经满,则send函数会返回SOCKET_ERROR(-1),调用WSAGetLastError函数返回WSAEWOULDBLOCK(10035)错误。
(2)如果对端关闭socket,则send函数返回SOCKET_ERROR(-1),调用WSAGetLastError函数返回WSAECONNABORTED(10053)或者WSAECONNRESET(10054)错误。
int recv(SOCKET s,char * buf,int len,int flags);
阻塞模式:
(1)recv会一直等待,如果有数据到来则接收数据,返回接收的数据大小。
(2)recv会一直等待,如果对端关闭了socket,则recv会返回0.
非阻塞模式:
(1)recv会立马返回,如果没有任何数据能接收,则返回SOCKET_ERROR(-1),调用WSAGetLastError函数则返回WSAEWOULDBLOCK(10035)错误。
(2)recv会立马返回,如果对端关闭了socket,则recv会返回0.
(3)recv会立马返回,如果TCP有数据接收,则直接返回接收到的数据大小。
以上4个函数我TCP和UDP用来接收和发送数据的,在调用这几个接口之前可以调用ioctlsocket 函数将这几个函数变成非阻塞式的。
返回值为真实发送和接收的数据大小。
9.intshutdown(SOCKET s,int how);
此函数为关闭连接的接收部分或者发送部分或者发送接收都关闭,函数调用失败返回:SOCKET_ERROR。
how参数对应下表:
Value | Meaning |
---|---|
| 关闭接收这一部分 |
| 关闭发送这一部分 |
| 关闭发送和接收 |
10. int closesocket(SOCKET s);
默认行为:把套接字标记为已关闭,然后返回到调用进程,TCP发送完已经排队的数据,发送完毕以后发生正常的TCP 4分组终止序列,减少对应套接字的一个引用计.关闭发送和接收。可以通过调用setsocketopt函数来改变这个默认行为。函数调用失败返回:SOCKET_ERROR。
11.int getsockname(SOCKET s,struct sockaddr * name,int * namelen);
int getpeername(SOCKET s,struct sockaddr * name,int * namelen);
此函数的作用:查看内核选择的临时端口和IP地址,返回与套接字有关的本地协议地址和外地协议地址,TCP客户没有调用bind就直接调用connect,用getsockname来返回内核赋予该连接的本地IP地址和本地端口号.函数调用失败返回:SOCKET_ERROR.
12.//主机字节序和网络字节序的转换
u_short htons(u_short hostshort);
u_long htonl(u_long hostlong);
u_short ntohs(u_short netshort);
u_long ntohl(u_long netlong);
13.IPV4字符串和整形(为网络字节序)之间的转换
unsigned long inet_addr(const char * cp);
char * inet_ntoa(struct in_addr in);
14 . int ioctlsocket(SOCKET s,long cmd,u_long FAR* argp);
此函数为设置connect,accept,recv,recvfrom是否为阻塞模式。如果调用失败,返回:SOCKET_ERROR 。
例:
u_long mode = 0;
ioctlsocket(s,FIONBIO,&mode);
控制为阻塞方式。
u_long mode = 1;
ioctlsocket(s,FIONBIO,&mode);
控制为非阻塞方式。
14.设置获取获取socket的配置项。
int getsockopt(SOCKET s,int level,int optname,char * optval,int * optlen);
int setsockopt(SOCKET s,int level,int optname,const char * optval,int optlen);
由于这2个函数的参数输入情况比较多,所以要想知道具体使用,请参考一下链接: SetSockOpt使用
15.int select( int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout );
The select function returns the total number of socket handles that are ready and contained in the fd_set structures, zero if the time limit expired, or SOCKET_ERROR if an error occurred. If the return value is SOCKET_ERROR, WSAGetLastError can be used to retrieve a specific error code.
Error code | Meaning |
---|---|
WSANOTINITIALISED | A successful WSAStartup call must occur before using this function. |
WSAEFAULT | The Windows Sockets implementation was unable to allocate needed resources for its internal operations, or the readfds, writefds, exceptfds, or timeval parameters are not part of the user address space. |
WSAENETDOWN | The network subsystem has failed. |
WSAEINVAL | The time-out value is not valid, or all three descriptor parameters were NULL. |
WSAEINTR | A blocking Windows Socket 1.1 call was canceled through WSACancelBlockingCall. |
WSAEINPROGRESS | A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a callback function. |
WSAENOTSOCK | One of the descriptor sets contains an entry that is not a socket. |
readfds:
- If listen has been called and a connection is pending, accept will succeed.
- Data is available for reading (includes OOB data if SO_OOBINLINE is enabled).
- Connection has been closed/reset/terminated.
writefds:
- If processing a connect call (nonblocking), connection has succeeded.
- Data can be sent.
exceptfds:
- If processing a connect call (nonblocking), connection attempt failed.
- OOB data is available for reading (only if SO_OOBINLINE is disabled).
对TCP和UDP的一些总结:
TCP是基于流模式的,发送端经过多次发送的数据,接收端也许一次就能全部接收。
UDP是基于报式的,发送端每次发送了多大的数据包,接收端就会接收到多大的数据包,如果因为发送端发送的某个数据包太大,接收端的数据缓冲区太小,则recvfrom函数会返回-1。表示此函数调用失败。所以建议UDP接收缓冲区要设置的大一点。防止出现以上错误。
下面是我写的一个TCP和UDP通信demo,里面测试了以上的部分接口,有些接口调用被注释了,要测试的话要去掉注释。