APUE学习笔记——第十六章 网络IPC:套接字

1、套接字描述符
套接字是通信端点的抽象,与应用程序要使用文件描述符访问文件一样,访问套接字也需要用套接字描述符,套接字描述符在UNIX系统是用文件描述符实现的,许多文件描述符函数(如read write)都可以处理套接字描述符。
创建一个套接字,调用socket函数
#include <sys/socket.h>
int socket(int domain,int type,int protocol)//若成功返回套接字描述符,错误返回-1
参数domain(域)确定通信的特性,包括地址格式,POSIX.1指定的各个域有:AF_INET(IPv4因特网域),AF_INET6(IPv6因特网域),AF_UNIX(UNIX域),AF_UNSPEC(未指定)。各个域有自己的格式表示地址,每个域的常数都是以AF_开头,表示地址族(address family)。
参数type确定套接字的类型,进一步确定通信特征,POSIX.1定义的套接字类型有:SOCK_DGRAM(长度固定的、无连接的不可靠报文传递,即使用UDP协议),SOCK_STREAM(有序、可靠、双向的面向连接字节流,即TCP协议),SOCK_RAW(IP协议的数据报接口),SOCK_SEQPACKET(长度固定、有序、可靠的面向连接报文传递)
参数protocol通常是0,表示按给定的域和套接字类型选择默认协议,当对同一域和套接字类型支持多个协议时,可以使用protocol参数选择一个特定协议。
套接字通信是双向的,可以采用函数shutdown来禁止套接字上的输入/输出
#include <sys/socket.h>
int shutdown(int sockfd,int how)
如果how是SHUT_RD(关闭读端),则无法从套接字读取数据;如果how是SHUT_WR(关闭写端),则无法使用套接字发送数据;如果how是SHUT_RDWR则将同时无法读取和发送数据
2、寻址
2.1字节序
处理器的架构有大端字节序和小端字节序,下面是判断大端字节序和小端字节序的C程序
UnionEndian{
    int val;
char str[sizeof(int)]
}E;
int main(){
    E.val = 1;
if(E.str[0] == 1) puts("little endian");
else if(E.str[sizeof(int) - 1] == 1) puts("big endian");
else puts("unknown");
}
网络协议指定了字节序,因此异构计算机系统能够交换协议信息而不会混淆字节序。TCP/IP协议栈采用大端字节序。对于TCP/IP,地址用网络字节序来表示,所以应用程序有时需要在处理器的字节序与网络字节序之间转换。对于TCP/IP应用程序,提供了四个通用函数以实施在处理器字节序和网络字节序之间的转换。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostint32) 返回值:以网络字节序表示的32位整型数(htonl = host to net long)
uint16_t htons(uint16_t hostint16) 返回值:以网络字节序表示的16位整型数
uint32_t ntohl(uint32_t netint32)  返回值:以主机字节序表示的32位整型数
uint16_t ntohl(uint16_t netint16)  返回值:以主机字节序表示的16位整型数
2.2、地址格式
地址标识了特定通信域中的套接字端点,地址格式与特定的通信域相关,为使不同格式的地址能够被传入到套接字函数,地址被强制转换成通用的地址结构sockaddr表示:
struct sockaddr{
    sa_family_t sa_family; /*address family */
char sa_data[]; /* variable-length address*/
};
套接字实现可以自由地添加额外的成员并且定义sa_data成员的大学,在Linux中,该结构定义如下:
struct sockaddr{
    sa_family_t sa_family; /*address family */
char sa_data[14]; /* variable-length address*/
};
而在FreeBSD中,该结构定义如下:
struct sockaddr{
    unsigned char sa_len; /* total length*/
    sa_family_t sa_family; /*address family */
char sa_data[14]; /* variable-length address*/
};
因特网地址定义在<netinet/in.h>。在IPv4因特网域(AF_INET)中,套接字用如下结构sockaddr_in表示:
struct in_addr{
    in_addr_t s_addr; /* IPv4 address*/
};
struct sockaddr_in{
    sa_family_t sin_family;  /*address family*/
in_port_t sin_port; /*port number*/
struct in_addr sin_addr; /*IPv4 address*/
};
相对IPv6(AF_INET6)套接字地址用如下结构sockaddr_in6表示:
struct in6_addr{
    in_addr_t s_addr; /* IPv4 address*/
};
struct sockaddr_in6{
    sa_family_t sin6_family;  /*address family*/
in_port_t sin6_port; /*port number*/
uint32_t sin6_flowinfo; /*traffic class and flow info*/
struct in_addr sin6_addr; /*IPv4 address*/
uint32_t sin6_scope_id; /* set of interfaces for scope */
};
尽管sockaddr_in和sockaddr_in6相差比较大,但是他们均可以被强制转换成sockaddr结构传入到套接字例程中。BSD网络软件中包含函数inet_addr和inet_ntoa,用于在二进制地址格式与点分十进制字符串表示之间相互转换,这两个函数仅用于IPv4地址,但功能相似的两个新函数inet_ntop和inet_pton支持IPv4和IPv6地址。
#include <arpa/inet.h>
const char* inet_ntop(int domain, const void* addr,char* str,socklen_t size) 返回值:成功返回地址字符串指针
int inet_pton(int domain,const char* str,void* addr)
2.3、地址查询
通过调用gethostent,可以找到给定计算机的主机信息
#include <netdb.h>
struct hostent* gethostent();
void sethostent(int stayopen);
void endhostent()
如果主机数据文件没有打开,gethostent会打开它,函数gethostent返回文件的下一个条目,函数sethostent会打开文件,如果文件已经被打开,那么将其回绕,函数endhostent将关闭文件。当gethostent返回时,得到一个指向hostent结构的指针。数据结构hostent至少包含以下成员:
struct hostent{
   char *h_name; //name of host
   char **h_aliases;//pointer to alternate host name array
   int h_addrtype;//address type
   int h_length;
   char ** h_addr_list;//pointer to array of network addresses
};
返回的地址采用网络字节序。
从接口获得网络名字和网络号:
#include <netdb.h>
struct netent *getnetbyaddr(uint32_t net,int type)
struct netent *getnetbyname(const char* name)
struct netent *getnetent()
void setnetent(int stayopen)
void endnetent()
数据结构netent至少包含如下字段:
struct netent{
    char *n_name;
char **n_aliases;
int n_addrtype;
uint32_t n_net; //network number
};
网络号按照网络字节序返回,地址类型是一个地址族常量(如AF_INET)
可以将协议名字和协议号采用以下函数映射:
#include <netdb.h>
struct protoent *getprotobyname(const char *name)
struct protoent *getprotobynumber(int proto)
struct protoent *getprotoent()
void setprotoent(int stayopen)
void enprotoent()
POSIX.1定义结构protoent至少包含如下成员:
struct protoent{
    char *p_name;
char **p_aliases;
int p_proto;//protocol number
};
服务是由地址的端口号部分表示的。每个服务由一个唯一的,熟知的端口号来提供。采用函数getservbyname可以将一个服务名字映射到一个端口号,函数getservbyport将一个端口号映射到一个服务名,或者采用函数getservent顺序扫描服务数据库。
#include <netdb.h>
struct servent *getservbyname(const char* name, const char *proto)
struct servent *getservbyport(int port,const char *proto)
struct servent *getservent()
void setservent(int stayopen)
void endservent()
结构servent至少包含如下成员:
struct servent{
    char *s_name;
char **s_aliases;
int s_port;
char *s_proto;//name of protocol

};

