网络 IPC 套接字socket

APUE书中所有实例源码下载地址:http://www.apuebook.com

apue学习笔记(第十六章 网络IPC:套接字):https://www.cnblogs.com/runnyu/p/4648678.html

一起学 Unix 环境高级编程 (APUE) 之 网络 IPC:套接字:http://www.cnblogs.com/0xcafebabe/p/4478824.html

linux下网络编程:http://blog.csdn.net/freeking101/article/details/71449111


根据 APUE (第十六章 网络IPC:套接字) 整理,如有疑问,直接看 APUE。。。



1. 套接字描述符




socket 函数


使用 man socket 查看具体使用

NAME
       socket - create an endpoint for communication

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);

DESCRIPTION
       socket()  creates  an  endpoint  for communication and returns a file descriptor that refers to that endpoint.
       The file descriptor returned by a successful call will be the lowest-numbered file  descriptor  not  currently
       open for the process.








2. 地址


字节序


IP地址+端口 可以唯一确定一个socket进程。运行在同一计算机上进程之间相互通信时。一般不用考虑字节的顺序(字节序分为大端序和小端序。小端序:低地址存放数据低位,高地址存放数据高位,可以记忆为:小弟弟(低低),小高高。虽说有点黄但好记,大端序和小端序存储相反)。字节序是一个处理器架构特性,用于指示像整数这样的大数据类型的内部字节顺序。不同架构的处理器可能是大端也可能是小端,有些处理器可以配置成大端或者小端。字节序不一样,数据就不能正常处理。

大端格式:低地址存放高位数据,高地址存放低位数据。
小端格式:低地址存放低位数据,高地址存放高位数据。


如图所示,假设要存放的数据是 0x30313233,那么 33 是低位,30 是高位,在大端存储格式中,30 存放在低位,33 存放在高位;而在小端存储格式中,33 存放在低位,30 存放在高位。这个东西有什么作用呢?它其实就是我们使用的网络设备(计算机、平板电脑、智能手机等等)在内存当中存储数据的格式。



处理器字节序和网络序转换函数


既然通讯双方的设备存储数据的格式不同,那么一端发送过去的数据,另一端是无法正确解析的,这该怎么办?没关系,其实系统准备了一组函数可以实现字节序转换,我们可以像使用公式一样使用它们。




地址格式





二进制地址和点分十进制字符串地址转换






地址内部结构信息查询



相关函数(可以使用 man 命令查看函数的详细使用帮助):

get network host entry (得到给定计算机的主机信息)
    gethostbyname, 
    gethostbyaddr, 
    sethostent, 
    gethostent, 
    endhostent, 
    h_errno, 
    herror, 
    hstrerror, 
    gethostbyaddr_r,
    gethostbyname2, 
    gethostbyname2_r, 
    gethostbyname_r, 
    gethostent_r 

get network entry (得到网络和网络号相关信息)
    getnetent, getnetbyname, getnetbyaddr, setnetent, endnetent

get protocol entry (得到协议和协议号相关信息)
    getprotoent, getprotobyname, getprotobynumber, setprotoent, endprotoent 
	
get service entry (得到服务相关信息。服务是由地址的端口号部分表示的)
    getservent, getservbyname, getservbyport, setservent, endservent

network address and service translation	(网络地址和服务映射)
    getaddrinfo, freeaddrinfo, gai_strerror


示例 使用getaddrinfo 打印主机和服务信息

使用到的数据结构

struct addrinfo {  
    int              ai_flags;  
    int              ai_family;  
    int              ai_socktype;  
    int              ai_protocol;  
    size_t           ai_addrlen;  
    struct sockaddr *ai_addr;  
    char            *ai_canonname;  
    struct addrinfo *ai_next;  
};  

struct sockaddr_in {  
  sa_family_t       sin_family; /* Address family       */  
  __be16        sin_port;   /* Port number          */  
  struct in_addr    sin_addr;   /* Internet address     */  
  
  /* Pad to size of `struct sockaddr'. */  
  unsigned char     __pad[__SOCK_SIZE__ - sizeof(short int) -  
            sizeof(unsigned short int) - sizeof(struct in_addr)];  
};  

struct in_addr {  
    __be32  s_addr;  
}; 

