建立一个最简单的socket类——网络编程初步

建立一个最简单的socket类——网络编程初步

                                                                                               fancy于2005年1月4日

        网络编程大概是程序员最为热衷的技术之一,本人在无聊的课余稍稍翻阅了那本很著名的大部头《tcp/ip协议详解》。于是尝试着封装了一个极为简陋的socket类,通过编码调试,惊喜的发现它确实可以实现一些简单的传输功能。个人欣慰的同时将其发布出来,希望可以给像我一样蹩脚并挣扎于茫茫黑夜中的各位同仁一点帮助。
        Socket是使用Unix文件描述符和其他程序通讯的方式。我们利用系统调用socket()。它返回套接口描述符,我们可以通过这个套接口描述符来调用send()和recv()。
        Internet套接字有两种类型。一种是“Stream Socket”,流式套接口;另一种是“Datagram Socket”,数据报套接口,也叫无连接套接口。
        流式套接口是可靠的双向通讯的数据流,它是无错误的传递数据的,有自己的错误控制。流式套接口的高质量数据传输是靠了“TCP”协议来实现。像qq,popo的文件传输就是使用这种方法。
        数据报套接口它是不可靠的,当你发送一个数据报的时候,它可能到达,可能数据次序会颠倒。它使用的是“UDP”协议。

        了解了这些,现在让我们来看一段真正的程序:

 //server.cpp
 #include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <string.h>
 #include <sys/types.h>
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <sys/wait.h>
 #define MYPORT 6140  
 #define BUFSIZE 100

 main()
 {
  int sockfd;
  struct sockaddr_in my_addr;
  struct sockaddr_in their_addr;
  char buf[BUFSIZE];

  if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1){
   printf("socket error/n");
   exit(1);
  }

  my_addr.sin_family=AF_INET;
  my_addr.sin_port=htons(MYPORT);
  my_addr.sin_addr.s_addr=INADDR_ANY;
  bzero(&(my_addr.sin_zero),8);

  if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))==-1){
   printf("bind error");
   exit(1);
  }

  int addr_len=sizeof(struct sockaddr);
  int numbytes;
  if((numbytes=recvfrom(sockfd,buf,BUFSIZE,0,(struct sockaddr *)&their_addr,&add_len))==-1){
   printf("recvfrom error");
   exit(1);
  }

  printf("Get package from %s/n",inet_ntoa(their_addr.sin_addr));
  printf("Package is %d bytes/n",numbytes);
  
  buf[numbytes]='/0';
  printf("Package is /"%s/"/n",buf);
  close(sockfd);
 }

 //client.cpp
 #include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <string.h>
 #include <sys/types.h>
 #include <netinet/in.h>
 #include <netdb.h>
 #include <sys/socket.h>
 #include <sys/wait.h>
 #define MYPORT 6140

 int main(int argc,char *argv[])
 {
  int sockfd;
  struct sockaddr_in their_addr;
  struct hostent *he
  if(argc<3){
   printf("lees argument/n");
   exit(1);
  }

  if((he=gethostbyname(argv[1]))==NULL){
   printf("gethostbyname error/n");
   exit(1);
  }
  
  if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1){
   printf("socket error/n");
   exit(1);
  }

  their_addr.sin_family=AF_INET;
  their_addr.sin_port=htons(MYPORT);
  their_addr.sin_addr=*((struct in_addr *)he->h_addr);
  bzero(&(their_addr.sin_zero),8);

  int numbytes;
  if((numbytes=sendto(sockfd,argv[2],strlen(argv[2],0,(struct sockaddr *)&their_addr,sizeof(struct sockaddr)))==-1){
   printf("sentto error/n");
   exit(1);
  }

  printf("Send %d bytes to %s/n",numbytes,inet_ntoa(their_addr.sin_addr));
  close(sockfd);
  return 0;

 }

        从上面的两段程序,我们可以看出,通过套接字发送和接受数据的步骤基本如下:
        首先使用socket()建立套接字,然后使用bind()确定地址和端口。接着就可以sendto()或者recvfrom()数据了。这是数据报套接口的一般过程,流式套接口与此略有差别,它需要使用listen()在指定的端口进行监听。客户端则首先要通过connect()来连接正在监听的端口。每一个连接都将被加入到等待接受(accept())的队列中。服务器调用accept()告诉对方有空闲的连接。accept()返回一个新的套接口文件描述符。原来的那个文件描述符还在继续监听指定的端口,新的描述符用于发送(send())和接受(recv())数据。

        在清楚了套接字的工作原理后,开始对其进行封装,基本的目的是屏蔽一些繁琐的操作,让使用更为简单。

//ESock.h
#ifndef _ESOCK_H_
#define _ESOCK_H_

#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <unistd.h>

claes ESock
{
private:
 int sin_size;
 int sockfd,new_fd;
 unsigned short port;
 struct sockaddr_in my_addr,their_addr,get_addr;
 
public:
 ESock();
 virtual ~ESock();
 int Send(const char *buf,int len);
 bool Connect();
 int Recv(const char *buf,int len);
 void Accept();
 void Listen();
 bool Socket(int type);
 void SetPort(unsigned short in_port);
 void SetRemotePort(unsigned short in_port);
 unsigned short GetRemotePort();
 bool Bind();
 int Recvfrom(const char *buf,int len);
 int Sendto(const char *buf,int len);
 bool SetRemoteAddrees(const char *addrees);
 void GetRemoteAddrees(const char *addrees);
 void Close();
};

