基于TCP/IP的SOCKET接口实现网络通信

本文介绍了基于TCP/IP的SOCKET接口实现网络通信的基本原理及编程方法。涵盖了UNIX下的SOCKET编程实现过程,以及Visual C++5.0提供的SOCKET编程机制。

基于TCP/IPSOCKET接口实现网络通信
基于TCP/IP的SOCKET通令编程接口由4BSD UNIX首先提出,它只能用于UNIX系统。随着微机应和越来越广泛,SOCKET在UNIX的成功应用使得将SOCKET移植到DOS和WIDOWS下成为一件有意义的工作,因此在90年代初,SUN MICROSYSTEM、JSB CORPORATION、FTP SOFTWARE、MICRODYNE以及MICROSOFT等共同制定了一套标准,即WIDOWS SOCKETS规范,把SOCKET机制引入了WIDOWS,先后推出了WINSOCK 1.0、WINSOCK 1.1、WINSOCK 2.0,由于WIDOWS操作系统与UNIX系统任务调度方式的区别,WINSOCK除了可以兼容UNIX和SOCKET编程接口外,又把它加以扩展以适合WIDOWS文件驱动特性。由于WINDOWS编程方法相对复杂,为此,现在提供的编程语言中,都将该机制封装到类中,通过类来编写基于WINDOWS/NT下的网络通信程序。下面我们分别介绍SOCKET通信的有关概念,以及UNIX下的SOCKET编程和基于WINDOWS/NT开发工具提供的SOCKET编程方法。 
  一、基于TCP/IP的SOCKET编程的基本原理
利用基于TCP/IP的SOCKET通信编程接口编写程序,其目的是在TCP/IP所组建网络的不同机器之间利用客户/服务器模式建立通信连接。为建立该连接,开发人员只要提供一些基本的连接信息,其余由操作系统内核来完成。下面我们来讨论建立一个完整通信连接开发者需要提供的信息。以机器A通过TCP/IP与机器B进行网络通信为例,对于机器A来说我们需要知道如下信息:(1)机器B的TCP/IP地址;(2)与机器B中哪一个进程(或软件系统)联系。
以上两个需要的参数,第一个显然可以被大家理解,但第二个也许有人存在疑问,因为象电子邮件收发、TELNET、PING等基本的TCP/IP网络应用程序,在建立连接时都只要提供TCP/IP地址(或者对应域名地址),根本没有必要提供第二个参数,这是为什么呢?其实原因很简单,就是它们使用的是一些标准接口,第二个参数早就定义了,这些应用系统在发出呼叫请求前,自己已经知道该与对方怎样联系,在发请求前,自动将第二个参数加入请求中。如果开发者也要开发这样的系统,就需要知道这些标准接口;对于那些需要建立专用系统网络连接的,却需要双方协商。
以上两个需要提供的参数,在套接字中分别表示为机器B的地址和机器B的通信端口。通过在同一机器的不同通信软件中定义不同端口地址,来表示机器A是与机器B中哪套系统通信。不管是利用何种协议,完全建立一个网络连接需要五个基本信息。它们分别是双方的地址、约定的通讯端口和协议类型。SOCKET通信编程接口并不是专门为TCP/IP通信提供的,因此套接字通信编程需要在参数中指明通信协议类型。套接字是利用客户/服务器模式来实现通信的,客户端软件和服务器端软件的具体实现也有所不同。
具体来说,在客户端利用基于TCP/IP和SOCKET通信编程的基本步骤是:①声明一个套接字类型的变量,需要在该变量定义中提供本机IP地址和通信端口并指明协议类型,由于在此介绍的是基于TCP/IP的套接字通信,因此协议类型应该是TCP/IP,在编程接口中该类型用AF-INET来表示;②向对方发出连接请求,连接时编程者需要提供对方TCP/IP地址和通信端口,同时SOCKET实现程序自动向对提供本机TCP/IP地址和通信端口;③如果连接成功,会收到对方的应答信号,这以后的通信就可以通过套接字的相关操作来实现了。
利用SOCKET来实现服务器端通信软件的步骤是:①同客户端程序第1个步骤;②服务器端通信软件进入等待客户端连接的状态,如果收到连接,则从对方连接请求中获取对方的IP地址和通信端口,并向对方发送连接成功的应答信号。
由上面的介绍可知,客户端与服务器端通过SOCKET通信都需要知道5个基本信息,不同的是客户端软件开发者需要向编程接口全部提供5个参数,而服务器端软件开发者只需要提供3个。
需要注意的是客户端和服务器端所提供的本地端口地址可以相同,可以不同,但客户端口地址可以动态分配,而服务器端端口地址必须固定,否则连接就不能建立。
下面介绍在不同编程语言中编写与客户端和服务器端的通信模块。
  二、UNIX下通过SOCKET实现面向连接的网络通信
  下面分别介绍在UNIX环境下编程时,怎样编写客户端和服务器端的通信模块。
1.客户端通信模块
  (1)步骤一,定义套接字变量
