Record and Summarize

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

首先引入常用的几个头文件:

#include<errno.h>         // location:/usr/include/asm-generic/errno.h
#include<sys/types.h>     // location: /usr/include/netinet/types.h
#include<sys/socket.h>    // location:usr/include/i386-linux-gnu/sys/socket.h
#include<netinet/in.h>    // location:/usr/include/netinet/in.h
#include <arpa/inet.h>    // location:/usr/include/arpa/inet.h
#include <netdb.h>
#include <unistd.h>       // 只关注与网络相关的函数

其次引入常用的结构体:

//结构体1 <sys/socket.h>
struct sockaddr {    
   unsigned short sa_family; /* 地址家族, AF_xxx */ 
   char sa_data[14]; /*14字节协议地址*/ 
   }; 
//说明:这个结构体是存储套接字信息的最基本的结构体,但是直接使用它是很困难的。sa_family可以是任意类型,但一般是AF_INET
//结构体2  <netinet/in.h>
struct sockaddr_in { 
   short int sin_family; /* 通信类型 */ 
   unsigned short int sin_port; /* 端口,网络字节序 */ 
   struct in_addr sin_addr; /* Internet 地址 ,网络字节序*/ 
   unsigned char sin_zero[8]; /* 为了与sockaddr结构的长度相同而被加入*/ 
   }; 

struct sockaddr_in6 {
      sa_family_t sin6_family;         /* AF_INET6 */
      in_port_t sin6_port;               /* transport layer port # */
      uint32_t sin6_flowinfo;           /* IPv6 traffic class & flow info */
      struct in6_addr sin6_addr;    /* IPv6 address */
      uint32_t sin6_scope_id;        /* set of interfaces for a scope */
};
// 注意:IPv6结构体 struct sockaddr_in6,同样可以与struct sockaddr互相转换

  说明:sockaddr和sockaddr_in是并列结构,但用sockaddr_in可以轻松的处理一些基本元素。因此,所有以sockaddr指针为参数的接口都可以用sockaddr_in的指针通过显示转换进行替换。同时sockaddr_in在初始化时应该使用函数 bzero() 或 memset() 来全部置零。总结,除非遇到特殊情况,否则不需要定义sockaddr类型的变量。

//补充结构体:<netinet/in.h>
struct in_addr { 
   unsigned long s_addr; 
   }; 

struct in6_addr {
      uint8_t s6_addr[16];            /* IPv6 address */
};
//结构体3  <netdb.h>
struct hostent {
   char *h_name;  //地址的正式名称
   char **h_aliases; // 空字节-地址的预备名称的指针
   int h_addrtype;  //地址类型; 通常是AF_INET。
   int h_length;   // 地址的比特长度
   char **h_addr_list; //网络字节顺序的ip地址列表
      #define h_addr h_addr_list[0] //为了与老版本的兼容
   }; //存储一台主机的信息

注意:要充分利用 errno.h获取错误信息(在<errno.h>中有介绍,在<stdio.h>有用法)。

一、转换   //<arpa/inet.h>

1、网络字节序和主机字节序相互转换
  你能够转换两种类型: short (两个字节)和 long (四个字节)。
  htons()--"Host to Network Short"
  htonl()--"Host to Network Long"
  ntohs()--"Network to Host Short"
  ntohl()--"Network to Host Long"
  对于以上四个函数,每一个函数的参数的类型和返回值的类型都是相同的,即unsigned short 或 unsigned long。且出现错误均返回-1.

2、点分十进制与数值之间的转换
(1)int inet_pton(int af, const char *src, void *dst);
  这个函数转换网络地址字符串到网络地址,第一个参数af是地址族,第二个参数*src是来源地址,第三个参数* dst接收转换后的数据。

  inet_pton 是inet_addr的扩展,支持的多地址族有下列:

  • af = AF_INET,src为指向字符型的地址,即点分十进制地址,函数将该地址转换为in_addr的结构体,并复制在*dst中。
  • af = AF_INET6,src为指向IPV6的地址,函数将该地址转换为in6_addr的结构体,并复制在*dst中。

  如果函数出错将返回一个负值,并将errno设置为EAFNOSUPPORT,如果参数af指定的地址族和src格式不对,函数将返回0;成功的话返回1