hint是用于过滤地址的模版。只选取ai_flags,ai_type,ai_family,ai_protocol,用来指定如何处理地址和名字。
addrgetinfo()取得输入主机和服务的 addrinfo解构,按照hint的过滤作用保存在ailist中,依次打印flag,type等,在通过定义struct sockaddr_in snip来保存AF_INET family 这一簇地址格式的 aip->ai_addr;使用inet_ntop()函数把得到的地址信息解析出来,放到addr指针域,打印出来。

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>

void print_family(struct addrinfo *aip)
{
    printf("family ");
    switch(aip->ai_family)
    {
    case AF_INET:
        printf("inet ");
        break;
    case AF_INET6:
        printf("inet6 ");
        break;
    case AF_UNIX:
        printf("unix ");
        break;
    case AF_UNSPEC:
        printf("unspecified ");
        break;
    default:
        printf("unkown ");
    }
}

void print_type(struct addrinfo *aip)
{
    printf("type ");
    switch(aip->ai_socktype)
    {
    case SOCK_STREAM:
        printf("stream ");
        break;
    case SOCK_DGRAM:
        printf("datagram");
        break;
    case SOCK_SEQPACKET:
        printf("seqpacket ");
        break;
    case SOCK_RAW:
        printf("raw ");
        break;
    default:
        printf("unknown (%d)",aip->ai_socktype);
    }
}

void print_protocol(struct addrinfo *aip)
{
    printf(" protocol ");
    switch(aip->ai_protocol)
    {
    case 0:
        printf("default ");
        break;
    case IPPROTO_TCP:
        printf("TCP ");
        break;
    case IPPROTO_UDP:
        printf("UDP ");
        break;
    case IPPROTO_RAW:
        printf("raw ");
        break;
    default:
        printf("unknown (%d)",aip->ai_protocol);
    }
}

void print_flags(struct addrinfo *aip)
{
    printf("flags");
    if(aip->ai_flags == 0)
        printf("0");
    else
    {
        if(aip->ai_flags & AI_PASSIVE)
            printf(" passive ");
        if(aip->ai_flags & AI_CANONNAME)
            printf(" canon ");
        if(aip->ai_flags & AI_NUMERICHOST)
            printf(" numhost ");
    }
}

int main()
{
    struct addrinfo *ailist,*aip;
    struct addrinfo hint;
    struct sockaddr_in *sinp;
    const char *addr;
    int err;
    char abuf[INET_ADDRSTRLEN];
    hint.ai_flags = AI_CANONNAME;
    hint.ai_family = 0;
    hint.ai_socktype = 0;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if(getaddrinfo("localhost",NULL,&hint,&ailist) != 0)
    {
        printf("getaddrinfo error: %s",gai_strerror(err));
        exit(-1);
    }
    for(aip = ailist;aip != NULL;aip = aip->ai_next)
    {
        print_flags(aip);
        print_family(aip);
        print_type(aip);
        print_protocol(aip);
        printf("\n\thost %s",aip->ai_canonname ?aip->ai_canonname : "-");
        if(aip->ai_family == AF_INET)
        {
            sinp = (struct sockaddr_in *)aip->ai_addr;
            addr = inet_ntop(AF_INET,&sinp->sin_addr,abuf,INET_ADDRSTRLEN);
            printf(" address %s ",addr?addr:"unknown");
            printf(" port %d ",ntohs(sinp->sin_port));
        }
        printf("\n");
    }
    exit(0);
}

下面是程序运行结果:



套接字与地址绑定


bind 函数

可以使用 bind 函数,将地址绑定到一个套接字。(man bind)

NAME
       bind - bind a name to a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);



getsockname 函数和getpeername 函数

可以调用 getsockname 函数来得到绑定到套接字上的地址。(man getsockname):

NAME
       getsockname - get socket name

SYNOPSIS
       #include <sys/socket.h>

       int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

DESCRIPTION
       getsockname()  returns  the  current  address to which the socket sockfd is bound, in the buffer pointed to by
       addr.  The addrlen argument should be initialized to indicate the amount of space (in  bytes)  pointed  to  by
       addr.  On return it contains the actual size of the socket address.

       The  returned  address  is  truncated if the buffer provided is too small; in this case, addrlen will return a
       value greater than was supplied to the call.

RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.


如果套接字已经和对等方连接,可以调用getpeername函数得到对方的地址。(man getpeername)。和 getsockname 用法相同:

NAME
       getpeername - get name of connected peer socket

SYNOPSIS
       #include <sys/socket.h>

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

