第16章 网络IPC:套接字

  1. 进程通信(interProcess Communication ,IPC)
  2. 套接字描述符
    套接字是通信端点的抽象。
    #include<sys/socket.h>
    int socket(int domain, int type, int protocol);
    返回值:若成功,返回文件(套接字)描述符;若出错,返回 -1
    参数domain(域)确定通信的特性,包括地址格式。
    参数type确定套接字的类型,进一步确定通信特征。
    参数protocol通常是0,表示为给定的域和套接字选择默认协议。

通过网络套接字可以使得不同计算机上运行的进程相互通信。

1、创建套接字

#include <sys/socket.h>

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

注意:AF_LOCAL域是AF_UNIX的别名,AF_UNSPEC域可以代表任何域。

2、套接字通信是双向的,禁止套接字上的输入/输出

#include < sys/socket.h>

Int shutdown ( int sockfd, int how);

3、处理字节序和网络字节序之间的轮换:

#include< arpa/inet.h>

Uint32_t htonl (uint32_t hostint32);

Uint16_t htons(uint16_t hostint16);

Uint32_t ntohl (uint32_t netint32);

Uint16_t ntohs (uint16_t netint16);

4、地址格式:

Struct sockaddr{

       Sa_family_t sa_family;

       Char sa_data[];}

在Linux中,该结构定义如下:

Struct sockaddr{

       Sa_family_t sa_family;

       Char sa_data[14];};

而在FreeBSD中,该结构定义如下:

Struct sockaddr{

       Unsigned char sa_len;

       Sa_family_t sa_family;

       Char sa_data[14];};

因特网地址定义在<netinet/in.h>中。在IPV4因特网域(AF_INET)中,套接字地址用如下结构sokaddr_in表示:

Struct in_addr{

       In_addr_t s_addr;}

Struct sockaddr_in{

       Sa_family_t sin_family;

       In_port_t sin_port;

Struct in_addr sin_addr;

};

数据类型in_port_t 定义成uint16_t。数据类型in_addr_t 定义成uint32_t。这些整数类型在<sdint.h>中定义并指定了相应的位数。

与IPV4因特网域(AF_INET)相比较,IPV6因特网域(AF_INET6)套接字地址用如下结构sockaddr_in6表示:

Struct in6_addr{

       Uint8_t s6_addr[16];};

 Struct sockaddr_in6{

       Sa_family_t sin6_family;

       In_port_t sin6_port;

       Uint32_t sin6_flowinfo;

       Struct in6_addr sin6_addr;

       Uint32_t sin6_scope_id;}

 在Linux中,sockaddr_in 定义如下:

Struct sockaddr_in{

       Sa_family_t sin_family;

       In_port_t sin_port;

       Struct in_addr sin_addr;

       Unsigned char sin_zero[8];};

5、打印出能被人理解的地址格式函数

#include <arpa/inet.h>

const char * inet_ntop (int domain, const void * restrict addr, char * restrict str, socklen_t size);

Int inet_pton ( int domain, const char * restrict str, void * restrict addr);

 6、找给定计算机的主机信息

#include<netdb.h>

Struct hostent * gethostent (void);

Void sethostent (int stayopen);

Void endhostent (void);

Struct hostent{

       Char * h_name;

       Char ** h_aliases;

       Int h_addrtype;

       Int h_length;

       Char ** h_addr_list;};

7、从接口获得网络名字和网络号:

#include<netdb.h.

Struct netent * getnetbyaddr (uint32_t net, int type);

Struct netent * getnetbyname (const char * name);

Struct netent * getnetent (void);

Void setnetent (int stayopen);

Void endnetent (void);

 Struct netent{

       Char * n_name;

       Char ** n_aliases;

       Int n_addrtype;

       Uint32_t n_net;};

8、将协议名字和协议号采用以下函数映射

#include <netdb.h>

Struct protoent * getprotobyname (const char * name);

Struct protoent * getprotobynumber (int proto);

Struct protoent * getprotoent (void);

Void setprotoent (int stayopen);

Void endprotoent (void);

Struct protoent{

       Char * p_name;

       Char ** p_aliases;

       Int p_proto;};

9、从一个服务名映射到一个端口号,服务名

#include<netdb.h>

Struct servent * getservbyname (const char * name, const char * proto);

Struct servent * getservbyport (int port, const char * proto);

