Windows Socket 网络编程(二) —— 套接字编程原理
作者: 冰点工作室 小鹰
一、客户机/服务器模式
在TCP/IP网络中两个进程间的相互作用的主机模式是客户机/服务器模式(Client/Server model)。该模式的建立基于以下两点:1、非对等作用;2、通信完全是异步的。客户机/服务器模式在操作过程中采取的是主动请示方式:

首先服务器方要先启动,并根据请示提供相应服务:(过程如下)
1、打开一通信通道并告知本地主机,它愿意在某一个公认地址上接收客户请求。
2、等待客户请求到达该端口。
3、接收到重复服务请求,处理该请求并发送应答信号。
4、返回第二步,等待另一客户请求
5、关闭服务器。
客户方:
1、打开一通信通道,并连接到服务器所在主机的特定端口。
2、向服务器发送服务请求报文,等待并接收应答;继续提出请求……
3、请求结束后关闭通信通道并终止。


二、基本套接字
为了更好说明套接字编程原理,给出几个基本的套接字,在以后的篇幅中会给出更详细的使用说明。
1、创建套接字——socket()
功能:使用前创建一个新的套接字
格式:SOCKET PASCAL FAR socket(int af,int type,int procotol);
参数:af: 通信发生的区域
type: 要建立的套接字类型
procotol: 使用的特定协议

2、指定本地地址——bind()
功能:将套接字地址与所创建的套接字号联系起来。
格式:int PASCAL FAR bind(SOCKET s,const struct sockaddr FAR * name,int namelen);
参数:s: 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
其它:没有错误,bind()返回0,否则SOCKET_ERROR
地址结构说明:
struct sockaddr_in
{
short sin_family;//AF_INET
u_short sin_port;//16位端口号,网络字节顺序
struct in_addr sin_addr;//32位IP地址,网络字节顺序
char sin_zero[8];//保留
}

3、建立套接字连接——connect()和accept()
功能:共同完成连接工作
格式:int PASCAL FAR connect(SOCKET s,const struct sockaddr FAR * name,int namelen);
SOCKET PASCAL FAR accept(SOCKET s,struct sockaddr FAR * name,int FAR * addrlen)
;
参数:同上

4、监听连接——listen()
功能:用于面向连接服务器,表明它愿意接收连接。
格式:int PASCAL FAR listen(SOCKET s, int backlog);

5、数据传输——send()与recv()
功能:数据的发送与接收
格式:int PASCAL FAR send(SOCKET s,const char FAR * buf,int len,int flags);
int PASCAL FAR recv(SOCKET s,const char FAR * buf,int len,int flags);socket编程聊天室基本流程
参数:buf:指向存有传输数据的缓冲区的指针。
6、多路复用——select()
功能:用来检测一个或多个套接字状态。
格式:int PASCAL FAR select(int nfds, fd_set FAR * readfds,fd_set FAR * writefds,
fd_set FAR * exceptfds,const struct timeval FAR * timeout);
参数:readfds:指向要做读检测的指针
writefds:指向要做写检测的指针
exceptfds:指向要检测是否出错的指针
timeout:最大等待时间
select()* 执行同步I/O多路复用。
select函数的参数( int nfds, fd_set readfds, fd_set writefds, fd_set exceptfds, const struct timeval timeout )
我记得是:第一个是个较为次要的值,设成0就行了。 后面的几个FD_SET类型的参数才是最重要的;
第一个FD_SET型的参数readfds是表示要被检查是否可读的 Sockets,把你想要接收数据的那个套接字放在这里;
第二个FD_SET参数ritefds是表示要被检查是否可写的 Sockets,将你要发送数据的套接字
放在这里;
还有个FD_SET参数exceptfds是表示要被检查是否有错误的 Sockets select()
函数的第五个参数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 都是 「called by value- result」;而「called by value-result」的意思就是说,我们在将参数传给系统时,要先设启始值,并将这些参数的位址(address)告诉系统;而系统则会利用到这些值来做些运算或其他用途,最后并将结果再写回这些参数的位址中。 因此这些参数的值在传入前和函数返回后,可能会不同;所以每次调用 select() 前,对这些参数一定要重新设定它们的值。 假设我们要检查 socket 1 和 2 目前是否可以用来传送资料,以及 socket 3 是 否有资料可读;我们不打算检查 sockets 是否有错误发生,所以 exceptfds 设为 NULL。步骤大致如下: FD_ZERO( &writefds ); FD_ZERO( &readfds ); FD_SET( 1, &writefds ); FD_SE
T( 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
格式:BOOL PASCAL FAR closesocket(SOCKET s);

三、典型过程图
2.1 面向连接的套接字的系统调用时序图



2.2 无连接协议的套接字调用时序图



2.3 面向连接的应用程序流程图

FD_ZERO,FD_ISSET这些都是套节字结合操作宏 
  看看MSDN上的select函数, 
  这是在select  io  模型中的核心,用来管理套节字IO的,避免出现无辜锁定. 
  int  select(    int  nfds,fd_set  FAR  *readfds,    fd_set  FAR  *writefds,                             
      fd_set  FAR  *exceptfds,                 
      const  struct  timeval  FAR  *timeout     
  ); 
  第一个参数不管,是兼容目的,最后的是超时标准,select是阻塞操作 
  当然要设置超时事件. 
  接着的三个类型为fd_set的参数分别是用于检查套节字的可读性,可写性,和列外数据性质. 
   
  我举个例子 
  比如recv(),  在没有数据到来调用它的时候,你的线程将被阻塞 
  如果数据一直不来,你的线程就要阻塞很久.这样显然不好. 
  所以采用select来查看套节字是否可读(也就是是否有数据读了) 
  步骤如下 
  socket  s; 
  ..... 
  fd_set  set; 
  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,...); 
       
      } 
      //do  something  here 
  } 
   
  不知道你现在明白没有.另,由于这段时间没忙这,有错误不负责任.呵呵. 

1、Socket服务器端:
Socket服务器端流程如下:加载套接字->创建监听的套接字->绑定套接字->监听套接字->处理客户端相关请求。
下面是孙鑫VC详解里面的服务器端的例子:
C++代码
#include <Winsock2.h> 
#include <stdio.h> 
 
void main() 
    //加载套接字 
    WORD wVersionRequested; 
    WSADATA wsaData; 

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。