DESCRIPTION
       getpeername()  returns  the  address  of  the peer connected to the socket sockfd, in the buffer pointed to by
       addr.  The addrlen argument should be initialized to indicate the amount of space  pointed  to  by  addr.   On
       return  it contains the actual size of the name returned (in bytes).  The name is truncated if the buffer pro‐
       vided is too small.

       The returned address is truncated if the buffer provided is too small; in this case,  addrlen  will  return  a
       value greater than was supplied to the call.



3. 建立连接




connect 函数

使用 man connect 查看具体使用方法:

NAME
       connect - initiate a connection on a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);



下面代码显示了一种如何处理瞬时 connect 错误的方法,这在一个负载很重的服务器上很有可能发生。

#include "apue.h"
#include <sys/socket.h>

#define MAXSLEEP 128

int connect_retry(int sockfd, const struct sockaddr *addr, socklen_t alen)
{
    int numsec;

    /*
     * Try to connect with exponential backoff.
     */
    for (numsec = 1; numsec <= MAXSLEEP; numsec <<= 1) 
    {
        if (connect(sockfd, addr, alen) == 0) 
        {
            /*
             * Connection accepted.
             */
            return(0);
        }

        /*
         * Delay before trying again.
         */
        if (numsec <= MAXSLEEP/2)
            sleep(numsec);
    }
    return(-1);
}

示例代码:

#include "apue.h"
#include <sys/socket.h>

#define MAXSLEEP 128

int connect_retry(int domain, int type, int protocol, const struct sockaddr *addr, socklen_t alen)
{
    int numsec, fd;

    /*
     * Try to connect with exponential backoff.
     */
    for (numsec = 1; numsec <= MAXSLEEP; numsec <<= 1) 
    {
        if ((fd = socket(domain, type, protocol)) < 0)
            return(-1);
        if (connect(fd, addr, alen) == 0) 
        {
            /*
             * Connection accepted.
             */
            return(fd);
        }
        close(fd);

        /*
         * Delay before trying again.
         */
        if (numsec <= MAXSLEEP/2)
            sleep(numsec);
    }
    return(-1);
}



listen 函数


服务器调用listen函数来宣告它愿意接受连接请求

#include <sys/socket.h>
int listen(int sockfd,int backlog);


参数backlog提供了一个提示,提示系统改进程所要入队的未完成连接请求数量。一旦队列满了,系统就会拒绝多余的连接请求。

一旦服务器调用了listen,所用的套接字就恩能够接受连接请求。使用accept函数获得连接请求并建立连接。

#include <sys/socket.h>
int accept(int fd,struct sockaddr *restrict addr,socklen_t *restrict len); 

函数返回的是一个连接到调用connect的客户端的套接字描述符


服务器初始化套接字:

#include "apue.h"
#include <errno.h>
#include <sys/socket.h>

int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen)
{
    int fd;
    int err = 0;

    if ((fd = socket(addr->sa_family, type, 0)) < 0)
        return(-1);
    if (bind(fd, addr, alen) < 0)
        goto errout;
    if (type == SOCK_STREAM || type == SOCK_SEQPACKET) 
    {
        if (listen(fd, qlen) < 0)
            goto errout;
    }
    return(fd);

errout:
    err = errno;
    close(fd);
    errno = err;
    return(-1);
}




4. 数据传输




发送数据的三个函数(man send)和 接收收据的三个函数(man recv)

NAME
       send, sendto, sendmsg - send a message on a socket

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);

       ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, 
                      const struct sockaddr *dest_addr, socklen_t addrlen);

       ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

---------------------------------------------------------------------------------------------------------
NAME
       recv, recvfrom, recvmsg - receive a message from a socket

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t recv(int sockfd, void *buf, size_t len, int flags);

       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, 
                        struct sockaddr *src_addr, socklen_t *addrlen);

       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);


发送数据的函数


send 函数

使用 man send 查看具体使用方法。最简单的是 send 函数,它和 write 很像,但是可以指定标志来改变处理传输数据的方式。

使用 send 函数时,套接字必须已经连接,例如 使用 TCP 协议 的发送数据。参数 buf 和 len 与 write 函数中含义一致。

#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

下面总结了 send 函数中套接字 flags 可能的取值



sendto 函数

函数sendto和send很类似,区别在于sendto可以在无连接的套接字上指定一个目标地址。例如 使用 UDP 协议的发送数据。

#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, 
               const struct sockaddr *dest_addr, socklen_t addrlen);