下面两个函数getaddrinfo和getnameinfo分别代替了原来的gethostbyaddr和gethostbyname函数

函数getaddrinfo允许将一个主机名字和服务名字映射到一个地址。
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *host,const char* service,const struct addrinfo* hint,struct addrinfo** res)
int 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;
};
函数getnameinfo将地址转换成主机名或这服务名。
#include <sys/socket.h>
#include <netdb.h>
int getnameinfo (const struct sockaddr *addr, socklen_t alen, char *host,socklen_t hostlen, char *service, socklen_t servlen, unsigned int flags); 
书本上有一个很详细的关于getaddrinfo函数用法的例程。
2.4、将套接字与地址绑定
用bind函数将地址绑定到一个套接字
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr *addr, socklen_t len)
对于因特网域,如果指定IP地址为INADDR_ANY,套接字端点可以被绑定到所有的系统网络接口。
可以调用函数getsockname来发现绑定到一个套接字的地址
#include <sys/socket.h>
int getsockname(int sockfd,struct sockaddr *addr,socklen_t *alenp)
调用getsockname之前,设置alenp为一个指向整数的指针,该整数指定缓冲区sockaddr的大小。
如果套接字已经和对方连接,调用getpeername来找到对方的地址
#include <sys/socket.h>
int getpeername(int sockfd,struct sockaddr *addr,socklen_t *alenp)