#endif

//ESock.cpp
#include "esock.h"

ESock::ESock()
{
}

ESock::~ESock()
{
}

bool ESock::Socket(int type)
{
 if((sockfd=socket(AF_INET,type,0))==-1)
  return false;

 return true;
}

void ESock::SetPort(unsigned short in_port)
{
 my_addr.sin_family=AF_INET;
 port=in_port;
 my_addr.sin_port=htons(port);
 my_addr.sin_addr.s_addr=INADDR_ANY;
}

void ESock::SetRemotePort(unsigned short in_port)
{
 their_addr.sin_port=htons(in_port);
}

unsigned short ESock::GetRemotePort()
{
 return ntohs(get_addr.sin_port);
}

bool ESock::Bind()
{
 if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))==-1)
  return false;

 return true;
}

int ESock::Recvfrom(const char *buf,int len)
{
 int numbytes;
 int addr_len=sizeof(struct sockaddr);
 if((numbytes=recvfrom(sockfd,buf,len,0,(struct sockaddr *)&get_addr,&addr_len))==-1){
  return false;
 }
 buf[numbytes]='/0';
 return numbytes;
}

int ESock::Sendto(const char *buf,int len)
{
 return sendto(sockfd,buf,len,0,(struct sockaddr *)&their_addr,sizeof(struct sockaddr));

}

bool ESock::SetRemoteAddrees(const char* addrees)
{
 struct hostent *he;
 if((he=gethostbyname(addrees))==NULL)
  return false;

 their_addr.sin_family=AF_INET;
 their_addr.sin_port=htons(port);
 their_addr.sin_addr=*((struct in_addr *)he->h_addr);

 return true;

}

void ESock::GetRemoteAddrees(const char *addrees)
{
 strcpy(addrees,inet_ntoa(get_addr.sin_addr));
}

void ESock::Close()
{
 close(sockfd);
 close(new_fd);
}

void ESock::Listen()
{
 listen(sockfd,10);
}

void ESock::Accept()
{
 sin_size=sizeof(struct sockaddr_in);
 new_fd=accept(sockfd,(struct sockaddr *)&get_addr,&sin_size);
 
}


int ESock::Recv(const char *buf,int len)
{
 return recv(new_fd,buf,len,0);
 
}

bool ESock::Connect()
{
 if(connect(sockfd,(struct sockaddr *)&their_addr, sizeof(struct sockaddr))==-1)
  return false;

 return true;
}

bint ESock::Send(const char *buf, int len)
{
 return send(sockfd,buf,len,0);
}


        以上是极为简单的Sock封装,鉴于数据报的使用方法在此之前已有介绍,接下来只贴出用这个ESock类编写的文件传输的测试代码。

//server.cpp
#include <stdio.h>
#include "ESock.h"
#include "EFile.h"

int main()
{
 ESock es;
 es.Socket(SOCK_STREAM);
 es.SetPort(6140);
 es.Bind();
 es.Listen();
 es.Accept();
 
 char buf[1000];
 memset(a,'/0',1000);
 EFile *fd=new EFile("accept.txt","w");
 delete fd;
 bool flag=true;
 int i=0,recvlen=0;
 while(1)
 {  
  recvlen=es.Recv(buf,1000);
  if(recvlen>0)
  {
   EFile *f=new EFile("accept.txt","a");
   f->Write(buf,sizeof(char),recvlen);
   delete f;
   if(flag)
    printf("Geting data……/n");
   flag=false;
   i++;
   printf("/b/b/b/b/b/b");
   printf("%6d",i);

  }
    
 }
 return 0;
}

#include <stdio.h>
#include "ESock.h"
#include "EFile.h"

int main()
{
 ESock es;

 es.Socket(SOCK_STREAM);
 printf("Input IP Address:/n");
 char ip[20];
 scanf("%s",ip);
 es.SetRemoteAddress(ip);
 es.SetRemotePort(6140);
 es.Connect();

 EFile file("send.txt","r");
 char buf[1000];
 memset(buf,'/0',1000);
 int i=0;
 int readlen=0;
 while(readlen=file.Read(buf,sizeof(char),1000))
 {
  es.Send(buf,readlen);
  i++;
  printf("/b/b/b/b/b/b");
  printf("%6d",i);
 }
 printf("Send OK/n");
 es.Close();
 return 0;
}

        远方的eishn用这个程序向我传送了一个大约2M的pdf文件,我用fc将它和原始文件比较,确定传输是可靠的。这也是唯一能给我慰藉的结果。
        以上所有程序均在cygwin下使用gcc编译并运行成功,最后的文件传输程序使用到了自己写的EFile类,相信各位应该能轻松将其用一般的文件读写函数来替换。所有这些都是我极为粗浅的理解和实践,恳请各位专业人士给予我的幼稚以严厉的批评指正!


                                                                                                                                     

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值