(2)const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);
  这个函数转换网络二进制结构转换到字符串类型的地址,参数的作用和inet_pton相同,只是多了一个参数socklen_t cnt,他是所指向缓存区dst的大小,避免溢出,如果缓存区太小无法存储地址的值,则返回一个空指针,并将errno置为ENOSPC。
  inet_ntop函数成功的话返回字符串的首地址,错误返回NULL;

二、socket基本函数

1、int socket(int domain, int type, int protocol); //<sys/socket.h>
  domain 应该设置成 "AF_INET"
  type 告诉内核 是 SOCK_STREAM 类型还是 SOCK_DGRAM 类型
  protocol 设置为 0
  socket() 只是返回你以后在系统调用中可能用到的 socket 描述符,或 者在错误的时候返回-1。全局变量 errno 中将储存返回的错误值。

2、int bind(int sockfd, struct sockaddr *my_addr, int addrlen); //<sys/socket.h>
  说明:一旦你有一个套接字,你可能要将套接字和机器上的一定的端口关联 起来。(如果你想用listen()来侦听一定端口的数据,这是必要一步。即作为服务器端)如果你只想用 connect(),那么这个步骤没有必要(即作为客户端)。
  sockfd 是调用 socket 返回的文件描述符。my_addr 是指向数据结构 struct sockaddr 的指针,它保存你的地址(即端口和 IP 地址) 信息。 addrlen 设置为 sizeof(struct sockaddr)。 如果函数执行成功,返回值为0,否则为-1。

/******************************可以这么用**************************/
my_addr.sin_port = 0; /* 随机选择一个没有使用的端口 */ 
my_addr.sin_addr.s_addr = INADDR_ANY; /* 使用自己的IP地址 */ 
/********************但严格来说应该这么用**************************/
my_addr.sin_port = htons(0); /* 随机选择一个没有使用的端口 */ 
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);/* 使用自己的IP地址 */ 

3、int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); //<sys/socket.h>
  说明:sockfd 是系统调用 socket() 返回的套接字文件描述符。serv_addr 是 保存着目的地端口和 IP 地址的数据结构 struct sockaddr。addrlen 设置 为 sizeof(struct sockaddr)。错误返回-1


4、int listen(int sockfd, int backlog); //<sys/socket.h>
  说明:sockfd 是调用 socket() 返回的套接字文件描述符。backlog 是在进入队列中允许的连接数目。什么意思呢? 进入的连接是在队列中一直等待直到你接受 (accept() 请看下面的文章)连接。它们的数目限制于队列的允许。 大多数系统的允许数目是20,你也可以设置为5到10。 发生错误的时候返回-1
  listen只是把一个未连接的套接口转变为一个被动的套接口,以便接受外来的连接而已。所以listen用一次就够了,不用放在循环里边。
  listen应该理解为把本地ip和端口设置为监听,而不是监听client连接。
  accept()是监听client连接。

5、int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //<sys/socket.h>
  说明:将从连接请求队列中获得连接信息,创建新的套接字,并返回该套接字的文件描述符。新创建的套接字用于服务器与客户机的通信,而原来的套接字仍然处于监听状态。
  sockfd 是套接字描述符。参数addr所指的结构会被系统填入远程主机的地址数据,参数addrlen为scokaddr的结构长度。这里的addrlen所指向的值,是必须初始化的,而且要初始化为一个大于等于sizeof(struct sockaddr)的值,而accept函数在执行后,会将实际值赋给addrlen所指向的值,故如果期望值小于实际值,就无法获得客户端信息。
  成功返回新套接字描述符;失败返回-1。
  注意,在系统调用 send() 和 recv() 中你应该使用新的套接字描述符 new_fd。

