WindowsSocket网络编程.docx
《WindowsSocket网络编程.docx》由会员分享,可在线阅读,更多相关《WindowsSocket网络编程.docx(18页珍藏版)》请在冰豆网上搜索。
![WindowsSocket网络编程.docx](https://file1.bdocx.com/fileroot1/2023-2/17/1ea7234f-177e-444b-8add-cb19dec4b418/1ea7234f-177e-444b-8add-cb19dec4b4181.gif)
WindowsSocket网络编程
WindowsSocket网络编程
(二)——套接字编程原理
作者:
冰点工作室小鹰
一、客户机/服务器模式
在TCP/IP网络中两个进程间的相互作用的主机模式是客户机/服务器模式(Client/Servermodel)。
该模式的建立基于以下两点:
1、非对等作用;2、通信完全是异步的。
客户机/服务器模式在操作过程中采取的是主动请示方式:
首先服务器方要先启动,并根据请示提供相应服务:
(过程如下)
1、打开一通信通道并告知本地主机,它愿意在某一个公认地址上接收客户请求。
2、等待客户请求到达该端口。
3、接收到重复服务请求,处理该请求并发送应答信号。
4、返回第二步,等待另一客户请求
5、关闭服务器。
客户方:
1、打开一通信通道,并连接到服务器所在主机的特定端口。
2、向服务器发送服务请求报文,等待并接收应答;继续提出请求……
3、请求结束后关闭通信通道并终止。
二、基本套接字
为了更好说明套接字编程原理,给出几个基本的套接字,在以后的篇幅中会给出更详细的使用说明。
1、创建套接字——socket()
功能:
使用前创建一个新的套接字
格式:
SOCKETPASCALFARsocket(intaf,inttype,intprocotol);
参数:
af:
通信发生的区域
type:
要建立的套接字类型
procotol:
使用的特定协议
2、指定本地地址——bind()
功能:
将套接字地址与所创建的套接字号联系起来。
格式:
intPASCALFARbind(SOCKETs,conststructsockaddrFAR*name,intnamelen);
参数:
s:
是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
其它:
没有错误,bind()返回0,否则SOCKET_ERROR
地址结构说明:
structsockaddr_in
{
shortsin_family;//AF_INET
u_shortsin_port;//16位端口号,网络字节顺序
structin_addrsin_addr;//32位IP地址,网络字节顺序
charsin_zero[8];//保留
}
3、建立套接字连接——connect()和accept()
功能:
共同完成连接工作
格式:
intPASCALFARconnect(SOCKETs,conststructsockaddrFAR*name,intnamelen);
SOCKETPASCALFARaccept(SOCKETs,structsockaddrFAR*name,intFAR*addrlen);
参数:
同上
4、监听连接——listen()
功能:
用于面向连接服务器,表明它愿意接收连接。
格式:
intPASCALFARlisten(SOCKETs,intbacklog);
5、数据传输——send()与recv()
功能:
数据的发送与接收
格式:
intPASCALFARsend(SOCKETs,constcharFAR*buf,intlen,intflags);
intPASCALFARrecv(SOCKETs,constcharFAR*buf,intlen,intflags);
参数:
buf:
指向存有传输数据的缓冲区的指针。
6、多路复用——select()
功能:
用来检测一个或多个套接字状态。
格式:
intPASCALFARselect(intnfds,fd_setFAR*readfds,fd_setFAR*writefds,
fd_setFAR*exceptfds,conststructtimevalFAR*timeout);
参数:
readfds:
指向要做读检测的指针
writefds:
指向要做写检测的指针
exceptfds:
指向要检测是否出错的指针
timeout:
最大等待时间
select()*执行同步I/O多路复用。
select函数的参数(intnfds,fd_setreadfds,fd_setwritefds,fd_setexceptfds,conststructtimevaltimeout)
我记得是:
第一个是个较为次要的值,设成0就行了。
后面的几个FD_SET类型的参数才是最重要的;
第一个FD_SET型的参数readfds是表示要被检查是否可读的Sockets,把你想要接收数据的那个套接字放在这里;
第二个FD_SET参数ritefds是表示要被检查是否可写的Sockets,将你要发送数据的套接字放在这里;
还有个FD_SET参数exceptfds是表示要被检查是否有错误的Socketsselect()
函数的第五个参数timeout,是让我们用来设定select函数要等待(block)多久。
兹述说如下:
(1)如果timeout设为「NULL」,那么select()就会一直等到「至少」某一个socket的事件成立了才会return,这和其他的blocking函数一样。
select(...,NULL)
(2)如果timeout的值设为{0,0}(秒,微秒),那么select()在检查后,不管有没有socket的事件成立,都会马上return,而不会停留。
timeout.tv_sec=timeout.tv_usec=0;select(...,&timeout)
(3)如果timout设为{m,n},那么就会等到至少某一个socket的事件发生,或是时间到了(m秒n微秒),才会return。
timeout.tv_sec=m;timeout.tv_usec=n;select(...,&timeout)
返回值:
成功-符合条件的Sockets总数(若Timeout发生,则为0)失败-SOCKET_ERROR(呼叫WSAGetLastError()可得知原因)
说明:
使用者可利用此函式来检查Sockets是否有资料可被读取,或是有空间可以写入,或是有错误发生。
关于对FD_SET类型的操作,有几个比较重要的宏:
FD_ZERO(*set)--将set的值清乾净FD_SET(s,*set)--将s加到set中FD_CLR(s,*set)--将s从set中删除FD_ISSET(s,*set)--检查s是否存在於set中参数readfds、writefds、及exceptfds都是「calledbyvalue-result」;而「calledbyvalue-result」的意思就是说,我们在将参数传给系统时,要先设启始值,并将这些参数的位址(address)告诉系统;而系统则会利用到这些值来做些运算或其他用途,最后并将结果再写回这些参数的位址中。
因此这些参数的值在传入前和函数返回后,可能会不同;所以每次调用select()前,对这些参数一定要重新设定它们的值。
假设我们要检查socket1和2目前是否可以用来传送资料,以及socket3是否有资料可读;我们不打算检查sockets是否有错误发生,所以exceptfds设为NULL。
步骤大致如下:
FD_ZERO(&writefds);FD_ZERO(&readfds);FD_SET(1,&writefds);FD_SET(2,&writefds);FD_SET(3,&readfds);select(...,&readfds,&writefds,NULL,...)if(FD_ISSET(1,&writefds))send(1,data);if(FD_ISSET(2,&writefds))send(2,data);if(FD_ISSET(3,&readfds))recv(3,data);
7、关闭套接字——closesocket()
功能:
关闭套接字s
格式:
BOOLPASCALFARclosesocket(SOCKETs);
三、典型过程图
2.1面向连接的套接字的系统调用时序图
2.2无连接协议的套接字调用时序图
2.3面向连接的应用程序流程图
FD_ZERO,FD_ISSET这些都是套节字结合操作宏
看看MSDN上的select函数,
这是在selectio模型中的核心,用来管理套节字IO的,避免出现无辜锁定.
intselect(intnfds,fd_setFAR*readfds,fd_setFAR*writefds,
fd_setFAR*exceptfds,
conststructtimevalFAR*timeout
);
第一个参数不管,是兼容目的,最后的是超时标准,select是阻塞操作
当然要设置超时事件.
接着的三个类型为fd_set的参数分别是用于检查套节字的可读性,可写性,和列外数据性质.
我举个例子
比如recv(),在没有数据到来调用它的时候,你的线程将被阻塞
如果数据一直不来,你的线程就要阻塞很久.这样显然不好.
所以采用select来查看套节字是否可读(也就是是否有数据读了)
步骤如下
sockets;
.....
fd_setset;
while
(1)
{
FD_ZERO(&set);//将你的套节字集合清空
FD_SET(s,&set);//加入你感兴趣的套节字到集合,这里是一个读数据的套节字s
select(0,&set,NULL,NULL,NULL);//检查套节字是否可读,
//很多情况下就是是否有数据(注意,只是说很多情况)
//这里select是否出错没有写
if(FD_ISSET(s,&set)//检查s是否在这个集合里面,
{//select将更新这个集合,把其中不可读的套节字去掉
//只保留符合条件的套节字在这个集合里面
recv(s,...);
}
//dosomethinghere
}
不知道你现在明白没有.另,由于这段时间没忙这,有错误不负责任.呵呵.
1、Socket服务器端:
Socket服务器端流程如下:
加载套接字->创建监听的套接字->绑定套接字->监听套接字->处理客户端相关请求。
下面是孙鑫VC详解里面的服务器端的例子:
C++代码
#include
#include
voidmain()
{
//加载套接字
WORDwVersionRequested;
WSADATAwsaData;
interr;
wVersionRequested=MAKEWORD(1,1);
err=WSAStartup(wVersionRequested,&wsaData);
if(err!
=0)
{
return;
}
if(LOBYTE(wsaData.wVersion)!
=1||
HIBYTE(wsaData.wVersion)!
=1)
{
WSACleanup();
return;
}
//创建监听的套接字
SOCKETsockSrv=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_INaddrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//把U_LONG的主机字节顺序转换为TCP/IP网络字节顺序
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
//绑定套接字
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
//将套接字设置为监听模式,准备接受用户请求
listen(sockSrv,5);
SOCKADDR_INaddrClient;
intlen=sizeof(SOCKADDR);
printf("%s\n","welcome,theserveisstarted...");
while
(1)
{
//等待用户请求到来
SOCKETsockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);
charsendBuf[100];
sprintf(sendBuf,"welcome%sto",inet_ntoa(addrClient.sin_addr));
//发送数据
send(sockConn,sendBuf,100,0);
charrevBuf[100];
//接收数据
recv(sockConn,revBuf,100,0);
//打印接受数据
printf("%s\n",revBuf);
//关闭套接字
closesocket(sockConn);
}
}
#include
#include
voidmain()
{
//加载套接字
WORDwVersionRequested;
WSADATAwsaData;
interr;
wVersionRequested=MAKEWORD(1,1);
err=WSAStartup(wVersionRequested,&wsaData);
if(err!
=0)
{
return;
}
if(LOBYTE(wsaData.wVersion)!
=1||
HIBYTE(wsaData.wVersion)!
=1)
{
WSACleanup();
return;
}
//创建监听的套接字
SOCKETsockSrv=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_INaddrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//把U_LONG的主机字节顺序转换为TCP/IP网络字节顺序
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
//绑定套接字
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
//将套接字设置为监听模式,准备接受用户请求
listen(sockSrv,5);
SOCKADDR_INaddrClient;
intlen=sizeof(SOCKADDR);
printf("%s\n","welcome,theserveisstarted...");
while
(1)
{
//等待用户请求到来
SOCKETsockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);
charsendBuf[100];
sprintf(sendBuf,"welcome%sto",inet_ntoa(addrClient.sin_addr));
//发送数据
send(sockConn,sendBuf,100,0);
charrevBuf[100];
//接收数据
recv(sockConn,revBuf,100,0);
//打印接受数据
printf("%s\n",revBuf);
//关闭套接字
closesocket(sockConn);
}
}
注意:
需要包含头文件,并且在工程设置的link里面加上ws32_2.dll
如果在VC中还有一个简单的加载套接字的方法:
C++代码
if(!
AfxSocketInit())
{
AfxMessageBox("套接字加载失败!
");
returnfalse;
}
if(!
AfxSocketInit())
{
AfxMessageBox("套接字加载失败!
");
returnfalse;
}
这个不需要包含上面注里面的头文件和ws2_32.lib库就可以实现加载套接字。
2、Socket客户端:
Socket客户端同样需要先加载套接字,然后创建套接字,不过之后不用绑定和监听了,而是直接连接服务器,发送相关请求。
同样贴出孙鑫VC详解里面的客户端的例子:
(不是我偷懒,是人家实在写的太好,无法超越)
C++代码
#include
#include
voidmain()
{
//加载套接字
WORDwVersionRequested;
WSADATAwsaData;
interr;
wVersionRequested=MAKEWORD(1,1);
err=WSAStartup(wVersionRequested,&wsaData);
if(err!
=0)
{
return;
}
if(LOBYTE(wsaData.wVersion)!
=1||
HIBYTE(wsaData.wVersion)!
=1)
{
WSACleanup();
return;
}
//创建套接字
SOCKETsockClient=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_INaddrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//把U_LONG的主机字节顺序转换为TCP/IP网络字节顺序
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
//向服务器发送请求
connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
//接受数据
charrecBuf[100];
recv(sockClient,recBuf,100,0);
printf("%s\n",recBuf);
//发送数据
send(sockClient,"thisis马亚南1",strlen("thisis马亚南")+1,0);
//关闭套接字
closesocket(sockClient);
WSACleanup();
}
#include
#include
voidmain()
{
//加载套接字
WORDwVersionRequested;
WSADATAwsaData;
interr;
wVersionRequested=MAKEWORD(1,1);
err=WSAStartup(wVersionRequested,&wsaData);
if(err!
=0)
{
return;
}
if(LOBYTE(wsaData.wVersion)!
=1||
HIBYTE(wsaData.wVersion)!
=1)
{
WSACleanup();
return;
}
//创建套接字
SOCKETsockClient=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_INaddrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//把U_LONG的主机字节顺序转换为TCP/IP网络字节顺序
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
//向服务器发送请求
connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
//接受数据
charrecBuf[100];
recv(sockClient,recBuf,100,0);
printf("%s\n",recBuf);
//发送数据
send(sockClient,"thisis扈修非",strlen("thisis扈修非")+1,0);
//关闭套接字
closesocket(sockClient);
WSACleanup();
}
需要加载的头文件和库同上