sendmsg 函数

#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

POSIX.1定义了msghdr结构,它至少有以下成员:

struct msghdr {
        void         *msg_name;       /* optional address */
        socklen_t     msg_namelen;    /* size of address */
        struct iovec *msg_iov;        /* scatter/gather array */
        size_t        msg_iovlen;     /* # elements in msg_iov */
        void         *msg_control;    /* ancillary data, see below */
        size_t        msg_controllen; /* ancillary data buffer len */
        int           msg_flags;      /* flags on received message */
};


接收数据的函数


recv 函数

函数recv和read相似,但是recv可以指定标志来控制如何接收数据

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);




recvfrom 函数

#include <sys/socket.h>
ssize_t recvfrom(int sockfd,void *restrict buf,size_t len,int flags
                 struct sockaddr *restrict addr,
                 socklen_t *restrict addrlen);



recvmsg 函数

#include <sys/socket.h>
ssize_t recvmsg(int sockfd,struct msghdr *msg,int flags);




客户机/服务器模式



在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户机/服务器模式*(client/server),即客户像服务其提出请求,服务器接受到请求后,提供相应的服务。

服务器:

(1)首先服务器方要先启动,打开一个通信通道并告知本机,它愿意在某一地址和端口上接收客户请求

(2)等待客户请求到达该端口

(3)接收服务请求,处理该客户请求,服务完成后,关闭此新进程与客户的通信链路,并终止

(4)返回第二步,等待另一个客户请求

(5)关闭服务器

客户方:

(1)打开一个通信通道,并连接到服务器所在的主机特定的端口

(2)向服务器发送请求,等待并接收应答,继续提出请求

(3)请求结束后关闭通信信道并终止



基于TCP(面向连接)的socket编程



服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。


示例程序

程序显示一个客户端命令,该命令用于与服务器通信以获取系统命令 uptime 的输出,该服务称为“remote uptime”(简称 ruptime)

客户端:

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>

#define BUFLEN		128

extern int connect_retry(int, int, int, const struct sockaddr *, socklen_t);

void print_uptime(int sockfd)
{
	int		n;
	char	buf[BUFLEN];

	while ((n = recv(sockfd, buf, BUFLEN, 0)) > 0)
		write(STDOUT_FILENO, buf, n);
	if (n < 0)
		err_sys("recv error");
}

int main(int argc, char *argv[])
{
	struct addrinfo	*ailist, *aip;
	struct addrinfo	hint;
	int				sockfd, err;

	if (argc != 2)
		err_quit("usage: ruptime hostname");
	memset(&hint, 0, sizeof(hint));
	hint.ai_socktype = SOCK_STREAM;
	hint.ai_canonname = NULL;
	hint.ai_addr = NULL;
	hint.ai_next = NULL;
	if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)
		err_quit("getaddrinfo error: %s", gai_strerror(err));
	for (aip = ailist; aip != NULL; aip = aip->ai_next) 
	{
		if ((sockfd = connect_retry(aip->ai_family, SOCK_STREAM, 0, aip->ai_addr, aip->ai_addrlen)) < 0) 
                {
			err = errno;
		} 
                else 
		{
			print_uptime(sockfd);
			exit(0);
		}
	}
	err_exit(err, "can't connect to %s", argv[1]);
}


服务端:

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <sys/socket.h>

#define BUFLEN    128
#define QLEN 10

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif

extern int initserver(int, const struct sockaddr *, socklen_t, int);

void serve(int sockfd)
{
    int        clfd;
    FILE    *fp;
    char    buf[BUFLEN];

    set_cloexec(sockfd);
    for (;;) 
    {
        if ((clfd = accept(sockfd, NULL, NULL)) < 0) 
        {
            syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno));
            exit(1);
        }
        set_cloexec(clfd);
        if ((fp = popen("/usr/bin/uptime", "r")) == NULL) 
        {
            sprintf(buf, "error: %s\n", strerror(errno));
            send(clfd, buf, strlen(buf), 0);
        } 
        else 
        {
            while (fgets(buf, BUFLEN, fp) != NULL)
                send(clfd, buf, strlen(buf), 0);
            pclose(fp);
        }
        close(clfd);
    }
}

