using Socket in C++

先从底层说起,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;   /* 存放服务器端网络地址的结构 */
int	sockfd;                   /* 客户端的套接字描述符 */
int	port;                     /* 服务器端套接字协议端口号*/
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 《浅谈原始套接字》





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值