Winsock基础编程
Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。
1 基于TCP
它是面向连接的,稳定的网络通信。它的通信流程如下所示:
<1>TCP 连接建立
TCP连接的建立需要经过三次握手,所谓的三次握手如下所示:
- 客户端向服务器发送一个SYN J
- 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
- 客户端再想服务器发一个确认ACK K+1
通俗的讲就是:
A:B,我想连接你
B:A,我看到了,你连接吧
A:那我连了
B:等待接受。。。
<2>TCP连接终止
TCP连接的终止需要下面四次挥手。
- 某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
- 另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
- 一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
- 接收到这个FIN的源发送端TCP对它进行确认。
通俗的讲就是:
A:我要关了啊
B:你要关吗?
B:这么长时间没反应,那我要关了。
A:看到你要关了
B:关闭
<3>TCP稳定性的保证
它采用的是确认重传机制,没发送一个消息它都会启动一个定时器,观察一定时间内有没有收到对方的确认信息,如果收到就算了,没收到它就以为消息没发过去然后就会重新发送。正是由于这种确认重传机制导致了TCP通信的效率没UDP高。
2 基于UDP
它是提供面向无连接的,不可靠的通信协议。它的通信过程如下图所示:
3 相关函数介绍
<1>WSAStartup函数
intPASCAL FAR WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
wVersionRequested:用来指定加载Winsock库的版本,低位字节代表主版本号,高位字节代表副版本号。通常用MAKEWORD(x,y)表示版本号,其中x表示高位自己,y表示低位字节。
lpWSAData:返回的指向WSADATA结构体的指针,这个结构体里面包含着主要关于版本号的信息。
返回值:
成功:返回0。
失败:
WSASYSNOTREADY:表示网络设备没有准备好。
WSAVERNOTSUPPORTTED:Winsock的版本信息号不支持。
WSAEINPROGRESS:一个阻塞式的Winsock1.1存在于进程中。
WSAEPROCLIM:已经达到Winsock的使用量的上限。
WSAEFAULT:lpWSAData不是一个有效的指针。
注意:
这个函数winsock的初始化函数,如果它失败那么后面的所有相关Winsock的都不会被执行。
<2>socket函数
SOCKETsocket(int af, int type, int proctocol);
这个socket有点类似句柄,或者文件指针。
af:address family(地址族),一般都填AF_INIT,表示是在Internet上的Socket;
type:Socket的类型,当采用流连接方式时用SOCK_STREAM,用数据报文方式时用
SOCK_DGRAM。
proctocol:一般都为0,表示对2种类型的Socket分别采用缺省的TCP和UDP传输协议。
<3>bind函数
int bind(SOCKET s, const sockaddr* name, intnamelen);
这个函数的作用是把socket绑定到指定的IP地址以及端口。
s:要绑定的socket
name:指向sockaddr*的结构体指针。结构体如下所示:
struct sockaddr { u_shortsa_family; char sa_data[14]; };
但是通常在Winsock中用sockaddr_in结构体代替这个结构体,它如下所示:
struct sockaddr_in { short sin_family; unsigned short sin_port; struct in_addr sin_addr; char sin_zero[8]; };
其中in_addr是这样一个结构体
struct in_addr { union { struct{ UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b; struct{ USHORT s_w1,s_w2; } S_un_w; ULONG S_addr; } S_un; }
我们可以看到这个结构体里面就有一个union,字节长度是4个字节。可以利用这个结构方便的将点分十进制格式的IP地址转换成u_long类型,并且将结果赋予S_addr。
namelen: name所指向的结构体的长度
注意:
端口号的范围是0-65536,但是1024一下的端口都被预占,所以在指定端口号的要在其上面。
另外,in_addr和sockaddr结构体的字节长度相等。
<4>listen函数
int listen(SOCKET s, int backlog);
s: 启动监听的socket
backlog:等待连接队伍的最大长度,一般设置为1-5
<5>accept函数
SOCKET accept(SOCKET s, sockaddr* addr, int* addrlen);
s:监听状态的套接字
addr:保存客户端的信息,ip,端口号等
addrlen:地址信息的长度,这也是个返回值
注意:
当调用accept函数的时候,程序就会等待客户端调用connect函数发生连接。
<6>connect函数
Int connect(SOCKET s,const sockaddr*name,int namelen);
s:连接的套接字
addr:欲要连接的地址
addrlen:地址信息的长度
<7>send/recv函数
int send(SOCKET s,const char* buf,int len,int flags);
int recv(SOCKET s,const char* buf,int len,int flags);
s:发送信息的socket
buf:发送/接受 数据的缓冲区
len:发送/接受 信息的长度
flags:影响函数行为的值,一般设置为0
注意:
发送的长度一般要根据具体的字节长度来指定,接受的长度则是整个缓冲区的长度。
<8>sendto/recvfrom 函数
int recvfrom(SOCKET s, char* buf, int len,int flags, struct sockaddr* from, int* fromlen);
int sento(SOCKET s, char* buf, int len ,int flags , struct sockaddr* to , int tolen);
s:己方的socket
buf:缓冲区
len:缓冲区长度
flags:一般设置为0,是个影响函数行为的参数
from/to:返回值,表示对方的地址信息 / 输入的对方地址信息
fromlen/tolen:返回值,表示对方地址信息的长度 / 输入的对方地址信息长度
<9> closesocket函数
int closesocket(socket s);
关闭一个socket。
UDP的示例程序:
//server #include <WinSock2.h> #include <stdio.h> #pragma comment(lib,"ws2_32.lib") void main() { WORD wVersionRequested; WSADATA wsaData; interr; wVersionRequested = MAKEWORD(1,1); err =WSAStartup(wVersionRequested,&wsaData); if(err!= 0) { return; } if(LOBYTE(wsaData.wVersion)!= 1 || HIBYTE(wsaData.wVersion) != 1 ) { WSACleanup(); return; } 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) ); charrecvBuf[100]; charsendBuf[100]; chartempBuf[200]; SOCKADDR_IN addrClient; int len= sizeof(SOCKADDR); while(1) { recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len); if('q' == recvBuf[0]) { sendto(sockSrv,"q",strlen("q")+1,0,(SOCKADDR*)&addrClient,len); printf("chat end"); } sprintf(tempBuf,"%s say : %s",inet_ntoa(addrClient.sin_addr),recvBuf); printf("%s\n",tempBuf); printf("please input data:\n"); gets(sendBuf); sendto(sockSrv,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addrClient,len); } closesocket(sockSrv); WSACleanup(); }
//client #include <WinSock2.h> #include <stdio.h> #pragma comment(lib,"ws2_32.lib") void main() { WORD wVersionRequested; WSADATA wsaData; interr; wVersionRequested = MAKEWORD(1,1); err =WSAStartup(wVersionRequested,&wsaData); if(err!= 0) { printf("WSAStartup failed\n"); return; } if(LOBYTE(wsaData.wVersion)!= 1 || HIBYTE(wsaData.wVersion) != 1) { WSACleanup(); return; } SOCKET sockClient =socket(AF_INET,SOCK_DGRAM,0); SOCKADDR_IN addrServer; addrServer.sin_addr.S_un.S_addr =inet_addr("127.0.0.1"); addrServer.sin_family = AF_INET; addrServer.sin_port = htons(6000); charrecvBuf[100]; charsendBuf[100]; chartempBuf[200]; int len= sizeof(SOCKADDR); while(1) { printf("please input data\n"); gets(sendBuf); sendto(sockClient,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addrServer,len); recvfrom(sockClient,recvBuf,100,0,(SOCKADDR*)&addrServer,&len); if('q' == recvBuf[0]) { sendto(sockClient,"q",strlen("q")+1,0,(SOCKADDR*)&addrServer,len); printf("chat end\n"); break; } sprintf(tempBuf,"%s say :%s",inet_ntoa(addrServer.sin_addr),recvBuf); printf("%s\n",tempBuf); } closesocket(sockClient); WSACleanup(); }