int main(int argc, char *argv[])
{
    struct addrinfo    *ailist, *aip;
    struct addrinfo    hint;
    int                sockfd, err, n;
    char            *host;

    if (argc != 1) err_quit("usage: ruptimed");
    if ((n = sysconf(_SC_HOST_NAME_MAX)) < 0) n = HOST_NAME_MAX;    /* best guess */
    if ((host = malloc(n)) == NULL) err_sys("malloc error");
    if (gethostname(host, n) < 0) err_sys("gethostname error");
    daemonize("ruptimed");
    memset(&hint, 0, sizeof(hint));
    hint.ai_flags = AI_CANONNAME;
    hint.ai_socktype = SOCK_STREAM;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) 
    {
        syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s", gai_strerror(err));
        exit(1);
    }
    for (aip = ailist; aip != NULL; aip = aip->ai_next) 
    {
        if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN)) >= 0) 
        {
            serve(sockfd);
            exit(0);
        }
    }
    exit(1);
}


另一个版本的服务器程序

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/wait.h>

#define QLEN 10

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif

extern int initserver(int, const struct sockaddr *, socklen_t, int);

void serve(int sockfd)
{
    int        clfd, status;
    pid_t    pid;

    set_cloexec(sockfd);
    for (;;) {
        if ((clfd = accept(sockfd, NULL, NULL)) < 0) {
            syslog(LOG_ERR, "ruptimed: accept error: %s",
              strerror(errno));
            exit(1);
        }
        if ((pid = fork()) < 0) {
            syslog(LOG_ERR, "ruptimed: fork error: %s",
              strerror(errno));
            exit(1);
        } else if (pid == 0) {    /* child */
            /*
             * The parent called daemonize ({Prog daemoninit}), so
             * STDIN_FILENO, STDOUT_FILENO, and STDERR_FILENO
             * are already open to /dev/null.  Thus, the call to
             * close doesn't need to be protected by checks that
             * clfd isn't already equal to one of these values.
             */
            if (dup2(clfd, STDOUT_FILENO) != STDOUT_FILENO ||
              dup2(clfd, STDERR_FILENO) != STDERR_FILENO) {
                syslog(LOG_ERR, "ruptimed: unexpected error");
                exit(1);
            }
            close(clfd);
            execl("/usr/bin/uptime", "uptime", (char *)0);
            syslog(LOG_ERR, "ruptimed: unexpected return from exec: %s",
              strerror(errno));
        } else {        /* parent */
            close(clfd);
            waitpid(pid, &status, 0);
        }
    }
}

int main(int argc, char *argv[])
{
    struct addrinfo    *ailist, *aip;
    struct addrinfo    hint;
    int                sockfd, err, n;
    char            *host;

    if (argc != 1)
        err_quit("usage: ruptimed");
    if ((n = sysconf(_SC_HOST_NAME_MAX)) < 0)
        n = HOST_NAME_MAX;    /* best guess */
    if ((host = malloc(n)) == NULL)
        err_sys("malloc error");
    if (gethostname(host, n) < 0)
        err_sys("gethostname error");
    daemonize("ruptimed");
    memset(&hint, 0, sizeof(hint));
    hint.ai_flags = AI_CANONNAME;
    hint.ai_socktype = SOCK_STREAM;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) {
        syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s",
          gai_strerror(err));
        exit(1);
    }
    for (aip = ailist; aip != NULL; aip = aip->ai_next) {
        if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr,
          aip->ai_addrlen, QLEN)) >= 0) {
            serve(sockfd);
            exit(0);
        }
    }
    exit(1);
}





基于UDP(面向无连接)的socket编程



服务器先创建socket,将socket绑定(bind)一个本地地址和端口上,等待数据传输(recvfrom)。这个时候如果有个客户端创建socket,并且向服务器发送数据(sendto),服务器就建立了连接,实现了数据的通信,连接结束后关闭连接。


示例程序

客户端:

下面程序采用数据报套接字接口的uptime客户端命令版本

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>

#define BUFLEN        128
#define TIMEOUT        20

void sigalrm(int signo)
{
}

void print_uptime(int sockfd, struct addrinfo *aip)
{
    int        n;
    char    buf[BUFLEN];

    buf[0] = 0;
    if (sendto(sockfd, buf, 1, 0, aip->ai_addr, aip->ai_addrlen) < 0)
        err_sys("sendto error");
    alarm(TIMEOUT);
    if ((n = recvfrom(sockfd, buf, BUFLEN, 0, NULL, NULL)) < 0) {
        if (errno != EINTR)
            alarm(0);
        err_sys("recv error");
    }
    alarm(0);
    write(STDOUT_FILENO, buf, n);
}