在UNIX中是通过结构sockaddr_in来定义套接字通信的基本信息(该结构的详细内容请参见有关资料),然后通过该结构由系统分配一套接字。
我们声明客户端相关变量如下:
struct sockaddr_in ClientSocketAddr;/*本机通信地址信息变量*/
  struct sockaddr_in ServerSocketAddr;/*服务器通信地址信息*/
  int  ClientSocket; /*套接字句柄变量,也就是系统分配的套接字*/
  int ret,socklen;
(2)步骤二,初始化相关信息,并向系统请求分配套接字
对应程序如下:
ClientSocketAddr.sin_port=htons(2086);/*指定通信端口为2086,并将端口号转换成为网络字节顺序*/
ClientSocketAddr.sin_addr.s_addr=0x81010101f;/*本机IP地址:129.1.1.31,该语句赋值也可用inet_addr(“129.1.1.31”)代替*/
ClientSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);/*分配套接字,参数含义见有关参考书,对于基于TCP/IP编程来说,固定使用该格式调用就可以了*/
if(ClientSocket<0)/*分配失败的原因一般是系统资源不够引起的*/
  {
  perror(“Can't allocate a new socket! \ n”);
  return;
  }
socklen=sizeof(ClientSocketAddr);/* sockaddr_in结构的长度是在不同环境下是不同的,故在此取其大小*/
ret=bind(socketid,&ClientSocketAddr,socklen);/*将套接字与本机的地址信息结合(绑定)起来*/
if(ret==-1)/*绑定失败一般是由于IP地址不正确或者对应通信端口已经与别的套接字绑定了引起的*/
  {
  perror(“Can't bind the new socket to socketaddress”);
  close(socketid);
  return;
  }
指定通信端口为2086时,如果不指定一具体端口,该值可以用系统定义常量INADDR_ANY来代替,表示由系统分配任意一个空闲的套接字,这种方式对于客户端来说应该还要保险些。因为指定端口可能被别的系统占用,这时系统就不能分配该端口对应的套接字给本部分程序,导致下面的分配失败。
(3)向服务器端发出连接请求
ServerSocketAddr.sin_port=1088;/*指定服务器通信端口为1088*/
ServerSocketAddr.sin_addr.s_addr=inet_addr(“129.1.1.10”);/*服务器IP地址:129.1.1.10*/
connect(ClientSocket,&ServerSocketAddr,sizeof(ServerSocketAddr));
2.服务器端通信模块
(1)步骤一和二同客户端程序
下面列出对应C程序:
int ServerSocket,ret,socklen,newsocket,clientsocklen;+struct sockaddr_in ServerSocketAddr,ClientSocketAddr;
  ServerSocketAddr.sin_family=AF_INET;
  ServerSocketAddr.sin_port=htons(1088);/*将端口号转换成为网络字节顺序*/
  ServerSocketAddr.sin_addr.s_addr=inet_addr(“129.1.1.10”);/*将IP地址串转换成为内部表示地址信息*/
ServerSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);/*分配服务器套接字*/
if(ServerSocket<0)
  {
  perror(“Can't allocate a new socket! n”);
  return;
  }
  socklen=sizeof(ServerSocketAddr);
  ret=bind(ServerSocket,& ServerSocketAddr,Socklen);/*将该套接字与服务器通信地址信息绑定*/
  if(ret==-1)
  {
  perror(“Can't bind the new socket to socketaddress”);
  close(ServerSocket);
  return;
  }
(2)步骤三,进入监听客户端连接请求状态,并进入等待客户连接请求状态
  if(listen(ServerSocket,5)<0)/*建立长度为5的请求队列,以允许多个五个客户端同时连接/*
  {
  perror(“Listen error;”);
  close(ServerSocket);
  return;
  }
  for(;;)
  {
  newsocket=accept(ServerSocket,&sockaddr,&socklen);/*监听客户连接请求,如果没客户端连接,程序将一直在此等待*/
  /*连接建立后,用生成的新套接字来处理当前连接请求,而原来监听的套接字继续监听别的连接请求*/
  if(newsocket<0)
  {
  perror(“Accept request failure”);
  close(ServerSocket);
  return;
  }
  if (fork()==0)/*生产子进程来处理连接后的任务*/
  }
  close(ServerSocket);
  DO{
  …… /*执行连接以后的通信任务*/
  
  }/*fork 结束*/
  }/*循环体结束*/
  三、Visul C++ 5.0提供的SOCKET编程机制
在Visul C++ 5.0中提供了CAsyncSocket、CSocket 等MFC类来实现网络通信,其中CSocet类是CAsyncSocket的派生类,它更适合于不需要对网络编程了解太深的应用开发。下面以CAsyncSocket类为例来介绍Visul C++ 5.0中套接字编程的实现。
在使用CAsyncSocket时,最好是从该类派生一新类,以便于开发者接管CAsyncSocket的消息机制,这样开发者可以重写这些消息响应方法,以进行适合自己的处理,这些响应方法包括:
virtual void OnClse(int nErrorCode);//收到对方端开连接时的响应方法
virtual void OnConnect(int nErrorCode);//客户端向对方发出连接请求后收到对方应答时的响应方法
virtual void OnAccept(int nErrorCode);//服务器端可以接收客户连接请求时的响应方法
virtual void OnReceive(int nErrorCode);//收到对端传送数据消息时的响应方法
virtual void OnSend(int nErrorCode);//可以向对端发送数据时的响应消息
下面具体介绍利用CAsyncSocket来实现网络通信的编程方法。在介绍时,将同时介绍服务器端与客户端实现的有关内容。为此,下面分别介绍类CMySocket、CClientSocket、CServerSocket,其中类CMySocket是从CAsyncSocket派生的,而后两者都是从CMySocket派生的,分别用来开发客户端和服务器通信程序。
在此首先介绍CMySocket的定义文件内容:
//Definition of CMysocket File:Socket.h
  class CMySocket:public CAsyncSocket
  {
  //Attributes
  public;
  char  LocalIP[20];//本机IP地址
  char  RemoteIP[20];//对端IP地址
  //Operations
  public;
  CMySocket(LPCTSTR LIP,INT LPort=0,LPCTSTR RIP=NULL,INT RPort=0);//构造函数,参数分别为本机IP地址、本地端口、对端IP地址、对端端口
  virtual CMySocket();//析构函数
  BOOL accept();
  BOOL connect();
  assWizard generated virtual function overrides
  //{{AFX_VIRTUAL(CMySocket)
  public://重写的响应方法
  virtual void OnClose(int nErrorCode);
  virtual void OnConnect(int nErrorCode);
  virtual void OnAccept(int nErrorCode);
  virtual void OnReceive(int nErrorCode);
  virtual void OnSend(int nErrorCode);
  //}}AFX_VIRTUAL
  protected;
  UINT  RemotePort;
  UINT  LocalPort;
  SOCKADDR_IN RemoteAddress;
  int      AddressLen;
  class CMySocket NewSocket;//服务器端监听连接使用
  };
  CMySocket实现文件的内容:
  //implemention of CMySocket:Socket.cpp
  #include“Socket.h”
  CMySocket;;CMySocket()
  {
  }
CMySocket::CMySocket(IPCTSTR LIP,INT LPort,IPCTSTR LIP,INT RPort)
  {
  strcpy(LocalIP,LIP);
  LocalPort=LPort;
  if(RIP)
  {
  Strcpy(RemoteIP,RIP);
  RemotePort=htons(RPort);
  RemoteAddress.sin_addr.s_addr=inet_addr(RemoteIP);
  RemoteAddress.sin_family=AF_INET;
  RemoteADDress.sin_port=RemotePort;
  }
  else
  strcpy (RemoteIP,“ 0”);
  IF(!Create(LPort,SOCK_STREAM,FD_READ|FD_ACCEPT|FD_CONNECT|FD_CLOSE,LocalIP))
  {
  //创建套接字失败则
  ……
    }
  }
  void CMySocket::OnClose(int nErrorCode)
  {
  //收到对方端开连接信息时的相关处理
  ……
  CAsyncSocket::OnClose(nErrorCode);
  }
  void CMySocket::OnClose(int nErrorCode)
  {
  //TODO:Add your specialized code here and /or call the base class
  char my_buff[180];
  strcpy(my_buff-“运行信息 n n”);
  IsConnectingFront=false;
  if(nErrorCode)
  {
  //不能建立连接时的处理
  ……
  }
  else
  {
  //连接建立成功后的处理
  ……
  }
  CAsyncSocket::OnClose(nErrorCode);
  }
  void CMySocket::OnClose(int nErrorCode)
  {
  accpt();
  CAsyncSocket::OnClose(nErrorCode);
  }
  void CMySocket::OnClose(int nErrorCode)
  {
  char buff[1024];
  int i;
  if (nErrorCode==0)
  {
  memset(buff,0,1024);
  i=Receive(buff,1024,!MSG_OOB);
  ……//收到数据后的相关处理
  }
  CAsyncSocket::OnClose(nErrorCode);
  }
  void CMySocket::OnClose(int nErrorCode)
  {
  ……//可以发送时的相关处理
  CAsyncSocket::OnClose(nErrorCode);
  }
  BOOL ClientSocket::connect(){return Connect(RemoteIP,RemotePort);})
  BOOL ClientSocket::accept()
  {
  BOOL i;
  AddressLen=sizeof(RemoteAddress)+8;
  i=Accept(NewSocket,(struct sockaddr *)&RemoteAddress,&AddressLen);
  return i;
  }
在定义了CMySocket类的基础上,就可以正式编写通信代码。
  (1)客户端代码
CMySocket ClientSocket(“129.1.1.31”,2086,“129.1.1.10”,1088);//分配并初始化套接字
ClientSocket.connect();//向服务器端发出连接请求
在连接成功后,基于响应方法进行有关通信处理。
(2)服务器端代码
CMySocket ClientSocket(“129.1.1.10”,1088);//分配并初始化
  ServerSocket.Listen();//创建监听队列
此后,基于响应方法进行有关通信处理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值