使用Winsock的客户端程序

来源:互联网 发布:淘宝商城意尔康女靴 编辑:程序博客网 时间:2024/04/30 02:57

不知道本程序有没有价值,先将Winsock编程学会了再说。

在正式开始前先介绍一下客户端/服务器程序。很多网络程序都是采用的客户端/服务器模型,简称C/S模型。事实上,这种架构我们非常熟悉,浏览器和Web服务器之间就是C/S的关系。知名服务器和客户端之间的数据交互是按照一定的标准进行的,从TCP/IP体系结构来说属于应用层协议。与知名C/S应用相比,我们自己开发的C/S应用采用的是私有的应用协议,而不是公开标准(当然,你也可以使用知名的协议)。有了这点认识,我们再来看看网络C/S程序的要点。

1面向连接与无连接

服务器与客户端之间的数据通信是由底层的计算机网络通信协议TCP/IP来提供的,TCP/IP协议族为应用层服务提供了两种传输层服务:面向连接的TCP协议和无连接的UDP协议。因此,在开发自己的C/S系统时,首先要决定的是选择面向连接的还是无连接的传输协议。

TCP与UDP之间的差别是很明显的:UDP是一种非常简单的传输层协议,它并不能保证数据最终到达目的地;与此相比,TCP提供可靠的传输服务,确认,超时和重传机制确保了应用层数据会被可靠地传送到目的地,同时TCP还提供流量控制。

采用UDP还是采用TCP并没有一定的标准,通常只有在以下几种情况下采用UDP协议:

(1)C/S之间的数据交互非常简单,如标准服务daytime和echo。

(2)与数据丢失相比,系统更不能容忍超时重传所带来的时间耗费,典型的应用是音视频系统。

(3)系统架构要求采用组播或者广播报文的方式。

除此之外,如果要在系统中采用UDP协议,那么就必须设计良好的能保证数据可靠传输的应用层协议:要达到这个目标,确认,超时重传和流量控制或许都是必需的,而这正是TCP协议所提供的。

2并发与迭代

通常情况下,服务器需要同时向多个客户提供服务,而客户端通常只与一个服务器联系(一个常见的例外是WEB浏览器)。我们把能同时接受多个客户连接的服务器称为并发服务器(Concurrent Server),而把一次只能服务一个客户的称为迭代服务器(Iterative Server)。

迭代服务器通常用于提供一些简单的服务,如daytime,echo等。对于复杂服务的提供来说,长时间地停顿在一个客户的服务上而拒绝其它客户是不可取的。

有了以上的知识,我们开发一个客户端程序来做分析:

#pragma comment(lib,"ws2_32.lib")

#include <STDIO.H>
#include <WINSOCK2.H>

SOCKET g_sockClient=INVALID_SOCKET;

void usage();

BOOL WINAPI CtrlHandler(DWORD dwEvent);