int main(int argc, char *argv[])
{
    struct addrinfo        *ailist, *aip;
    struct addrinfo        hint;
    int                    sockfd, err;
    struct sigaction    sa;

    if (argc != 2)
        err_quit("usage: ruptime hostname");
    sa.sa_handler = sigalrm;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGALRM, &sa, NULL) < 0)
        err_sys("sigaction error");
    memset(&hint, 0, sizeof(hint));
    hint.ai_socktype = SOCK_DGRAM;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)
        err_quit("getaddrinfo error: %s", gai_strerror(err));

    for (aip = ailist; aip != NULL; aip = aip->ai_next) {
        if ((sockfd = socket(aip->ai_family, SOCK_DGRAM, 0)) < 0) {
            err = errno;
        } else {
            print_uptime(sockfd, aip);
            exit(0);
        }
    }

    fprintf(stderr, "can't contact %s: %s\n", argv[1], strerror(err));
    exit(1);
}


服务器程序:

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <sys/socket.h>

#define BUFLEN        128
#define MAXADDRLEN    256

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif

extern int initserver(int, const struct sockaddr *, socklen_t, int);

void
serve(int sockfd)
{
    int                n;
    socklen_t        alen;
    FILE            *fp;
    char            buf[BUFLEN];
    char            abuf[MAXADDRLEN];
    struct sockaddr    *addr = (struct sockaddr *)abuf;

    set_cloexec(sockfd);
    for (;;) {
        alen = MAXADDRLEN;
        if ((n = recvfrom(sockfd, buf, BUFLEN, 0, addr, &alen)) < 0) {
            syslog(LOG_ERR, "ruptimed: recvfrom error: %s",
              strerror(errno));
            exit(1);
        }
        if ((fp = popen("/usr/bin/uptime", "r")) == NULL) {
            sprintf(buf, "error: %s\n", strerror(errno));
            sendto(sockfd, buf, strlen(buf), 0, addr, alen);
        } else {
            if (fgets(buf, BUFLEN, fp) != NULL)
                sendto(sockfd, buf, strlen(buf), 0, addr, alen);
            pclose(fp);
        }
    }
}

int
main(int argc, char *argv[])
{
    struct addrinfo    *ailist, *aip;
    struct addrinfo    hint;
    int                sockfd, err, n;
    char            *host;

    if (argc != 1)
        err_quit("usage: ruptimed");
    if ((n = sysconf(_SC_HOST_NAME_MAX)) < 0)
        n = HOST_NAME_MAX;    /* best guess */
    if ((host = malloc(n)) == NULL)
        err_sys("malloc error");
    if (gethostname(host, n) < 0)
        err_sys("gethostname error");
    daemonize("ruptimed");
    memset(&hint, 0, sizeof(hint));
    hint.ai_flags = AI_CANONNAME;
    hint.ai_socktype = SOCK_DGRAM;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) {
        syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s",
          gai_strerror(err));
        exit(1);
    }
    for (aip = ailist; aip != NULL; aip = aip->ai_next) {
        if ((sockfd = initserver(SOCK_DGRAM, aip->ai_addr,
          aip->ai_addrlen, 0)) >= 0) {
            serve(sockfd);
            exit(0);
        }
    }
    exit(1);
}




5. 套接字选项


套接字机制提供了设置跟查询套接字选项的接口。可以获取或设置以下3种选项

1.通用选项,工作在所有套接字类型上

2.在套接字层次管理的选项,但是依赖于下层协议的支持

3.特定于某协议的选项,每个协议独有的

可以使用setsockopt函数来设置套接字选项

#include <sys/socket.h>
int setsockopt(int sockfd,int level,int option,const void *val,socklen_t len);

level标识了选项应用的协议:

如果选项是通用的套接字层次选项,则level设置为SOL_SOCKET,否则,level设置成控制这个选项的协议编号。对于TCP选项,level是IPPROTO_TCP,对于IP,level是IPPROTO_IP。

下面总结了Single UNIX Specification中定义的通用套接字层次选项


可以使用getsockopt函数来查看选项的当前值

#include <sys/socket.h>
int getsockopt(int sockfd,int level,int option,void *restrict val,socklen_t restrict lenp);

采用地址复用初始化套接字端点:

#include "apue.h"
#include <errno.h>
#include <sys/socket.h>