6、send() and recv()函数(适用于流式数据传输)
(1)int send(int sockfd, const void *msg, int len, int flags); //<sys/socket.h>
  sockfd 是你想发送数据的套接字描述符(或者是调用 socket() 或者是 accept() 返回的。)msg 是指向你想发送的数据的指针。len 是数据的长度。 把 flags 设置为 0 就可以了。
  send() 返回实际发送的数据的字节数--它可能小于你要求发送的数 目! 注意,有时候你告诉它要发送一堆数据可是它不能处理成功。它只是 发送它可能发送的数据,然后希望你能够发送其它的数据。记住,如果 send() 返回的数据和 len 不匹配,你就应该发送余下的数据。但是这里也有个好消息:如果你要发送的包很小(小于大约 1K),它可能处理让数据一 次发送完。它在错误的时候返回-1。

(2)int recv(int sockfd, void *buf, int len, unsigned int flags); //<sys/socket.h>
  sockfd 是要读的套接字描述符。buf 是要读的信息的缓冲。len 是缓 冲的最大长度。flags 可以设置为0。
  如果sockfd 的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把sockfd 的接收缓冲中的数据复制到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把sockfd 的接收缓冲中的数据复制完。recv函数仅仅是复制数据,真正的接收数据是协议来完成的),recv函数返回其实际复制的字节数。如果recv在复制时出错,那么它返回SOCKET_ERROR(-1);如果recv函数在等待协议接收数据时网络中断了,那么它返回0。

7、sendto() and recvfrom()函数(适用于数据报式数据传输)
  既然数据报套接字不是连接到远程主机的,那么在我们发送一个包之前需要目标地址。

(1)int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen); //<sys/socket.h>
  除了另外的两个信息外,其余的和函数 send() 是一样 的。 to 是个指向数据结构 struct sockaddr 的指针,它包含了目的地的 IP 地址和端口信息。tolen 可以简单地设置为 sizeof(struct sockaddr)。 和函数 send() 类似,sendto() 返回实际发送的字节数(它也可能小于 你想要发送的字节数!),或者在错误的时候返回 -1。

(2)int recvfrom(int sockfd, void *buf, int len, unsigned int flags,  struct sockaddr *from, int *fromlen); //<sys/socket.h>
  from 是一个指向局部数据结构 struct sockaddr 的指针,它的内容是客户端机器的 IP 地址和端口信息。fromlen 是个 int 型的局部指针,它的初始值为 sizeof(struct sockaddr)。函数调用返回后,fromlen 保存着实际储存在 from 中的地址的长度。recvfrom() 返回收到的字节长度,或者在发生错误后返回 -1。

(3)注意
  如果你用 connect() 连接一个数据报套接字,你可以简单的调 用 send() 和 recv() 来满足你的要求。这个时候依然是数据报套接字,依然使用 UDP,系统套接字接口会为你自动加上目标和源的信息。

8、close()和shutdown()函数
(1)int close(int fd); //<unistd.h>
  返回值:成功返回0,出错返回-1。它将防止套接字上更多的数据的读写。任何在另一端读写套接字的企图都将返回错误信息。

(2)int shutdown(int sockfd, int how); //<sys/socket.h>
  sockfd 是你想要关闭的套接字文件描述复。how 的值是下面的其中之 一:

  •   0 - 不允许接受
  •   1 - 不允许发送
  •   2 - 不允许发送和接受(和 close() 一样)

  shutdown() 成功时返回 0,失败时返回 -1

9、int getpeername(int sockfd, struct sockaddr *addr, int *addrlen); //<sys/socket.h>
  函数 getpeername() 告诉你在连接的流式套接字上谁在另外一边,一般用于服务器端获取客户端的信息。前提显而易见,必须是在连接建立成功之后。
  sockfd 是连接的流式套接字的描述符。addr 是一个指向结构 struct sockaddr (或者是 struct sockaddr_in) 的指针,它保存着连接的另一边的 信息。addrlen 是一个 int 型的指针,它初始化为 sizeof(struct sockaddr)。 函数在错误的时候返回 -1