int main(int argc,char *argv[])
{
 unsigned long destAddr;
 int nPort;
 if(argc==2)
 {
  destAddr=inet_addr(argv[1]); // parse the IPV4 address into 32bit integer
                                                       // which suitable for the struct in_addr
  if(destAddr==INADDR_NONE)
  {
   usage();
   return -1;
  }
  nPort=23;
 }
 else
  if(argc==3)
  {
   destAddr=inet_addr(argv[1]);
   if(destAddr==INADDR_NONE)
   {
    usage();
    return -1;
   }
   nPort=atoi(argv[2]);
   if(nPort<=0||nPort>65535)
   {
    usage();
    return -1;
   }
  }
  else
  {
   usage();
   return -1;
  }
 if(!SetConsoleCtrlHandler(CtrlHandler,TRUE))
 {
  printf("SetConsoleCtrlHandler: %d/n",GetLastError());
  return -1;
 }

 WSADATA wsaData;
 WSAStartup(WINSOCK_VERSION,&wsaData);

 g_sockClient=socket(AF_INET,SOCK_STREAM,0);
 if(g_sockClient==INVALID_SOCKET)
 {
  WSACleanup();
  return -1;
 }

 struct sockaddr_in to;
 memset(&to,0,sizeof(to));
 to.sin_addr.S_un.S_addr=destAddr;
 to.sin_family=AF_INET;   // set the address family
 to.sin_port=htons(nPort);  // change the unsigned short into "Big-Endian"
 printf("connecting %s:%d......",inet_ntoa(to.sin_addr),nPort);

 // connect the destinate ip address
 if(connect(g_sockClient,(struct sockaddr *)&to,sizeof(to))==SOCKET_ERROR)
 {
  if(g_sockClient!=INVALID_SOCKET)
   closesocket(g_sockClient);
  printf("Failed.(connect %d)/n",WSAGetLastError());
  WSACleanup();
  return -1;
 }
 else
  printf("Successfully./nINPUT:/n");

 char bufa[83];
 char bufb[1000];
 fd_set readSet;
 struct timeval tv;
 int ret,len;
 while(1)    // loop and handle the network event
 {
  memset(bufa,0,83);
  gets(bufa);
  len=strlen(bufa);
  if(len>80)
   len=80;
  bufa[len]='/r';
  bufa[len+1]='/n';
  bufa[len+2]=0;
  ret=send(g_sockClient,bufa,strlen(bufa),0);  // send the data of bufa to the destinate address
  if(ret==SOCKET_ERROR)
  {
   printf("send: %d/n",WSAGetLastError());
   break;
  }

  FD_ZERO(&readSet);
  FD_SET(g_sockClient,&readSet);
  tv.tv_sec=3;
  tv.tv_usec=0;
  ret=select(0,&readSet,NULL,NULL,&tv);

  if(ret==SOCKET_ERROR)
  {
   printf("select: %d/n",WSAGetLastError());
   break;
  }
  if(ret==0)
  {
   printf("Timeout,No Response From Server./n");
   break;
  }
  if(FD_ISSET(g_sockClient,&readSet))
  {
   memset(bufb,0,1000);
   ret=recv(g_sockClient,bufb,1000,0);
   if(ret==SOCKET_ERROR)
   {
    printf("recv: %d/n",WSAGetLastError());
    break;
   }
   else
    printf("%s/n",bufb);
  }
 }
 if(g_sockClient!=INVALID_SOCKET)
  closesocket(g_sockClient);
 WSACleanup();
 printf("Stopped./n");

 return 0;
}

/* print the usage of mytelnet*/
void usage()
{
 printf("usage:/tmytelnet x.x.x.x port/t(0<port<65535)/n");
}

BOOL WINAPI CtrlHandler(DWORD dwEvent)
{
 switch(dwEvent)
 {
 case CTRL_C_EVENT:
 case CTRL_LOGOFF_EVENT:
 case CTRL_SHUTDOWN_EVENT:
 case CTRL_CLOSE_EVENT:
  printf("Stopping....../n");
  closesocket(g_sockClient);
  g_sockClient=INVALID_SOCKET;
  break;
 default:
  return FALSE;
 }
 return TRUE;
}

接下来我们连编该程序,并尝试登陆清华的bbs,输入telnet 161.111.8.238,然后回车。我得到的画面是这样的:

这个好像有点不对劲,呵呵,具体细节以后再讨论。接下来我们看看这个客户端的结构。

1.我们使用#pargma comment(lib,"ws2_32.lib")指示连接器查找并使用ws2_32.lib静态库。如果不使用编码方式,也可以选择在项目设置的Link选项中加入ws2_32.lib。

2.SOCKET g_sockClient=INVALID_SOCKET;声明套接口类型的全局变量g_sockClient。

3.读取输入参数目标IP和端口,分别保存在destAddr和nPort中。

4.调用SetConsoleCtrlHandler函数为MyTelnet设置某些特殊事件的响应函数,这些特殊事件有CTRL_C_EVENT,CTRL_CLOSE_EVENT等。

5.调用WSAStartup函数,初始化Winsock。

6.创建客户端的TCP套接口g_sockClient。

7.填充INET地址结构to,即设定所需连接的TCP服务器的IP地址和端口。

8.根据地址to,连接TCP服务器。

9.在循环体内,我们从控制台读取用户输入数据,发送给服务器,然后接收服务器的反馈并打印。

10.ret=select(0,&readSet,NULL,NULL,&tv);使用了select函数进行超时控制。

11.调用closesocket函数关闭客户端套接口g_sockClient。

12.调用WSACleanup函数,结束Winsock的使用。

BOOL WINAPI CtrlHandler(DWORD dwEvent)为控制台事件的响应函数。如果MyTelnet接受到CTRL_C_EVENT,CTRL_LOGOFF_EVENT,CTRL_SHUTDOWN_EVENT或者CTRL_CLOSE_EVENT事件,那么客户端关闭套接口g_sockClient(将导致主程序中关于g_sockClient的Winsock函数调用出错,从而退出循环),并将其赋值为INVALID_SOCKET。