int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen)
{
	int fd, err;
	int reuse = 1;

	if ((fd = socket(addr->sa_family, type, 0)) < 0)
		return(-1);
	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse,
	  sizeof(int)) < 0)
		goto errout;
	if (bind(fd, addr, alen) < 0)
		goto errout;
	if (type == SOCK_STREAM || type == SOCK_SEQPACKET)
		if (listen(fd, qlen) < 0)
			goto errout;
	return(fd);

errout:
	err = errno;
	close(fd);
	errno = err;
	return(-1);
}



6. 带外数据


带外数据是一些通信协议所支持的可选特征,允许更加高级的数据比普通数据优先传输。

TCP将带外数据称为紧急数据。TCP仅支持一个字节的紧急数据,但是允许紧急数据在普通数据传递机制数据流之外传输。

为了产生紧急数据,在三个send函数中任何一个指定标志MSG_OOB。如果带MSG_OOB标志传输字节超过一个时,最后一个字节被作为紧急数据字节。

如果安排发生套接字信号,当接收到紧急数据时,那么发送信号SIGURG信号。可以通过调用以下函数安排进程接收套接字的信号:

fcntl(sockfd,F_SETTOWN,pid);

F_GETOWN命令可以用来获取当前套接字所有权

owner=fcntl(sockfd,F_GETOWN,0);

为帮助判断是否已经达到紧急标记,可以使用函数sockatmark

#include <sys/socket.h>
int sockatmark(int sockfd);

当下一个要读取的字节在紧急标志处时,sockatmark返回1。



7. 非阻塞和异步I/O


在基于套接字异步I/O中,当能够从套接字中读取数据,或者套接字写队列中的空间变得可用时,可以安排发送信号SIGIO。通过两个步骤来使用异步I/O:

1) 建立套接字拥有者关系,信号可以被传送到合适的进程。

2) 通知套接字当I/O操作不会阻塞时发信号告知。

可以使用三种方式来完成第一个步骤:

A、 在fcntl使用F_SETOWN命令

B、 在ioctl中作用FIOSETOWN命令

C、 在ioctl中使用SIOCSPGRP命令。

     要完成第二个步骤,有两个选择:

A、 在fcntl中使用F_SETFL命令并且启用文件标志O_ASYNC。

B、 在ioctl中使用FIOASYNC




示例



TCP 示例 客户端 和服务端程序:

服务端代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ctype.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define SERV_PORT    8000

int main(void)
{
    int sfd, cfd;
    struct sockaddr_in serv_addr, clin_addr;   //IP  + 端口号
    socklen_t clin_addr_len;
    char buf[4096];
    int len, i;

    //AF_INET:ipv4协议  SOCK_STREAM:流式协议  0:流式协议里默认协议TCP
    sfd = socket(AF_INET, SOCK_STREAM, 0); 
    
    /* 构造本地进程绑定的地址信息 */
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;   // IPv4地址类型
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //绑定本机所有IP
    serv_addr.sin_port = htons(SERV_PORT);   //绑定端口号,转换为网络序

    /* 本地进程绑定,地址信息+socket文件描述符 */
    bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    /* 设置socket同时能处理多少客户端链接 */
    listen(sfd, 20);

    clin_addr_len = sizeof(clin_addr);
    cfd = accept(sfd, (struct sockaddr *)&clin_addr, &clin_addr_len);

    while (1) 
    {
        len = read(cfd, buf, sizeof(buf));//接收客户端发送过来的数据

        //模拟处理数据,小写转大写
        for (i = 0; i < len; i++) 
            buf[i] = toupper(buf[i]);

        write(cfd, buf, len);//把处理后的数据发回客户端
    }

    close(cfd); //关闭和客户端链接的socket
    close(sfd); //关闭accept监听用的socket

    return 0;
}


客户端代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ctype.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define SERV_PORT    8000
#define SERV_IP     "127.0.0.1"

int main(int argc, char *argv[])
{
    struct sockaddr_in serv_addr;
    int cfd, len;
    char buf[1024];

    cfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); //serv_addr.sin_addr.s_addr = SERV_IP;
    serv_addr.sin_port = htons(SERV_PORT);

    connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    while (fgets(buf, sizeof(buf), stdin)) 
    {
        write(cfd, buf, strlen(buf));
        len = read(cfd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, len);
    }
    close(cfd);
    return 0;
}






猜你喜欢

转载自blog.csdn.net/freeking101/article/details/78889736