10、int getsockname(int sockfd, struct sockaddr *addr, int *addrlen); //<sys/socket.h>
  函数 getsockname() 与getpeername()相反,它获取的是与套接字关联的本地协议地址。客户端可以在connect成功返回后,通过getsockname得到分配给此连接的本地IP地址和本地端口号;服务器也可以在bind成功后使用该函数,但没什么意义。
  sockfd 是连接的流式套接字的描述符。addr 是一个指向结构 struct sockaddr (或者是 struct sockaddr_in) 的指针,用它存储得到的本地信息。addrlen 是一个 int 型的指针,它初始化为 sizeof(struct sockaddr)。 函数在错误的时候返回 -1

11、int gethostname(char *hostname, size_t size); //<unistd.h>
  它返回你程序所运行的机器的主机名字。hostname 是一个字符数组指针,它将在函数返回时保存 主机名。size是hostname 数组的字节长度。 函数调用成功时返回 0,失败时返回 -1
  这一系列的系统调用如下,不再详细介绍(注意set的时候可能会涉及权限问题):

  • getdomainname 取域名
  • setdomainname 设置域名
  • gethostid 获取主机标识号
  • sethostid 设置主机标识号
  • gethostname 获取本主机名称
  • sethostname 设置主机名称

12、struct hostent *gethostbyname(const char * hostname); //<netdb.h>
  获取给定主机名的主机信息。
  gethostbyname() 成功时返回一个指向结构体 hostent 的指针;否则是个空 (NULL) 指针,但是和以前不同,不设置errno,而是设置h_errno 。用过 herror得到错误信息,用法与perror完全相同。

13、int select (int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval * timeout); //<sys/select.h>
  对多路同步I/O进行轮寻。
  Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、accept或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。
  会用到的信息:
(1)struct timeval 结构体

struct timeval{
long tv_sec; // seconds
long tv_usec; // microseconds
}

(2) maxfdp1应为最大描述符的值加1

(3)返回值
  >0:就绪描述字的数目
  -1:出错
  0 :超时
(4)readset writeset exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个条件不感兴趣,就可以把它设为NULL。如果三个指针都为NULL,我们就有了一个比sleep()函数更为精确的定时器(sleep()以秒为最小单位,这个以微妙为单位)。
(5)select() 让你可以同时监视多个套接字。如果你想知道的话,那么它就 会告诉你哪个套接字准备读,哪个又准备写,哪个套接字又发生了例外 (exception)。
(6)对于fd_set类型的数据,可以通过下面的四个宏对它们进行操作:

  • FD_ZERO(fd_set *set) - 清除一个文件描述符集合
  • FD_SET(int fd, fd_set *set) - 添加fd到集合
  • FD_CLR(int fd, fd_set *set) - 从集合中移去fd
  • FD_ISSET(int fd, fd_set *set) - 测试fd是否在集合中

(7)下面是一个select的应用,注意struct timval类型的time,它的位置很关键,不能仅在循环外赋值,否则它的时间会累加,必须每次循环赋一次值,使其每次都从0开始计时。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>
int main()
{
    char buf[1024];
    fd_set readset;
    struct timeval time;
    int state;    
    while(1)
    {
        FD_ZERO(&readset);
        FD_SET(0,&readset);
        time.tv_sec=3;
        time.tv_usec=0;
        state=select(1,&readset,NULL,NULL,&time);
        if(state<0)
            fprintf(stderr,"error:%s\nerror code:%d\n",strerror(errno),errno);
        else if(state==0)
            fprintf(stdout,"time out!\n");
        else
            if(FD_ISSET(0,&readset))
            {
                fgets(buf,1024,stdin);
                fprintf(stdout,"get the input:%s",buf);
            }
    }
    return 0;
}

 

 

posted on 2013-12-30 15:56  zhangjing327  阅读(217)  评论(0编辑  收藏  举报