Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4241007
  • 博文数量: 1148
  • 博客积分: 25453
  • 博客等级: 上将
  • 技术积分: 11949
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-06 21:14
文章分类

全部博文(1148)

文章存档

2012年(15)

2011年(1078)

2010年(58)

分类: 嵌入式

2011-03-24 19:59:46

   MiniWebServer 的详细实现

    在上面分析和设计的基础上,这一节介绍如何具体使用 C 语言来实现 MiniWebServer服务器。

5 监听和建立连接

    按照网络编程的一般做法,我们采用套接字接口 Socket 来监听和建立网络连接。套接字( Socket)起初来源于 UNIX,是加利福尼亚大学 berkeley 分校为 BSD 操作系统( UNIX的一种)开发的网络通信接口,因此它也常被称为 berkeyley 套接字。随着 UNIX 操作系统的广泛使用,Socket 成为了最流行的网络通信程序接口,并被移植到 Window 和 JAVA 中,通常的网络编程都选择 socket 接口作为基础。

    socket 接口本身被设计为可以使用与任何底层的协议,不过目前它的实现一般基于TCP/IP协议。TCP/IP 是互联网上最通用的网络协议 (尽管国际标准化组织规定了一个七层网络模型,但实际当中很少有完整的实现),它的基本模型如下图:




     IP层负责将数据包从一个源节点传送到一个目标节点,节点有一个四字节的数字组成,即IP地址。网关收到数据包并根据该包目标IP来转换发到下一个网关,知道到目的地为止。

    TCP层负责检查传输的数据包的正确性。数据包可能在网络上丢失,TCP负责检查数据包是否丢失或出错,然后请求重发数据包,知道获得正确而完整的数据包为止。而UDP则不对数据包的正确性进行检查。如果要保证数据的正确性,应用层应负责对此加以检查。

    平时我们所用的 Socket接口正是在上述模型的基础上实现的。应用 Socket 接口编程时需要在服务器监听连接,而在客户端请求连接,建立连接的基本原理为:
  1. 服务器端:创建套接口-绑定套接口-设置套接口为监听模式,进入被动接受连接请求状态-接受请求,建立连接-度/写数据-终止连接
  2. 客户端:  创建套接口-与远程服务程序连接-读/写数据-终止连接
     如果转换成 socket 接口,上面的过程可以完成:
  1. 服务器端:socket-->bind-->listen-->accept
  2. 客服端 : socket-->connect
 
 
   
     下面来简单介绍在代码中需要用到的 socket 接口函数。

1.创建头接口 socket 函数
  1. int socket(int domain,int type,int protocol)

domain: 参数指定协议族,如AF_INET(IPv4协议),AF_INET6(IPv6协议) ,AF_LOCAL(UNIX域协议)
type  :套接口类型,SOCK_STREAM:字节流套接口,SOCK_DGRAM数据报套接口 和SOCK_RAW 原始套接口。
        其中,SOCK_STREAM,表示我们用的是TCP/IP协议,这样会提供按顺序的,可靠,双向,面向连接的比特流
        SOCK_DGRAM,表示我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信。
potocol:参数指定协议,也可以取0

socket 函数成功时返回一个套接口文件描述符



2.绑定头接口 bind 函数
  1. int bind(int sockfd,struct sockaddr *my_addr,int addrlen)

sockfd:   表示由socket调用返回的文件描述符
addrlen:  就是 sizeof(sockaddr),套接字地址结构的长度
my_addr:  是一个指向包含有本机 IP 地址及端口号等信息的 sockaddr 类型的指针

sockaddr的定义如下:
  1. struct sockaddr{
  2.     unsigned short as_family;   //协议族
  3.     char sa_data[14];  14字节协议地址
  4. }

as_family:一般为AF_INET,代表 Internet(TCP/IP)地址族;
sa _data : 包含该socket的 IP地址和端口号

    另外还有一种用于 Internet的套接字地址结构类型;
  1. struct sockaddr_in{
  2.       short int sin_family   //地址族
  3.       unsigned short int sin_port;  端口号
  4.       struct in_addr sin_addr;      IP地址
  5.       unsigned char sin_zero[8];    填充0,以保持与 struct sockaddr 同样大小
  6. }

   其中,sockaddr_in 的后缀_in表示internet。sin_zero用来将 sockaddr_in结构填充到与 struct sockaddr 同样的长度,可以用 bzero()或 memset() 函数将其置为零。

   指向 sockadd_in 的指针可以指向 sockaddr 的指针可以相互转换,这意味着如果一个函数所需参数类型是 sockaddr 时,你可以在函数调用的时候将一个指向 sockaddr_in 的指针转换为指向 sockaddr 的指针;或者相反。


   bind 函数返回值表明操作成功或失败,成功返回0,出错返回-1.


3.建立连接接口 connect 函数
      面向连接的客户端程序使用 connect 函数来配置socket 并与远端服务器建立一个TCP连接,
  1. int connect(int sockfd,struct sockadr * serv_addr,int addrlen)
sockfd:   是socket函数返回的socket描述符,
serv_addr  serv_addr是包含远端主机IP地址和端口号的指针
addrlen    是远端地址结构的长度

connect 函数在出现错误时返回-1,并且设置errno 为相应的错误码。

编写客户端无需调用 bind


4.监听接口 listen 函数
  1. int  listen(int sockfd,int backlog)
sockfd: 是bind后的描述符
backlog:是为经过处理的连接请求队列可以容纳的最大数目。即每一个连入请求都要进入一个连入请求队列,等待listen的程序调用accept函数来接受这个连接。当系统还没有调用accept函数的时候,如果有很多连接,那么本地能够等待的最大数目就是backlog的数值