Struct servent * getservent( (void);

Void setervent (int stayopen);

Void endservent (void);

Struct servent{

       Char * s_name;

       Char ** s_aliases;

       Int s_port;

       Char * s_proto;};

 10、从一个主机名字和服务名字映射到一个地址

#include <sys/socket.h>

#include <netdb.h>

Int getaddrinfo (const char * restrict host, const char * restrict service, const struct addrinfo * restrict hint, struct addrinfo ** restrict res);

Void freeaddrinfo (struct addrinfo * ai);

Struct addrinfo{

       Int ai_flags;

       int ai_family;

       Int ai_socktype;

       Int ai_protocol;

       Socklen_t ai_addrlen;

       Struct sockaddr * ai_addr;

       Char * ai_canonname;

       Struct addrinfo * ai_next;};

11、gai_strerror将返回的错误码转换成错误消息

#include<netdb.h>

Const char * gai_strerror (int error);

 12、将地址转换成主机或者服务名

#include<sys/socket.h>

#include <netdb.h>

Int getnameinfo (const struct sockaddr * restrict addr, socklen_t alen, char * restrict host,socklen_t hostlen, char * restrict service, socklen_t servlen, unsigned int flags);   

写一个程序获取本机的网络信息,程序如下:

复制代码
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <arpa/inet.h>
  5 #include <netdb.h>
  6 #include <sys/socket.h>
  7 
  8 void print_family(struct addrinfo *aip)
  9 {
 10     printf("family ");
 11     switch(aip->ai_family)
 12     {
 13     case AF_INET:
 14         printf("inet ");
 15         break;
 16     case AF_INET6:
 17         printf("inet6 ");
 18         break;
 19     case AF_UNIX:
 20         printf("unix ");
 21         break;
 22     case AF_UNSPEC:
 23         printf("unspecified ");
 24         break;
 25     default:
 26         printf("unkown ");
 27     }
 28 }
 29 
 30 void print_type(struct addrinfo *aip)
 31 {
 32     printf("type ");
 33     switch(aip->ai_socktype)
 34     {
 35     case SOCK_STREAM:
 36         printf("stream ");
 37         break;
 38     case SOCK_DGRAM:
 39         printf("datagram");
 40         break;
 41     case SOCK_SEQPACKET:
 42         printf("seqpacket ");
 43         break;
 44     case SOCK_RAW:
 45         printf("raw ");
 46         break;
 47     default:
 48         printf("unknown (%d)",aip->ai_socktype);
 49     }
 50 }
 51 
 52 void print_protocol(struct addrinfo *aip)
 53 {
 54     printf(" protocol ");
 55     switch(aip->ai_protocol)
 56     {
 57     case 0:
 58         printf("default ");
 59         break;
 60     case IPPROTO_TCP:
 61         printf("TCP ");
 62         break;
 63     case IPPROTO_UDP:
 64         printf("UDP ");
 65         break;
 66     case IPPROTO_RAW:
 67         printf("raw ");
 68         break;
 69     default:
 70         printf("unknown (%d)",aip->ai_protocol);
 71     }
 72 }
 73 
 74 void print_flags(struct addrinfo *aip)
 75 {
 76     printf("flags");
 77     if(aip->ai_flags == 0)
 78         printf("0");
 79     else
 80     {
 81         if(aip->ai_flags & AI_PASSIVE)
 82             printf(" passive ");
 83         if(aip->ai_flags & AI_CANONNAME)
 84             printf(" canon ");
 85         if(aip->ai_flags & AI_NUMERICHOST)
 86             printf(" numhost ");
 87     }
 88 }
 89 
 90 int main()
 91 {
 92     struct addrinfo *ailist,*aip;
 93     struct addrinfo hint;
 94     struct sockaddr_in *sinp;
 95     const char *addr;
 96     int err;
 97     char abuf[INET_ADDRSTRLEN];
 98     hint.ai_flags = AI_CANONNAME;
 99     hint.ai_family = 0;
100     hint.ai_socktype = 0;
101     hint.ai_protocol = 0;
102     hint.ai_addrlen = 0;
103     hint.ai_canonname = NULL;
104     hint.ai_addr = NULL;
105     hint.ai_next = NULL;
106     if(getaddrinfo("localhost",NULL,&hint,&ailist) != 0)
107     {
108         printf("getaddrinfo error: %s",gai_strerror(err));
109         exit(-1);
110     }
111     for(aip = ailist;aip != NULL;aip = aip->ai_next)
112     {
113         print_flags(aip);
114         print_family(aip);
115         print_type(aip);
116         print_protocol(aip);
117         printf("\n\thost %s",aip->ai_canonname ?aip->ai_canonname : "-");
118         if(aip->ai_family == AF_INET)
119         {
120             sinp = (struct sockaddr_in *)aip->ai_addr;
121             addr = inet_ntop(AF_INET,&sinp->sin_addr,abuf,INET_ADDRSTRLEN);
122             printf(" address %s ",addr?addr:"unknown");
123             printf(" port %d ",ntohs(sinp->sin_port));
124         }
125         printf("\n");
126     }
127     exit(0);
128 }
复制代码

程序执行结果如下:

13、将套接字与地址绑定

#include <sys/socket.h>

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

对于使用的地址有一些限制:

A、 在进程所运行的机器上,指定的地址必须有效,不能指定一个其他机器的地址。

B、 地址必须和创建套接字时的地址族支持的格式相匹配。

C、 端口号必须不小于1024,除非该进程具有相应的特权(即为超级用户)。

D、一般只有套接字端点能够与地址绑定,尽管有些协议允许多重绑定。

14、获取绑定到一个套接字的地址:

#include <sys/socket.h>

Int getsockname ( int sockfd, struct sockaddr * restrict addr, socklen_t * restrict alenp);

注意:在调用getsockname之前,设置alenp为一个指向整数的指针,该整数指定缓冲区sockaddr的大小。返回时,该整数会被设置成返回地址的大小。如果该地址和提供的缓冲区长度不匹配,则将其截断而不报错。如果当前没有绑定到该套接字的地址,其结果没有定义。

15、获得对方地址:

#include <sys/socket.h>

Int getpeername ( int sockfd, struct sockaddr * restrict addr, socklen_t * restrict alenp);

注意:如果套接字已经和对方连接,调用getpeername来找到对方的地址。除了还会返回对方的地址之外,函数getpeername和getsockname一样。

16、建立连接          

#include <sys/socket.h>

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

17、listen函数

#include <sys/socket.h>

Int listen ( int sockfd,int backlog);

18、accept函数

#include <sys/socket.h>

Int accept ( int sockfd, struct sockaddr * restrict addr, socklen_t * restrict len);

19、send、sendto以及sendmsg信息发送函数

#include <sys/socket.h>

Ssize_t send ( int sockfd, const void * buf, size_t nbytes, int flags);

Ssize_t sendto ( int sockfd, const void * buf, size_t nbytes, int flags, const struct sockaddr * destaddr, socklen_t destlen);

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

Struct msghdr{

Void * msg_name;

Socklen_t msg_namelen;

Struct iovec * msg_iov;

Int msg_iovlen;

Void * msg_control;

Socklen_t msg_controllen;

Int msg_flags;

};

19、recv、recvfrom 与recvmsg接收数据函数

#include <sys/socket.h>

Ssize_t recv ( int sockfd, void * buf, size_t nbytes, int flags);

Ssize_t recvfrom ( int sockfd, void * restrict buf, size_t len, int flags, struct sockaddr * restrict addr, socklen_t  * restrict addrlen);

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

写个程序练习网络IPC,客户端请求服务器获取当前时间,服务器调用uptime命令将时间发送给客户端。程序如下:

客户端程序:

View Code

服务器程序如下:

View Code

为了获取ruptime的网络信息,需要编辑/etc/services文件,追加ruptime    4000/tcp   。

程序执行结果如下:

客户端获取服务器时间:

服务器端响应客户请求:

20、带外数据:TCP支持带外数据,但是UDP不支持。TCP仅支持一个字节的紧急数据,但是允许紧急数据在普通数据传递机制数据流之外传输。为了产生紧急数据,在三个send函数中任何一个指定MSG_OOB。如果带MSG_OOB标志传输字节超过一个时,最后一个字节被看作紧急数据字节。当接收到紧急数据时,那么改善信号SIGURG。

TCP支持紧急标记的概念:在普通数据流中紧急数据所在的位置。如果采用套接字选项SO_OOBINLINE,那么可以在普通数据中接收紧急数据。为帮助判断是否接收到紧急标记,可以使用函数sockatmark

#include <sys/socket.h>

Int sockatmark ( int sockfd);

当下一个要读的字节在紧急标志所标识的位置时,sockatmark返回1。当带外数据出现在套接字读取队列时,select函数会返回一个文件描述符并且拥有一个异常状态挂起。可以在普通数据流上接受紧急数据,或者在某个recv函数中MSG_OOB标志在其他队列数据之前接收紧急数据。TCP队列仅有一字节的紧急数据,如果在接收当前的紧急数据字节之前又有新的紧急数据到来,那么当前的字节会被丢弃。

21、在基于套接字异步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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值