linux网络编程的预备知识

来源:互联网 发布:scboy的淘宝店 编辑:程序博客网 时间:2024/05/16 08:57

struct sockaddr结构:

先从最简单的结构谈起
struct sockaddr {
       unsigned short sa_family; /* 地址族, AF_xxx */
       char sa_data[14]; /* 14字节的协议地址*/
   };
   上面那个结构体总共占了16个字节,这个字符结构用到哪的呢?
#include<sys/socket.h>int bind(int sockfd, struct sockaddr* addr, socklen_t addrlen)
还有其他的譬如recefrom、sendto,哪怕是内核中最底层的这些函数,也用到了这个数据结构。
无责任猜测:这样直接就一个char数组,其实就是开辟了大小为14字节的一个栈,其首地址为sa_data,系统对地址的操作总是得心应手的。

而猿类在写程序的时候,要是直接面对着这么一个数据结构,一个个数组元素都要了解清楚,再进行操作的话,很烦很二逼。于是便有了下面这个数据结构:
struct sockaddr_in {       short int sin_family; /* 地址族 */       unsigned short int sin_port; /* 端口号 */       struct in_addr sin_addr; /* Internet地址 */       unsigned char sin_zero[8]; /* 与struct sockaddr一样的长度 */};
这个数据结构的话,相应的信息就很明了了。端口,ip地址一目了然,而且最关键的是,这个数据结构也是16个字节。
所以一般操作都是用sockadd_in进行赋值,再将sockaddr_in的地址强制转换成sockaddr。这样程序猿也舒服了,机器执行起来也舒服了。
for:
struct sockaddr_in servaddr,cliaddr;bzero(&servaddr,0);servaddr.sin_family = AF_INET;servaddr.sin_port = htons(PORT);servaddr.sin_addr.s_addr = htonl(INADDR_ANY);flag = bind(fd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr));

网络字节转换:

大致转自http://www.cnblogs.com/jacktu/archive/2008/11/24/1339789.html
tcp/ip中的网络字节,是大端字节。所以不管你机器是什么类型的,都必须将发送的数据转换成大端字节。所以什么端口号啊,地址啊之类的,存放入地址之前,最好都进行数据转换。
为了进行转换 bsd socket提供了转换的函数 有下面四个
htons 把unsigned short类型从主机序转换到网络序
htonl 把unsigned long类型从主机序转换到网络序
ntohs 把unsigned short类型从网络序转换到主机序
ntohl 把unsigned long类型从网络序转换到主机序

在使用little endian的系统中 这些函数会把字节序进行转换
在使用big endian类型的系统中 这些函数会定义成空宏
ps:
最常见的有两种
1. Little endian(小端):将低序字节存储在起始地址
2. Big endian(大端):将高序字节存储在起始地址

LE little-endian 
最符合人的思维的字节序 
地址低位存储值的低位
地址高位存储值的高位 

怎么讲是最符合人的思维的字节序,是因为从人的第一观感来说低位值小,就应该放在内存地址小的地方,也即内存地址低位 
反之,高位值就应该放在内存地址大的地方,也即内存地址高位 

BE big-endian 
最直观的字节序 
地址低位存储值的高位 
地址高位存储值的低位 

为什么说直观,不要考虑对应关系只需要把内存地址从左到右按照由低到高的顺序写出
把值按照通常的高位到低位的顺序写出 
两者对照,一个字节一个字节的填充进去 

例子:在内存中双字0x01020304(DWORD)的存储方式 
内存地址 
     4000 4001 4002 4003 
LE  04    03       02    01
BE  01    02      03    04 

 ip地址很直观的表现形式为xxx.xxx.xxx.xxx,如192.168.1.2.那么在实际程序中,这种数据是很惹人厌的,tcp/ip报文中也将ip地址规定为4个字节.
 所以需要将这种字符串类型的数据转换成4个字节的整数。这就需要调用inet_pton,将ip类型的数据转换成网络字节。反之可调用inet_ntop.
for:
if(inet_pton(AF_INET, "172.31.18.207", &servaddr.sin_addr) <= 0){printf("is not a valid IPaddress/n");return 0;}
上面的代码servaddr.sin_addr赋值为172.31.18.207相对应的网络字节。

select函数

int select (int maxfdp1, fd_set *readset, fd_set *writeset,  \
 fd_set *exceptset, const struct timeval * timeout); 

先要清零初始化
        FD_ZERO (&readfds);  
        FD_ZERO (&writefds);
再绑定套接字  
FD_SET (fd1, &readfds); 
FD_SET (fd1, &writefds); 
判断是否可读:
  FD_ISSET (fd1, &readfds);//检测是否fd1变成可读的了 


第一个参数为最大描述的套接字范围即(sockfd+1)
第二个参数为文件描述读的变化(rcv函数),当有数据接收的时候,返回大于0的值,
timeout的时候返回0,错误的时候返回负数
第二个参数为文件描述写的变化
第三个参数为异常错误
第四个参数为timeout的时间,若为NULL,则若没有中断触发,一直处于阻塞状态
                                                   若为0,则不阻塞
                                                 若为大于0 的值,则为timeout时间。

select()函数与Linux驱动程序的关系
  当用户调用select系统调用时,select系统调用会先调用poll_initwait(&table);,然后调用驱动程序中 struct file_operations下的fop->poll函数,在这个函数里应该调用poll_wait(),将current加到某个等待队列(这里调用poll_wait()),并检查是否有效,如果无效就调用schedule_timeout();去睡眠。事件发生后,schedule_timeout()回来,,调用fop->poll();,检查到可以运行,就调用poll_freewait(&table);从而完成select系统调用。重要的是fop->poll()里面要检查是否就绪,如果是,要返回相应标志。


原创粉丝点击