zoukankan      html  css  js  c++  java
  • Beej网络socket编程指南

    bind()函数
      一旦你有一个套接字,你可能要将套接字和机器上的一定的端口关联 起来。(如果你想用listen()来侦听一定端口的数据,这是必要一步--MUD 告 诉你说用命令 "telnet x.y.z 6969"。)如果你只想用 connect(),那么这个步 骤没有必要。但是无论如何,请继续读下去。
    这里是系统调用 bind() 的大概:
    #include <sys/types.h>
    #include <sys/socket.h>
    int bind(int sockfd, struct sockaddr *my_addr, int addrlen); 
    sockfd 是调用 socket 返回的文件描述符。my_addr 是指向数据结构 struct sockaddr 的指针,它保存你的地址(即端口和 IP 地址) 信息。 addrlen 设置为 sizeof(struct sockaddr)。 
    简单得很不是吗? 再看看例子: 
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #define MYPORT 3490 
    main()
       {
       int sockfd;
       struct sockaddr_in my_addr;
    sockfd = socket(AF_INET, SOCK_STREAM, 0); /*需要错误检查 */
    my_addr.sin_family = AF_INET; /* host byte order */ 
       my_addr.sin_port = htons(MYPORT); /* short, network byte order */ 
       my_addr.sin_addr.s_addr = inet_addr("132.241.5.10"); 
       bzero(&(my_addr.sin_zero),; /* zero the rest of the struct */ 
    /* don't forget your error checking for bind(): */ 
       bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)); 
       . 
       . 
       . 
    这里也有要注意的几件事情。my_addr.sin_port 是网络字节顺序, my_addr.sin_addr.s_addr 也是的。另外要注意到的事情是因系统的不同, 包含的头文件也不尽相同,请查阅本地的 man 帮助文件。
    在 bind() 主题中最后要说的话是,在处理自己的 IP 地址和/或端口的 时候,有些工作是可以自动处理的。
    my_addr.sin_port = 0; /* 随机选择一个没有使用的端口 */ 
      my_addr.sin_addr.s_addr = INADDR_ANY; /* 使用自己的IP地址 */ 
    通过将0赋给 my_addr.sin_port,你告诉 bind() 自己选择合适的端 口。同样,将 my_addr.sin_addr.s_addr 设置为 INADDR_ANY,你告诉 它自动填上它所运行的机器的 IP 地址。
    如果你一向小心谨慎,那么你可能注意到我没有将 INADDR_ANY 转 换为网络字节顺序!这是因为我知道内部的东西:INADDR_ANY 实际上就 是 0!即使你改变字节的顺序,0依然是0。但是完美主义者说应该处处一 致,INADDR_ANY或许是12呢?你的代码就不能工作了,那么就看下面 的代码:
    my_addr.sin_port = htons(0); /* 随机选择一个没有使用的端口 */ 
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);/* 使用自己的IP地址 */ 
    你或许不相信,上面的代码将可以随便移植。我只是想指出,既然你 所遇到的程序不会都运行使用htonl的INADDR_ANY。
    bind() 在错误的时候依然是返回-1,并且设置全局错误变量errno。 
    在你调用 bind() 的时候,你要小心的另一件事情是:不要采用小于 1024的端口号。所有小于1024的端口号都被系统保留!你可以选择从1024 到65535的端口(如果它们没有被别的程序使用的话)。
    你要注意的另外一件小事是:有时候你根本不需要调用它。如果你使 用 connect() 来和远程机器进行通讯,你不需要关心你的本地端口号(就象 你在使用 telnet 的时候),你只要简单的调用 connect() 就可以了,它会检 查套接字是否绑定端口,如果没有,它会自己绑定一个没有使用的本地端 口。

    gethostname()函数
      甚至比 getpeername() 还简单的函数是 gethostname()。它返回你程 序所运行的机器的主机名字。然后你可以使用 gethostbyname() 以获得你 的机器的 IP 地址。
      下面是定义:
      #include <unistd.h>
    int gethostname(char *hostname, size_t size);
    参数很简单:hostname 是一个字符数组指针,它将在函数返回时保存
    主机名。size是hostname 数组的字节长度。
    函数调用成功时返回 0,失败时返回 -1,并设置 errno。 

    域名服务(DNS)
      如果你不知道 DNS 的意思,那么我告诉你,它代表域名服务(Domain Name Service)。它主要的功能是:你给它一个容易记忆的某站点的地址, 它给你 IP 地址(然后你就可以使用 bind(), connect(), sendto() 或者其它 函数) 。当一个人输入:
       $ telnet whitehouse.gov 
    telnet 能知道它将连接 (connect()) 到 "198.137.240.100"。 
    但是这是如何工作的呢? 你可以调用函数 gethostbyname(): 
    #include <netdb.h>
      struct hostent *gethostbyname(const char *name); 
    很明白的是,它返回一个指向 struct hostent 的指针。这个数据结构 是这样的:
       struct hostent {
       char *h_name;
       char **h_aliases;
       int h_addrtype;
       int h_length;
       char **h_addr_list;
       };
       #define h_addr h_addr_list[0] 
    这里是这个数据结构的详细资料: 
    struct hostent: 
      h_name – 地址的正式名称。
      h_aliases – 空字节-地址的预备名称的指针。
      h_addrtype –地址类型; 通常是AF_INET。 
      h_length – 地址的比特长度。
      h_addr_list – 零字节-主机网络地址指针。网络字节顺序。
      h_addr - h_addr_list中的第一地址。
    gethostbyname() 成功时返回一个指向结构体 hostent 的指针,或者 是个空 (NULL) 指针。(但是和以前不同,不设置errno,h_errno 设置错 误信息。请看下面的 herror()。) 
    但是如何使用呢? 有时候(我们可以从电脑手册中发现),向读者灌输 信息是不够的。这个函数可不象它看上去那么难用。
    这里是个例子:

    #include <stdio.h>
      #include <stdlib.h>
      #include <errno.h>
      #include <netdb.h>
      #include <sys/types.h>
      #include <netinet/in.h>
    int main(int argc, char *argv[])
       {
       struct hostent *h;
    if (argc != 2) { /* 检查命令行 */
       fprintf(stderr,"usage: getip address
    ");
       exit(1);
       }
    if ((h=gethostbyname(argv[1])) == NULL) { /* 取得地址信息 */
       herror("gethostbyname");
       exit(1);
       }
    printf("Host name : %s
    ", h->h_name);
      printf("IP Address : %s
    ",inet_ntoa(*((struct in_addr *)h->h_addr)));
    return 0;
       }

    在使用 gethostbyname() 的时候,你不能用 perror() 打印错误信息 (因为 errno 没有使用),你应该调用 herror()。
    相当简单,你只是传递一个保存机器名的字符串(例如 "whitehouse.gov") 给 gethostbyname(),然后从返回的数据结构 struct hostent 中获取信息。 
    唯一也许让人不解的是输出 IP 地址信息。h->h_addr 是一个 char *, 但是 inet_ntoa() 需要的是 struct in_addr。因此,我转换 h->h_addr 成 struct in_addr *,然后得到数据。

    客户-服务器背景知识
      这里是个客户--服务器的世界。在网络上的所有东西都是在处理客户进 程和服务器进程的交谈。举个telnet 的例子。当你用 telnet (客户)通过23 号端口登陆到主机,主机上运行的一个程序(一般叫 telnetd,服务器)激活。 它处理这个连接,显示登陆界面,等等。


    图2:客户机和服务器的关系
    图 2 说明了客户和服务器之间的信息交换。 
    注意,客户--服务器之间可以使用SOCK_STREAM、SOCK_DGRAM 或者其它(只要它们采用相同的)。一些很好的客户--服务器的例子有 telnet/telnetd、 ftp/ftpd 和 bootp/bootpd。每次你使用 ftp 的时候,在远 端都有一个 ftpd 为你服务。 
    一般,在服务端只有一个服务器,它采用 fork() 来处理多个客户的连 接。基本的程序是:服务器等待一个连接,接受 (accept()) 连接,然后 fork() 一个子进程处理它。这是下一章我们的例子中会讲到的。 

    简单的服务器
      这个服务器所做的全部工作是在流式连接上发送字符串 "Hello, World! "。你要测试这个程序的话,可以在一台机器上运行该程序,然后 在另外一机器上登陆: 
       $ telnet remotehostname 3490 
    remotehostname 是该程序运行的机器的名字。 
    服务器代码:

    /*
    ** server.c -- a stream socket server demo
    */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <arpa/inet.h>
    #include <sys/wait.h>
    #include <signal.h>
    
    #define PORT "3490"  // the port users will be connecting to
    
    #define BACKLOG 10     // how many pending connections queue will hold
    
    void sigchld_handler(int s)
    {
        while(waitpid(-1, NULL, WNOHANG) > 0);
    }
    
    // get sockaddr, IPv4 or IPv6:
    void *get_in_addr(struct sockaddr *sa)
    {
        if (sa->sa_family == AF_INET) {
            return &(((struct sockaddr_in*)sa)->sin_addr);
        }
    
        return &(((struct sockaddr_in6*)sa)->sin6_addr);
    }
    
    int main(void)
    {
        int sockfd, new_fd;  // listen on sock_fd, new connection on new_fd
        struct addrinfo hints, *servinfo, *p;
        struct sockaddr_storage their_addr; // connector's address information
        socklen_t sin_size;
        struct sigaction sa;
        int yes=1;
        char s[INET6_ADDRSTRLEN];
        int rv;
    
        memset(&hints, 0, sizeof hints);
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_PASSIVE; // use my IP
    
        if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
            fprintf(stderr, "getaddrinfo: %s
    ", gai_strerror(rv));
            return 1;
        }
    
        // loop through all the results and bind to the first we can
        for(p = servinfo; p != NULL; p = p->ai_next) {
            if ((sockfd = socket(p->ai_family, p->ai_socktype,
                    p->ai_protocol)) == -1) {
                perror("server: socket");
                continue;
            }
    
            if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
                    sizeof(int)) == -1) {
                perror("setsockopt");
                exit(1);
            }
    
            if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
                close(sockfd);
                perror("server: bind");
                continue;
            }
    
            break;
        }
    
        if (p == NULL)  {
            fprintf(stderr, "server: failed to bind
    ");
            return 2;
        }
    
        freeaddrinfo(servinfo); // all done with this structure
    
        if (listen(sockfd, BACKLOG) == -1) {
            perror("listen");
            exit(1);
        }
    
        sa.sa_handler = sigchld_handler; // reap all dead processes
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_RESTART;
        if (sigaction(SIGCHLD, &sa, NULL) == -1) {
            perror("sigaction");
            exit(1);
        }
    
        printf("server: waiting for connections...
    ");
    
        while(1) {  // main accept() loop
            sin_size = sizeof their_addr;
            new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
            if (new_fd == -1) {
                perror("accept");
                continue;
            }
    
            inet_ntop(their_addr.ss_family,
                get_in_addr((struct sockaddr *)&their_addr),
                s, sizeof s);
            printf("server: got connection from %s
    ", s);
    
            if (!fork()) { // this is the child process
                close(sockfd); // child doesn't need the listener
                if (send(new_fd, "Hello, world!", 13, 0) == -1)
                    perror("send");
                close(new_fd);
                exit(0);
            }
            close(new_fd);  // parent doesn't need this
        }
    
        return 0;
    }

    或简单版本:http://tsnc.zhongaokao.com/tsnc_wgrj/doc/socket.html

    如果你很挑剔的话,一定不满意我所有的代码都在一个很大的main() 函数中。如果你不喜欢,可以划分得更细点。
    你也可以用我们下一章中的程序得到服务器端发送的字符串。 

    简单的客户程序 
      这个程序比服务器还简单。这个程序的所有工作是通过 3490 端口连 接到命令行中指定的主机,然后得到服务器发送的字符串。 
    客户代码: 

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <sys/wait.h>
    
    #define PORT 3490 
    #define MAXDATASIZE 100 //每次可以接收的最大字节
    
    void main(int argc,char *argv)
    {
        int sockfd,numbytes;
        char buf[MAXDATASIZE];
        struct sockaddr_in their_addr;//connector's addr info
    
        if(argc!=2)
        {
            printf("usage:./cient <ipaddr>
    ");
            exit(-1);
        }
        if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
        {
            perror("socket");
            exit(1);
        }
        memset(&their_addr,0,sizeof(their_addr));
        their_addr.sin_family=AF_INET;
        their_addr.sin_port=htons(PORT);
        
        inet_aton(argv[1],&their_addr.sin_addr);
    
        
        if(connect(sockfd,(struct sockaddr*)&their_addr,sizeof(struct sockaddr))==-1)
        {
            perror("connect"); exit(1);
        }
        if((numbytes=recv(sockfd,buf,MAXDATASIZE,0))==-1)
        {
            perror("recv");
            exit(1);
        }
        buf[numbytes]='';
        printf("Received:%s",buf);
        close(sockfd);
    }

    运行这个程序出错:段错误,核心已存储。

    6.3. Datagram Sockets

    We've already covered the basics of UDP datagram sockets with our discussion of sendto() and recvfrom(), above, so I'll just present a couple of sample programs: talker.cand listener.c.

    listener sits on a machine waiting for an incoming packet on port 4950. talker sends a packet to that port, on the specified machine, that contains whatever the user enters on the command line.

    Here is the source for listener.c:

  • 相关阅读:
    正则入门小随
    用栈求简单算术表达式的值
    数据结构复习
    数据结构笔记(第九章)
    数据结构笔记(第八章)
    Java第三阶段学习(十四、JSP动态页面、EL表达式、JSTL标签库)
    Java第三阶段学习(十三、会话技术、Cookie技术与Session技术)
    Java第三阶段学习(十二、HttpServletRequest与HttpServletResponse)
    Java第三阶段学习(十一、Servlet基础、servlet中的方法、servlet的配置、ServletContext对象)
    Java第三阶段学习(十、XML学习)
  • 原文地址:https://www.cnblogs.com/youxin/p/3801263.html
Copyright © 2011-2022 走看看