总结《Linux高性能服务器编程》第5章
第五章 Linux网络编程基础API
socket地址API
-
socket最开始的含义是一个IP地址和端口对(ip,port),它唯一地表示了使用TCP通信的一端;
-
主机字节序和网络字节序
-
字节序分为大端字节序/网络字节序(big endian)和小端字节序/主机字节序(little endian);
-
大端字节序是指一个整数的高位字节(23~31 bit)存储在内存的低地址处,低位字节(0
~7 bit)存储在内存的高地址处; -
Linux提供了如下4个函数来完成主机字节序和网络字节序之间的转换
#include<netinet/in.h> unsigned long int htonl(unsigned long int hostlong); unsigned short int htons(unsigned short int hostshort); unsigned long int ntohl(unsigned long int netlong); unsigned short int ntohs(unsigned short int netshort);
-
-
通用socket地址
-
socket网络编程接口中表示socket地址的是结构体sockaddr以及sockaddr_storage;
#include<bits/socket.h> struct sockaddr { sa_family_t sa_family; //地址族类型(sa_family_t)的变量,与协议族类型对应 char sa_data[14]; //存放socket地址值 }
-
-
专用socket地址
- UNIX本地域协议族:结构体sockaddr_un;
- TCP/IP协议族:结构体sockaddr_in和sockaddr_in6;
- 所有专用socket地址(以及sockaddr_storage)类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr;
-
IP地址转换函数
-
将用点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的IPv4地址:
in_addr_t inet_addr(const char*strptr); int inet_aton(const char*cp, struct in_addr*inp); int inet_pton(int af,const char*src, void*dst);
-
将用网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址:
char*inet_ntoa(struct in_addr in); const char*inet_ntop(int af, const void*src, char*dst, socklen_t cnt);
-
创建socket
-
Linux中,所有东西都是文件,socket也是可读、可写、可控制、可关闭的文件描述符;
-
使用socket系统调用可创建一个socket
#include<sys/types.h> #include<sys/socket.h> int socket(int domain,int type,int protocol);
- domain参数告诉系统使用哪个底层协议族:PF_INET、PF_INET6、PF_UNIX;
- type参数指定服务类型:SOCK_STREAM、SOCK_UGRAM;
- protocol参数是在前两个参数构成的协议集合下,再选择一个具体的协议,默认设置为0;
命名socket
-
将一个socket与socket地址绑定称为给socket命名,系统调用是bind
#include<sys/types.h> #include<sys/socket.h> int bind(int sockfd, const struct sockaddr*my_addr, socklen_t addrlen);
- bind将my_addr所指的socket地址分配给未命名的sockfd文件描述符;
- 绑定时两种常见错误:
- EACCES,被绑定的地址是受保护的地址,仅超级用户能够访问;
- EADDRINUSE,被绑定的地址正在使用中;
监听socket
-
服务器通过listen调用来被动接受连接;
-
使用listen系统调用创建监听队列以存放待处理的客户连接
#include<sys/socket.h> int listen(int sockfd,int backlog);
- sockfd参数指定被监听的socket,backlog参数提示内核监听队列的最大长度,监听队列的长度如果超过backlog,服务器将不受理新的客户连接;
接受连接
-
accept系统调用从listen监听队列中接受一个连接
#include<sys/types.h> #include<sys/socket.h> int accept(int sockfd, struct sockaddr*addr, socklen_t*addrlen);
发起连接
-
客户端需要通过connect系统调主动与服务器建立连接;
#include<sys/types.h> #include<sys/socket.h> int connect(int sockfd, const struct sockaddr* serv_addr, socklen_t addrlen);
- 连接时两种常见错误:
- ECONNREFUSED,目标端口不存在,连接被拒绝;
- ETIMEDOUT,连接超时;
- 连接时两种常见错误:
关闭连接
-
关闭该连接对应的socket;
-
通过关闭普通文件描述符的系统调用来完成;
#include<unistd.h> int close(int fd);
- fd:file describeter,文件描述符
- 每次将fd的引用计数减1,只有当fd的引用计数为0时,才真正关闭连接;
-
无论如何都要立即终止连接,使用shutdown系统调用
#include<sys/socket.h> int shutdown(int sockfd,int howto);
数据读写
-
TCP数据读写
-
对文件的读写操作read和write同样适用于socket;
-
socket编程接口提供了专用于socket数据读写的系统调用:
#include<sys/types.h> #include<sys/socket.h> ssize_t recv(int sockfd,void*buf,size_t len,int flags); ssize_t send(int sockfd,const void*buf,size_t len,int flags);
-
-
UDP数据读写
-
socket编程接口中用于UDP数据报读写的系统调用
#include<sys/types.h> #include<sys/socket.h> ssize_t recvfrom(int sockfd, void*buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen); ssize_t sendto(int sockfd,const void*buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);
-
-
通用数据读写函数
-
TCP流数据/UDP数据报均可以使用
#include<sys/socket.h> ssize_t recvmsg(int sockfd,struct msghdr*msg,int flags); ssize_t sendmsg(int sockfd,struct msghdr*msg,int flags); //msg参数是msghdr结构体类型的指针
-
带外标记
-
内核通知应用程序带外数据到达的两种常见方式:I/O复用产生的异常事件和SIGURG信号;
-
使用sockatmark系统调用判断sockfd是否处于带外标记,即下一个被读取到的数据是否是带外数据
#include<sys/socket.h> int sockatmark(int sockfd);
地址信息函数
-
获取一个连接socket的本端以及远端的socket地址
#include<sys/socket.h> int getsockname(int sockfd, struct sockaddr*address, socklen_t* address_len); int getpeername(int sockfd, struct sockaddr*address, socklen_t* address_len);
socket选项
-
读取和设置socket文件描述符属性的方法
#include<sys/socket.h> int getsockopt(int sockfd,int level,int option_name, void* option_value, socklen_t* restrict option_len); int setsockopt(int sockfd,int level, int option_name, const void* option_value, socklen_t option_len);
- 常用socket选项
网络信息API
-
socket地址的两个要素,即IP地址和端口号,都是用数值表示的,不便于记忆和扩展;
-
可用使用网络信息API实现主机名到IP地址的转换等功能;
-
获取主机的完整信息
#include<netdb.h> struct hostent* gethostbyname(const char*name); struct hostent* gethostbyaddr(const void*addr, size_t len,int type);
- gethostbyname:根据主机名称获取主机的完整信息;
- gethostbyaddr:根据IP地址获取主机的完整信息;
-
获取主机的完整信息
#include<netdb.h> struct servent* getservbyname(const char*name, const char*proto); struct servent* getservbyport(int port, const char*proto);
- getservbyname:根据名称获取某个服务的完整信息;
- getservbyport:根据端口号获取某个服务的完整信息;
-
getaddrinfo
#include<netdb.h> int getaddrinfo(const char*hostname, const char*service,const struct addrinfo* hints, struct addrinfo** result);
- 既能通过主机名获得IP地址,也能通过服务器名获得端口号;
-
getnameinfo
#include<netdb.h> int getnameinfo(const struct sockaddr*sockaddr,socklen_t addrlen, char*host, socklen_t hostlen, char*serv, socklen_t servlen, int flags);
- 通过socket地址同时获得以字符串表示的主机名和服务器名;