using Socket in C++

来源:互联网 发布:c语言程序举例 编辑:程序博客网 时间:2024/05/17 06:55

先从底层说起,Socket是如何实现的?

图1,Linux下收包。可见Socket是由操作系统内核在MAC层——IP层——传输层上实现的。

真正从网卡(NIC)进来的数据是完整的MAC帧,底层用数据结构sk_buff 描述,最终进入接收缓冲区recv buffer,而我们应用层调用read / recv /recvfrom 从接收缓冲区拷贝数据到应用层提供的buffer,对一般的套接字,如SOCK_STREAM(TCP), SOCK_DGRAM(UDP) 来说,此时缓冲区只有user data,其他各层的头部已经被去除,而对于SOCK_RAW 来说是IP head + IP payload,当然也可以是arp/rarp 包,甚至是完整的帧(加上MAC头)。

下面介绍Socket编程的基本步骤:


涉及到的数据结构:

Sockaddr地址的统一表示:
  1. 地址家族号  short
  2. 地址具体值   14字节
struct sockaddr_in {//用于TCP/IP家族的地址表示
        short   sin_family; //地址家族号,为扩展提供可能,一般取0,表示TCP/IP家族
        u_short sin_port;  //端口号
        struct  in_addr sin_addr; //存ip地址,具体见下图
        char    sin_zero[8]; //填充位,取8个0
};

Host Entry:可通过gethostbyname(char *)获取hostent*;

struct hostent{
char * h_name; //地址正式名称
char ** h_aliases; //别名数组
short h_addrtype; //地址类型,一般取AF_INET
short h_length; //地址长度(bit)
char ** h_addr_list; //IP列表,网络字节序
#define h_addr h_addr_list[0]; //第一个地址
};

点分十进制和无符号长整型互相转换

  1. long inet_addr(char* ip)点分十进制转网络字节序
  2. char* inet_ntoa(in_addr)网络字节序转点分十进制

方法介绍:

服务器端的sockaddr_in.sin_addr可以用IN_ADDRANY
创建socket的参数:
  1. 协议簇的类型,即指定通信域现在为TCP/IP  PF-INET。
  2. 传输层协议:SOCK_STREAM字节流(TCP)||SOCK_DGRAMA数据报文(UDP)
  3. 网络层协议:一般为0指ip
  • 创建后会自动绑定ip和随机端口号
Bind:
  1. Socket描述符
  2. 网络进程的地址信息,包括ip,port等
  3. 地址信息这个结构体的长度
  • 一定要注意,一般只在listen的服务器端口进行绑定。客户端和服务器并发新开的线程没有该过程,因为他们的交互是短暂的,用完就将资源交还给OS。服务器绑定是因为提供的服务是约定好的。
  • 另外 绑定是包括ip的,这对多网卡服务器是有意义的。
Listen:成功返回1,失败小于0
  1. Socket描述符
  2. 等待队列的最大长度,最大20一般5-10
  • 注意UDP方式则不需要提供该操作,因为是无连接的,接收方不需要等待。
  • 服务器收到请求,队列不满,则放到缓冲队列,用accept调用。
Accept(默认阻塞):
  1. 服务器端用于listen的Socket描述符,以提供服务器的地址信息
  2. Sockaddr指针,所接收客户端的网络地址信息。是出参,只给定义即可
  3. 上述地址的长度信息
  • 如果连接成功,系统将返回一个新的套接字描述符int,该套接字此后专用于与该客户机的数据交换。但需要注意,这返回的仅仅是新建好的一个套接字描述符,应该建立线程处理用户的请求。
  • Accept会有阻塞,缓冲队列取不到东西,则一直等待,使得主进程无法继续进行。而listen只是表示开始监听,指定哪个地方是接收请求的,缓冲队列长度,指定完就返回,不存在阻塞。
Connect(默认阻塞):
  1. 客户机的socket标识符
  2. 服务器的网络地址信息
  3. 地址长度
Read/write/send/recv(均为默认阻塞):
  1. 自己的套接字标识符
  2. 读写缓冲区指针
  3. 缓冲区长度
Close:
  • 关闭连接前应先终止连接

TCP通信,C++Code:

/*------------------------------------------* 程序:server.c* 目的: 分配一个套接字,然后反复执行如下几步:* (1) 等待客户的下一个连接* (2) 发送一个短消息给客户* (3) 关闭与客户的连接* (4) 转向(1)步* 命令行语法: server [ port ]* port – 服务器端监听套接字使用的协议端口号* 注意: 端口号可选。如果未指定端口号,服务器使用PROTOPORT中指定的缺省* 端口号*-----------------------------------------------*/ #include  <sys/types.h>#include  <sys/socket.h>#include  <netinet/in.h>#include  <netdb.h>#include  <stdio.h>#include  <string.h>#define  PROTOPORT  5188   /* 监听套接字的缺省协议端口号 */#define  QLEN  6          /* 监听套接字的请求队列大小 */int  visits = 0;                        /* 对于客户连接的计数*/ main(argc,argc)int  argc;char* argv[];{struct  hostent  *ptrh;          /* 指向主机列表中一个条目的指针 */struct  sockaddr_in  servaddr;   /* 存放服务器网络地址的结构 */struct  sockaddr_in  clientaddr;  /* 存放客户网络地址的结构 */int  listenfd;              /* 监听套接字描述符 */int  clientfd;              /* 响应套接字描述符 */int  port;                     /* 协议端口号 */int  alen;                     /* 地址长度 */char  buf[1000];          /* 供服务器发送字符串所用的缓冲区 */ /* 清空sockaddr结构 */memset( (char*)& servaddr, 0, sizeof(servaddr) );  servaddr.sin_family = AF_INET;   /* 设置为因特网协议族 */servaddr.sin_addr.s_addr = INADDR_ANY; /* 设置本地IP地址 */ /* 检查命令行参数,若已指定,则使用该端口号,否则使用缺省端口号 */if (argc > 1){                port = atoi(argv[1]); /* 如果指定了端口号,就将它转换成整数 */} else {   port = PROTOPORT;       /* 否则,使用缺省端口号 */} /* 将本地地址绑定到监听套接字*/if ( bind( listenfd, (struct sockaddr *)& servaddr, sizeof(servaddr)) < 0) {fprintf(stderr, ”bind failed\n” );exit(1);} /* 开始监听,并指定监听套接字请求队列的长度 */if  (listen(listenfd, QLEN) < 0) {fprintf(stderr, ”listen filed\n” );exit(1);} /* 服务器主循环—接受和处理来自客户端的连接请求 */while(1) {    alen = sizeof(clientaddr);      /* 接受客户端连接请求,并生成响应套接字 */    if((clientfd = accept( listenfd, (struct sockaddr *)& clientaddr, &alen)) < 0 ) {         fprintf( stderr, “accept failed\n”);         exit(1);    }    visits++;                       /* 累加访问的客户数 */    sprintf( buf, “this server has been contacted  %d  time \n”, visits );    send(clientfd, buf, strlen(buf), 0 );  /* 向客户端发送信息 */    closesocket( clientfd );           /* 关闭响应套接字 */}} 
/*----------------------------------------------------* 程序: client.c* 目的: 创建一个套接字,通过网络连接一个服务器,并打印来自服务器的信息* 语法: client [ host [ port ] ]*             host - 运行服务器的计算机的名字*             port - 服务器监听套接字所用协议端口号* 注意:两个参数都是可选的。如果未指定主机名,客户使用localhost;如果未指定端口号, * 客户将使用PROTOPORT中给定的缺省协议端口号*----------------------------------------------------*/ #include <sys/types.h> /* 基本系统数据类型*/#include <sys/socket.h>/* UNIX下,套接字接口包含文件*/#include <netinet/in.h>/* Internet地址簇*/#include <arpa/inet.h>/* Internet定义*/#include <netdb.h> /* 网络数据库操作*/#include <stdio.h> /* 标准输入输出*/#include <string.h>  /* 字符串处理*/#define PROTOPORT  5188  /*缺省协议端口号*/extern int errno;char localhost = “localhost”;  /*缺省主机名*/ main(argc,argv)int argc;char *argv[];{struct  hostent  *ptrh;          /* 指向主机列表中一个条目的指针 */struct  sockaddr_in  servaddr;   /* 存放服务器端网络地址的结构 */intsockfd;                   /* 客户端的套接字描述符 */intport;                     /* 服务器端套接字协议端口号*/char*  host;                  /* 服务器主机名指针 */int  n;                       /* 读取的字符数 */char  buf[1000] ;              /* 缓冲区,接收服务器发来的数据 */ memset((char*)& servaddr,0,sizeof(servaddr));    /* 清空sockaddr结构 */servaddr.sin_family = AF_INET;     /* 设置为因特网协议族 */ /* 检查命令行参数,如果有,就抽取端口号。否则使用内定的缺省值*/if (argc>2){                port = atoi(argv[2]);   /* 如果指定了协议端口,就转换成整数 */}else {      port = PROTOPORT;   /* 否则,使用缺省端口号 */}if (port>0)  /* 如果端口号是合法的数值,就将它装入网络地址结构 */ servaddr.sin_port = htons((u_short)port);else{                       /* 否则,打印错误信息并退出*/fprintf(stderr,”bad port number %s\n”,argv[2]);exit(1);} /* 检查主机参数并指定主机名 */if(argc>1){host = argv[1];   /* 如果指定了主机名参数,就使用它 */}else{host = localhost;  /* 否则,使用缺省值 */} /* 将主机名转换成相应的IP地址并复制到servaddr 结构中 *//* 从服务器主机名得到相应的IP地址 */ptrh = gethostbyname( host );   if  ( (char *)ptrh == null ) {    /* 检查主机名的有效性,无效则退出 */     fprintf( stderr, ”invalid host: %s\n”, host );     exit(1);}memcpy(&servaddr.sin_addr, ptrh->h_addr, ptrh->h_length ); 
/* 创建一个套接字*/sockfd = SOCKET(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {     fprintf(stderr, ”socket creation failed\n” );     exit(1);}/* 请求连接到服务器 */if (connect( sockfd, (struct sockaddr *)& servaddr,     sizeof(servaddr)) < 0) {     fprintf( stderr,”connect failed\n” );     /*拒绝连接,报错退出 */exit(1);} 
/* 从套接字反复读数据,并输出到用户屏幕上 */n = recv(sockfd , buf, sizeof( buf ), 0 );while ( n > 0) {    write(1,buf, n);    n = recv( sockfd , buf, sizeof( buf ), 0 );} /* 关闭套接字*/closesocket( sockfd ); /* 终止客户程序*/exit(0);} 

参考:http://blog.csdn.net/jnu_simba/article/details/12371127 《浅谈原始套接字》

 
原创粉丝点击