成功返回 0,出错返回-1


5.接受请求接口 accept函数
     由TCP服务器端调用,用来接受从客户端来的请求,如果没有请求,则该函数自行阻塞i,直到有请求为止。
  1. int accept(int sockfd,struct sockaddr *sliaddr,socklen_t *addrlen)

sockfd:     是被监听的socket描述符,
cliaddr:    是一个指向sockaddr_in 变量的指针,该变量用来存放提出连接请求客户端信息
addrlen:    为一个指向值为 sizeof(struct socaddr_in)的整形指针变量

  accept 成功时返回最后的服务器端的文件描述符,出现错误时,accept函数返回-1并置相应的errno值


在apue 上这么写着:
    函数accept所返回的文件描述符是套接字描述符,该描述符连接到调用 connect的客户端。这个新的套接字描述符和原始套接字(sockfd)具有相同的套接字类型和地址族。传给accept的原始套接字灭有关联到这个连接,而是继续保持可用状态并接受其他连接请求。




6.关闭套接字接口 close 函数
  1. int close (int sockfd)

7.字节转换函数
  
      在网络上存在各种计算机,其字节顺序是不相同的,有大字节序和小字节序之分。网络传输过程中,应该统一使用一种字节序,即网络字节序。由各个发送端发送前将数据转换成网络字节序,而接收端对接收到的字节重新转换成本机字节序。在linux中有专门的字节转换函数。
  1. unsigned long int htonl(unsigned long int hostlong)
  2. unsigned short int htons(unsigned short int hostshort)
  unsigned long int ntohl(unsigned long int netlong)
  unisnged short int ntohs(unsigned short int netshort)

   函数中 字母 h代表host,n代表network,s代表short,l代表long。第一个函数的意义是将本机器上的long数据转化为网络上的long,其他几个函数的含义相似。


8.设置socket描述符的状态
  1. int setsockopt(int s,int level,int optname,const void *optval,cocklen_toplen)
s:         所要设置的socket描述符
level:     所要改变的层数,通常使用SQL_SOCKET 代表socket的存取层
otname:    为所要设置的选项
optval:    代表要设置的值
optlen:    说明该值的长度

看看我们怎么实现 MiniWebServer 服务器中监听和建立连接部分的代码


完整代码链接

  1. ...
  2. typedef struct sockaddr SA;
  3. ...

  4. void handle_req(int fd);
  5. int parse_uri(char *uri, char *filename, char *cgiargs);
  6. void serve_static(int fd, char *filename, int filesize);
  7. void get_filetype(char *filename, char *filetype);
  8. void exec_cgi(int fd, char *method, int content_length, char *filename, char *cgiargs);
  9. void error_msg(int fd, char *cause, char *errnum,
  10.          char *shortmsg, char *longmsg);

  11. int main(int argc, char **argv)
  12. {
  13.     /* Check command line args */
  14.     if (argc != 2) {
  15. ################################################
  16.      fprintf格式化输出到文件,这里格式化输出到标准输出
  17.      fprintf(stderr, "usage: %s \n", argv[0]);
  18.         exit(1);
  19.     }

  20.     (, SIG_IGN);    ##### SIGCHLD子进程状态改变,SIG_IGN 表示忽略信号
  21.     signal(SIGPIPE, SIG_IGN);

  22.     int port = atoi(argv[1]);   #获取端口号  8000

  23.     int listen_fd;
  24.     struct sockaddr_in client_addr;
  25.     struct sockaddr_in server_addr;
############# AF_INET  IPv4 ,字节流接口
  1.     if((listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
  2.         perror("Unable to obtain network");
  3.         exit(1);
  4.     }
  5. ########################
  6. ######    配置服务器,使它能立即终止或重启
  7. ######    SO_REUSEADDR:SO_REUSEADDR是让端口释放后立即就可以被再次使用
  8.     int optval = 1;
  9.     if((setsockopt(listen_fd, SOL_SOCKET, , (void *)&optval,
  10.            sizeof(int))) < 0) {
  11.         perror("setsockopt failed");
  12.         exit(1);
  13.     }

  14.     server_addr.sin_family = AF_INET;   ###internet TCP/IP 地址族
  15.     server_addr.sin_port = htons(port);      # 主机到 网络
  16.     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);   ##define INADDR_ANY ((in_addr_t) 0x00000000)
  17.     
  18.     if(bind(listen_fd, (SA *)&server_addr,
  19.         sizeof(server_addr)) < 0) {
  20.         perror("Unable to bind socket");
  21.         exit(1);
  22.     }

  23.     if(listen(listen_fd, 1024) < 0) {
  24.         perror("Unable to listen");
  25.         exit(1);
  26.     }
  27.     
  28. #ifdef DEBUG
  29.     printf("listening... s=%d \n", listen_fd);
  30. #endif
  31.     
  32.     int conn_fd, len;
  33.     while (1) {
  34.         len = sizeof(client_addr);
  35.         if((conn_fd = (listen_fd, (SA *)&client_addr, &len)) < 0) {
  36.             exit(1);
  37.             close(listen_fd);
  38.         }
  39. #ifdef DEBUG
  40.         printf("connected. fd=%d \n", conn_fd);
  41. #endif
  42.         handle_req(conn_fd);  ######处理HTTP请求的处理
  43.         close(conn_fd);
  44.     }
  45. }











   
阅读(2538) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~