一、引言
网络IPC:网络进程间通信(network IPC)
通过套接字网络IPC接口和其他进程通信。同样的接口既可以用于计算机间通信也可用于计算机内通信。
本章讨论仅限于因特网事实上的通信标准:TCP/IP协议栈。
二、套接字描述符
套接字是不同主机之间的进行进行双向通信的断点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
要访问套接字就要使用套接字描述符,就像访问文件就要使用文件描述符一样。
1、创建套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
返回值:成功则返回文件(套接字)描述符,出错返回-1.
domain确定通信的特性,包括地址格式。
AF_INET IPv4因特网域
AF_INET6 IPv6因特网域
AF_UNIX UNIX域
AF_UNSPEC 未指定
type确定套接字的类型,进一步确定通信特征。
SOCK_DGRAM 长度固定的、无连接的不可靠报文传递
SOCK_RAW IP协议的数据报接口(POSIX.1中为可选)
SOCK_SEQPACKET 长度固定、有序、可靠的面向连接报文传递
SOCK_STREAM有序、可靠、双面的面向连接字节流
protocol通常是0,表示按给定的域和套接字类型选择默认协议。AF_INET,SOCK_STREAM的默认是TCP(传输控制协议)。AF_INET,SOCK_DGRAM的默认是UDP(用户数据包协议).
2、禁止套接字上的输入、输出
#include <sys/socket.h>
int shutdown(int sockfd,int how);
如果how是SHUT_RD,就无法从套接字上读取数据,如果how是SHUT_WR,就无法使用套接字发送数据。使用SHUT_RDWR无法读取和发送数据。
三、寻址
两个标识:计算机的网络地址可以帮助标识网络上想与之通信的计算机;服务可以帮助标识计算机上特定的进程。
字节序:大端字节序(大地址代表低字节),小端字节序(大地址代表高字节)。linux是小端字节序。
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); //网络到主机短整数
h表示主机(host)字节序;n表示网络(net)字节序;l表示长整数;s表示短整数。
地址格式:
通用地址结构:
struct sockaddr{
sa_family_tsa_family;/*addr family*/
char sa_data[]; /*variable-length address*/
...
};
IPv4套接字地址:
struct in_addr{
in_addr_t s_addr; /*IPv4 addr:uint32_t*/
};
struct sockaddr_in{
sa_family_tsin_family;/*address family*/
in_port_t sin_port; /*port number:uint16_t*/
struct in_addr sin_addr;/*IPv4 address*/
};
IPv6套接字地址:
struct in6_addr{
uint8_t s6_addr[16]; /*IPv6 addrt*/
};
struct sockaddr_in6{
sa_family_tsin6_family;/*address family*/
in_port_t sin6_port; /*port number:uint16_t*/
uint32_t sin6_flowinfo; /*traffic class and flow info*/
struct in6_addr sin6_addr;/*IPv6 address*/
uint32_t sin6_scope_id; /*set of interfaces for scope*/
};
4计算机理解的地址格式和人理解的地址格式之间的转换
#include<arpa/inet.h>
const char *inet_ntop(int domain, const void *restrict addr, char *restrict str,socklen_t size); /*将网络字节序的二进制地址转化成为文本格式字符串*/
返回值:成功返回地址字符串指针,出错返回NULL
size指定了用以保存文本字符串的缓冲区的大小,INET_ADDRSTRLEN用来存放IPv4地址的文本字符串;INET6_ADDRSTRLEN用来存放IPv6地址的文本字符串
int inet_pton(int domain, const char *restrict str, void *restrict addr);/*将文本格式字符串转化成为网络字节序的二进制地址*/
返回值:成功则返回1,若格式无效则返回0,出错返回-1.
缓冲区addr要足够发来存放32位IPv4地址,或者128位IPv6地址。
参数domain仅支持两个值:AF_INET和AF_INET6。
地址查询:
5、找到给定计算机的主机信息
#include<netdb.h>
struct hostent *gethostent(void); /*如果主机文件没有打开,该函数会打开,返回文件的下一个条目*/
返回值:成功返回指针,错误返回NULL
void sethostent(int stayopen); /*打开文件,如果文件已经被打开,那么将其绕回*/
void endhostent(void); /*关闭文件*/
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; /*length in bytes of address*/
char **h_addr_list; /*pointer to array of network addresses*/
...
};
6、获得网络名字和网络号
#include<netdb.h>
struct netent *getnetbyaddr(uint32_t net,int type);
struct netent *getnetbyame(const char *name);
struct netent *getnetent(void);
以上三个函数返回值:成功返回指针,出错返回NULL
void setnetent(int stayopen);
void endnetent(void);
netent结构体:
struct netent{
char *n_name; /*network name*/
char **n_aliases; /*alternate network name array pointer*/
int n_addrtype; /*address type*/
uint32_t n_net;/*network number*/
...
};
7、协议名称和协议号可以采用以下的映射
#include <netdb.h>
struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
struct protoent *getprotoent(void);
以上函数的返回值:若成功返回指针,若出错返回NULL
void setprotoent(int stayopen);
void endprotoent(void);
struct protoent{
char *p_name;/*protocol name*/
char **p_aliases;/*pointer to alternate protocol name array*/
int p_proto;/*protocol number*/
...
};
8、
#include <netdb.h>
struct servent *getservbyname(cosnt char *name,const char *proto); /*将一个服务名字映射到一个端口号*/
struct servent *getservbyport(int port, const char *proto);/*将一个端口号映射到一个服务名*/
struct servent *getservent(void); /*顺序扫描服务数据库*/
以上三个函数的返回值:若成功返回指针,若出错返回NULL
struct setservent(int stayopen);
void endservent(void);
struct servent{
char *s_name; /*servce name*/
char **s_aliases; /*pointer to alternate service name array*/
int s_port; /*port number*/
char *s_proto; /*name of proto*/
...
};
9、
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *restrict host, /*将一个主机名字和服务名字映射到一个地址*/
cosnt char *restrict service,
const struct addrinfo *restrict hint,
struct addrinfo**restrict res);
返回值:若成功返回0,若出错返回非0错误码
void freeaddrinfo(struct addrinfo *ai);
struct addrinfo{
int ai_flags; /*customize behavior*/
int ai_family; /*address family*/
int ai_socktype; /*socket type*/
int ai_protocol; /*protocol*/
socklen_t ai_addrlen; /*length in bytes of address*/
struct sockaddr*ai_addr;/*address*/
char *ai_canonname; /*canonical name of host*/
struct addrinfo*ai_next;/*next in list*/
...
};
10、将地址转化为主机名或者服务名
#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);
返回值:若成功返回0,若出错返回非零的值。
四、将套接字与地址绑定
11、将地址绑定到一个套接字
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr *addr,socklen_t len);
返回值:若成功返回0,若失败返回-1
12、获得绑定到套接字的地址
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);
返回值:成功返回0,失败返回-1.
13、如果套接字已经和对方链接,调用getpeername来找到对方的地址
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);
返回值:若成功返回0,失败返回-1.
五、建立连接
14、建立连接
#include <sys/socket.h>
int connect (int sockfd, cosnt struct sockaddr restrict *addr,socklen_t len);
返回值:若成功返回-,失败返回-1.
addr为想与之通信的地址。
15、宣告可以接受连接请求
#include <sys/socket.h>
int listen (int sockfd, int backlog);
返回值:成功返回0,失败返回-1.
backlog:该进程所要入队的连接请求数量,一旦队列满,系统会拒绝多余连接的请求。
16、获得连接请求并建立连接
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *restrict addr,socklen_t *restrict len);
返回值:若成功返回文件(套接字)描述符,若失败返回-1.
六、数据传输
17、发送函数1
#include <sys/socket.h>
ssize_t send(int sockfd,const void *buf, size_t nbytes ,int flags);
返回值:成功返回发送到字节数,失败返回-1
18、发送函数2
#include <sys/socket.h>
ssize_t sendto(int sockfd, cosnt void *buf, size_t nbytes, int flags, cosnt sruct sockaddr *destaddr, socklen_t destlen);
返回值:成功返回发送的字节数,失败返回-1.
19、发送函数3
#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg,int flags);
返回值:若成功返回发送的字节数,若出错返回-1.
struct msghdr{
void *msg_name; /*optional address*/
socklen_t msg_namelen; /*address size in bytes*/
struct iovec*msg_iov;/*array of I/O buffers*/
int msg_iovlen; /*number of elements in array*/
void *msg_control; /*ancillary data*/
socketlen_tmsg_flags;/*flags for received message*/
...
};
20、接受数据1
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
返回值:以字节计数的消息长度,若无可用信息或对方已经按序结束则返回0,若出错则返回-1.
flags:套接字调用标志
MSG_OOB 如果协议支持,接收带外数据
MSG_PEEK 返回报文内容而不是真正取走报文
MSG_TRUNC 即使报文被截断,要求返回的是报文的实际长度
MSG_MAITALL 等待直到所有的数据可用
21、接受数据2
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
返回值:以字节计数的消息长度,若无可用信息或对方已经按序结束则返回0,否则返回-1.