当前位置:Linux教程 - Linux - 绿色兵团技术指南(二)

绿色兵团技术指南(二)



        

    用C语言编写Socket程序---入门篇

    本文的目的在于为初学者提供一个快速的入门指导,用来迅速熟悉用C语言来编写Internet网络应用程序。本文假设读者已经具备了C语言的基本知识和语法,并且读者有使用Uinx/Linux的经验。尽管Uinx/Linux的Socket编程与在Windows下的有一些不同的地方,但是在此我并不想展开。另外,本文所有的程序都在Red Hat 5.2下编译通过,并且在glibc 2.0.7和libc 5.3.12两种环境下都没有问题。现在就开始我们的教程吧:)。

    对一个程序员而言,sockets和底层的文件描述符非常类似(可以在sockets里使用read()和write()函数),尽管建立一个socket比打开,读取和写入一个文件更为麻烦,但这是由于网络连接比单纯的本地硬盘的读写复杂的多所造成的。

    通常,sockets用来实现客户机/服务器对。服务器的任务是监听某个特定的端口,当接收到客户端的服务请求时完成相应的服务;客户机的任务是请求服务器完成事先设定好的服务。

    作为入门级的文章,我们在这里不会使用所有的socket类型和功能,但是我们会向读者提供足够的信息。现在,就让我们开始吧。

    =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
    建立一个socket:socket()

    你所要学的socket编程的第一件事就是用socket()建立一个socket:

    - -------

    #include
    #include

    int socket(int af, int type, int protocol)

    - ------

    \int af\代表地址族或者称为socket所代表的域,通常有两个选项:
    AF_UNIX - 只在单机上使用。
    AF_INET - 可以在单机或其他使用DARPA协议(UDP/TCP/IP)的异种机通信。

    \int type\代表你所使用的连接类型,通常也有两种情况:
    SOCK_STREAM - 用来建立面向连接的sockets,可以进行可靠无误的的数据传输
    SOCK_DGRAM - 用来建立没有连接的sockets,不能保证数据传输的可靠性。

    在本文中,我们着重使用AF_INET地址族和SOCK_STREAM连接类型。

    \int protocol\通常设定为0。这样的目的是使系统选择默认的由协议族和连接类型所确定的协议。

    这个函数的返回值是一个文件描述句柄,如果在此期间发生错误则返回-1并且设定了相应的errno。

    - ------

    #include
    #include

    int sockfd /* soon to be socket file descriptor */

    sockfd = socket(AF_INET, SOCK_STREAM, 0)
    /* error checking here */

    - ------

    如果执行成功,我们就拥有了一个socket的文件句柄,通过这个句柄就可以访问Internet了。

    =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
    名字绑定socket: bind()

    下一步要完成的就是名字绑定工作了:

    - ------

    #include
    #include

    int bind(int sockfd, struct sockaddr *name, int namelen)

    - ------

    在这个函数里,sockfd是从socket()调用得到的文件描述句柄。name是一个指向sockaddr类型结构的一个指针。如果地址族被设定为AF_UNIX,这个类型的定义是如下所示:

    - ------

    struct sockaddr {
    u_short sa_family;
    char sa_data[14];
    };

    - ------

    在这个结构种,name.sa_family应当被设定为AF_UNIX。name.sa_data应当包含最长为14个字节的文件名,这个文件名用来分配给socket。namelen给出了文件名的具体长度。

    - ------

    #include
    #include

    struct sockaddr name;
    int sockfd;

    name.sa_family = AF_UNIX;
    strcpy(name.sa_data, \"/tmp/whatever\");

    sockfd = socket(AF_UNIX, SOCK_STREAM, 0)
    /* error checking code here */

    bind(sockfd, &name, strlen(name.sa_data) + sizeof(name.sa_family)
    /* error checking code here */

    - ------

    如果调用成功,则返回值为0,如果调用失败返回值为-1,并设定相应的错误代码errno。

    现在,让我们在使用另一种结构,它是在使用AF_INET地址族的时候使用的。

    - -----

    struct sockaddr_in {
    short int sin_family; /* Address family */
    unsigned short int sin_port; /* Port number */
    struct in_addr sin_addr; /* Internet address */
    unsigned char sin_zero[8]; /* Same size as struct sockaddr */
    };

    - ------

    看起来它比前一个大多了,但要掌握它并不十分困难。

    - ------

    #include
    #include
    #include
    #include

    int sockfd, port = 23;
    struct sockaddr_in my_addr;

    if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
    printf(\"Socket Error, %d\\n\", errno);
    exit(1);
    }

    my_addr.sin_family = AF_INET; /* host byte order */
    my_addr.sin_port = htons(port); /* see man htons for more information */
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* get our address */
    bzero(&(my_addr.sin_zero), 8); /* zero out the rest of the space */

    if((bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1)
    {
    printf(\"Bind Error, %d\\n\", errno);
    close(sockfd);
    exit(1);
    }

    - ------

    现在,如果没有问题的话,我们建立的socket就有一个名字了!相反,如果不成功,它会设定相应的错误代码,并使程序退出。这里需要说明的是,如果你的计算机不想和别人的计算机连接,那么完全没有必要使用bind()。对于端口的绑定,在服务器而言是不合适的,它只应该在客户机上实现。

    =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
    远程连接: connect()

    这是和别的计算机连接所必须的一步。

    - ------

    #include
    #include

    int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

    - ------

    sockfd是我们建立的文件描述句柄,serv_addr是一个sockaddr结构,包含目的的地址和端口号,addrlen 被设定为sockaddr结构的大小。

    - ------

    #include
    #include
    #include

    #define DEST_IP \"132.241.5.10\"
    #define DEST_PORT 23

    main()
    {
    int sockfd;
    struct sockaddr_in dest_addr; /* will hold the destination addr */

    sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */

    dest_addr.sin_family = AF_INET; /* host byte order */
    dest_addr.sin_port = htons(DEST_PORT); /* short, network byte order */
    dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
    bzero(&(dest_addr.sin_zero), 8); /* zero the rest of the struct */

    connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
    /* error checking code here */
    /* more code
    .
    .
    .
    */
    }

    - ------

    同样,connect()在调用返回后,如果返回值为0则表明成功,如果是1则说明有错误,并且同时设定了相应的错误代码。由于我们现在不关心具体和那个端口连接,所以在上面的例程里我们没有调用了bind()函数。

    =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
    监听: listen()

    当我们需要建立一个服务器的时候,我们需要有一种手段来监听输入的请求,而listen()函数正是提供这个功能。

    - ------

    #include
    #include

    int listen(int sockfd, int backlog);

    - ------

    参数backlog是指一次可以监听多少个连接

    它的调用返回结果和上述的几个函数是一样的,这里就不多说了。
    值得一提的是,在这里,我们需要建立一个绑定,用来接收特定端口的服务请求。

    - ------

    socket(); /* to create out socket file descriptor */
    bind(); /* to give our socket a name */
    listen(); /* listen for connection */

    - ------

    =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
    接受连接: accept()

    好了,现在开始实质性的工作了。当有人试图从我们打开的端口登陆进来时我们应该响应他,这个时候就要用到accept()函数了。

    - ------

    #include
    #include

    int accept(int sockfd, void *addr, int *addrlen);

    - ------

    函数调用所需的参数都是我们所熟悉的:)

    - ------

    #include
    #include
    #include

    #define MYPORT 1500 /* the port users will be connecting to */
    #define BACKLOG 5 /* how many pending connections queue will hold */

    main()
    {
    int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */
    struct sockaddr_in my_addr; /* my address information */
    struct sockaddr_in their_addr; /* connector\s address information */
    int sin_size;

    sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */

    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 = INADDR_ANY; /* auto-fill with my IP */
    bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */

    /* did you remember your error checking? */
    bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));

    listen(sockfd, BACKLOG);

    sin_size = sizeof(struct sockaddr_in);
    new_fd = accept(sockfd, &their_addr, &sin_size);

    - ------

    这里我们要注意的是:我们用new_fd来完成所有的接收和发送的操作。如果在只有一个连接的情况下你可以关闭原来的sockfd用来防止更多的输入请求。

    =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
    输入和输入的完成: send() and recv()

    在我们完成了上述的工作后,最后一步就是传输数据了:)。在这里,我们通过send()和recv()来实现。

    - ------

    #include
    #include

    int send(int sockfd, const void *msg, int len, int flags);
    int recv(int sockfd, void *buf, int len, unsigned int flags);

    - ------
    send():
    sockfd - socket file descriptor
    msg - message to send
    len - size of message to send
    flags - read \man send\ for more info, set it to 0 for now :)

    recv():
    sockfd - socket file descriptor
    buf - data to receive
    len - size of buf
    flags - same as flags in send()

    send() 例程:
    - ------

    char *msg = \"Hey there people\";
    int len, send_msg;

    /* code to create(), bind(), listen() and accept() */

    len = strlen(msg);
    bytes_sent = send(sockfd, msg, len, 0);

    - ------
    recv() 例程:
    - ------

    char *buf;
    int len, recv_msg;

    /* code to create(), bind(), listen() and accept() */

    len = strlen(buf);
    recv_msg = recv(sockfd, buf, len, 0);

    - ------

    如果你使用的连接类型是SOCK_DGRAM,那么应该使用sendto()和recvfrom()来实现数据传输。

    =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
    最后一步: close() and shutdown()

    当传输结束时,应当关闭连接。

    - ------

    #include

    /* all you code */

    close(sockfd);

    - ------

    更保险的方法是用shutdown()来关闭连接。

    - ------

    int shutdown(int sockfd, int how)

    - ------

    参数how的选择:
    1 - 不允许接收更多的数据
    2 - 不允许发送更多的数据
    3 - 不允许接收和发送更多的数据(和close()一样)

    一切就是这么简单:)

    =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
    你是谁: getpeerbyname()

    可能你还想知道是谁正在和你连接,那么看下面:

    - ------

    #include

    int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

    - ------

    参数addr是一个指向\struct sockaddr\或者\struct sockaddr_in\的指针。

    如果执行成功,我们就得到了对方的地址了,然后用inet_ntoa()用gethostbyaddr()来得到对方更多的信息。如果还想知道更多的,请参阅RFC 1413。

    =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
    我是谁: gethostname()

    - ------

    #include

    int gethostname(char *hostname, size_t size);

    - ------
    hostname是一个存放主机名字的字符数组

    返回的hostname可以作为gethostbyname()的参数,这样又可以得到自己的IP地址了。

    =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+=
    我的IP是多少?

    现在把我们所学的集中起来,逐步就可以完成一个实用的程序了。还是来研究一下如下的情况:

    $ telnet microsoft.com
    Trying 206.163.24.176 (not the real address but I\m too lazy to try :))

    我们看到,telnet程序所做的第一件事情是域名解析!这个工作是由gethostbyname()完成的。

    - ------

    #include

    struct hostent *gethostbyname(const char *name);

    - ------

    一个特殊的结构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]

    - ------
    这个结构可以被分为:

    h_name - 正式的机器名
    h_aliases - 一个以NULL结尾的字符串,表示机器的别名。
    h_addrtype - 地址所使用的类型,通常是AF_INET。
    h_length - 用字节数表示的地址长度。
    h_addr_list - 一个以0结尾的数组,表示机器的网络地址,以网络的字节顺序排列。
    h_addr - 在h_addr_list里的第一个地址。

    gethostbyname()返回一个指向hostent结构的指针或者是一个NULL指针表示错误(尽管如此,错误代码没有设定!)。

    现在我们就来完成我们的DNS程序:

    - ------

    #include
    #include
    #include
    #include
    #include
    #include

    int main(int argc, char *argv[])
    {
    struct hostent *h;

    if (argc != 2) { /* error checking on the command line */
    fprintf(stderr,\"Usage: getip \\n\");
    exit(1);
    }

    if ((h=gethostbyname(argv[1])) == NULL) { /* get the host info */
    herror(\"gethostbyname\");
    exit(1);
    }

    printf(\"Host name : %s\\n\", h->h_name);
    printf(\"IP Address : %s\\n\",inet_ntoa(*((struct in_addr *)h->h_addr
    发布人:netbull 来自:黑白世界