3、建立连接
在处理面向连接的网络服务(SOCK_STREAM或SOCK_SEQPACKET),在开始交换数据之前,需要在请求服务的进程套接字(客户端)和提供服务的进程套接字(服务器端)之间建立一个连接。
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *addr,socklen_t len)
服务器调用listen来宣告可以接受连接请求。
#include <sys/socket.h>
int listen(int sockfd,int backlog)
参数backlog提供一个提示,表示该进程所要入队的连接请求数量,实际值由系统决定,上限由#include <sys/socket.h>中SOMAXCONN指定
使用函数accept获得连接请求并建立连接
#include <sys/socket.h>
int accept(int sockfd,const struct sockaddr *addr,socklen_t len)


4、数据传输
既然套接字端点表示为文件描述符,那么只要建立连接,就可以使用read和write来通过套接字通信。尽管可以通过read和write交换数据,但这就是这两个函数所能做的一切,如果想指定选项,从多个客户端接收数据包或者发送外带数据,需要采用六个传递数据的套接字函数中的一个。
发送数据函数:
send函数,和write很像,但是可以指定标志来改变处理传输数据的方式。
#include <sys/socket.h>
ssize_t send(int sockfd,const void* buf, size_t nbytes, int flags)
参数buf和nbytes与write中的含义一致,不同的是send支持第四个参数flags
sendto函数,允许在无连接的套接字上指定一个目标地址
#include <sys/socket.h>
ssize_t sendto(int sockfd,const void* buf, size_t nbytes, int flags,const struct sockaddr *destaddr,socklen_t destlen)
sendmsg函数,类似于writev:
#include <sys/socket.h>
ssize_t sendmsg(int sockfd,const struct msghdr *msg, int flags)
数据结构msghdr成员:
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;
};
接收数据函数:
#include <sys/socket.h>
ssize_t recv(int sockfd,const void* buf, size_t nbytes, int flags)
#include <sys/socket.h>
ssize_t recvfrom(int sockfd,const void* buf, size_t nbytes, int flags,const struct sockaddr *destaddr,socklen_t destlen)
#include <sys/socket.h>
ssize_t recvmsg(int sockfd,const struct msghdr *msg, int flags)


5、套接字选项
设置套接字选项setsockopt函数
#include <sys/socket.h>
int setsockopt(int sockfd,int level,int option,const void* val, socklen_t len)
参数level标识了选项应用的协议,如果选项是通用的套接字层选项,level设置为SOL_SOCKET。否则,level设置成控制这个选项的协议号。
参数val根据选项的不同指向一个数据结构或一个整数。一些选项是on/off开关。如果整数非0,那么选项被启用。如果整数为0,那么该选项被禁止。参数len指定了val指向的对象大小。
函数getsockopt发现选项的当前值:
#include <sys/socket.h>
int getsockopt(int sockfd,int level,int option,const void* val, socklen_t len)
  























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值