socket是一种IPC方法。容许单台或经过网络链接的主句进程间交换数据linux
- 各应用程序建立一个socket
- 服务器将本身的socket绑定到一个都知道的地址上
fd=socket(domain, type, protocol)
编程
#include<sys/socket.h>
int socket(int domain, int type, int protocal) //return fd success, -1 error int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//return 0 s, -1 e
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
};
复制代码
socket缓存
- domain:识别出socket的地址格式,代表通讯范围
![]()
- type:
SOCKET_STREAM
(TCP)和SOCKET_DGRAM
(UDP)![]()
- protocol 通常为0
对于fd,
getsockname
,getpeername
服务器
#include<sys/socket.h>
int listen(int sockfd, int backlog);
//return 0 s, -1 e
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//return fd success, -1 error
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//return 0 s, -1 e
复制代码
主动和被动socket网络
- 一个主动socket经过connect 创建一个到被动socket的链接
- 一个被动socket经过listen 被标记成容许接入链接的socket
listen并发
- backlog: 若是在服务器调用accept前,客户端connect,会产生一个未决的链接,backlog限制这个数量,在这个数量内,客户端链接请求会当即成功,数量外的会阻塞到一个未决链接被accept,并从未决链接队列删除
accept负载均衡
- 会建立一个新的socket,这个新的socket会与connect另外一端的socket链接
- addr和addrlen在调用事后会为对端地址信息,若是不关心对端信息传入时设为NULL,0
#nclude<sys/socket.h>
ssize_t recvfrom(int sockfd, void *buffer, size_t length, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
//return num of bytes received, 0 EOF, -1 error
ssize_t recvfrom(int sockfd, void *buffer, size_t length, int flags, struct sockaddr *dest_addr, socklen_t *addrlen);
//return num of bytes send, -1 error
复制代码
- 客户端若是须要接受服务端发送的数据报的话还须要
bind
一下- 数据报能够
connect
以后调用write
和直接send_to
同样,只适用于发起connect
的一端,connect
以后内核会记录socket的对端,屡次调用connect后面的会修改前面的
struct sockaddr{
sa_family_t sa_family;
char sa_data[108];
};
const char *SOCKNAME = "/tmp/mysock";
int sfd;
struct sockaddr_un addr;
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sfd == -1)
errExit("socket");
/* Create socket */
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKNAME, sizeof(addr.sun_path) - 1);
if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1)
errExit("bind");
复制代码
- 没法将socket绑定到一个已有路径名上,bind会返回EADDRINUSE
- 没法open 打来一个socket
- 当不须要一个socket时使用unlink
- 若是sun_path的第一个字符为NULL字节,会建立抽象socket命名空间
- 无需担忧名字和文件系统冲突
- socket关闭后自动删除抽象名
- 权限无需写权限
#include<sys/socket.h>
int socketpair(int domain, int type, int protocol, int sockfd[2]);
复制代码
- domian只能是AF_UNIX
- 调用完sockpair以后,进程会fork一个子进程,子进程继承fd副本进行通讯
netstat -i
dom
- 因为端口号和IP地址须要被网络中的全部主机理解,须要统一标准字节序,即网络字节序,它是大端的
#include<arpa/inet.h>
// return netword byte order
uint16_t htons(uint16_t host_uint16);
uint32_t htonl(uint32_t host_uint32);
// return host byte order
uint16_t ntohs(uint16_t net_uint16);
uint32_t ntohl(uint32_t net_uint32);
复制代码
#include<netinet/in.h>
struct in_addr { /* IPv4 4-byte address */
in_addr_t s_addr; /* Unsigned 32-bit integer */
};
struct sockaddr_in { /* IPv4 socket address */
sa_family_t sin_family; /* Address family (AF_INET) */
in_port_t sin_port; /* Port number */
struct in_addr sin_addr; /* IPv4 address */
unsigned char __pad[X]; /* Pad to size of 'sockaddr' structure (16 bytes) */
};
struct in6_addr { /* IPv6 address structure */
uint8_t s6_addr[16]; /* 16 bytes == 128 bits */
};
struct sockaddr_in6 { /* IPv6 socket address */
sa_family_t sin6_family; /* Address family (AF_INET6) */
in_port_t sin6_port; /* Port number */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* Scope ID (new in kernel 2.4) */
};
复制代码
#include<arpa/inet.h>
// 二进制ip地址和可读ip地址('127.0.0.1')转换
int inet_pton(int domain, const char *src_str, void *addrptr);
//return 1 success, 0 格式str不催, -1 error
const char *inet_ntop(int domain, const void *addrptr, char *dst_str, size_t len);
//return dst_str的指针success, NULL error
复制代码
![]()
- 解析也是从顶级域名开始一级级解析
- 域名的补全规则定义在
/etc/resolv.conf
文件中,默认会用本机域名补全
/etc/services
记录了(IANA)端口号和服务名
#include<sys/socket.h>
#include<netdb.h>
int getaddrinfo(const char *host, const char *service, const struct addrinfo *hints, struct addrinfo **result);
//return 0 success, other error,可能须要向DNS服务器发请求
struct addrinfo {
int ai_flags; /* Input flags (AI_* constants) */
int ai_family; /* Address family (AF_INET | AF_INET6)*/
int ai_socktype; /* Type: SOCK_STREAM, SOCK_DGRAM */
int ai_protocol; /* Socket protocol */
size_t ai_addrlen; /* Size of structure pointed to by ai_addr */
char *ai_canonname; /* Canonical name of host */
struct sockaddr *ai_addr; /* Pointer to socket address structure */
struct addrinfo *ai_next; /* Next structure in linked list */
};
int freeaddrinfo(struct addrinfo *result);
//释放result分配的内存
int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, size_t hostlen, char *service, size_t servlen, int flags);
// 0 success, other fail,不想要获取的参数传NULL,len设为0
复制代码
getaddrinfosocket
- host 为主机名或者ip字符串
- service 为一个服务名或者十进制端口号
- hint指向的结构规定了,result返回的socket地址结构的标准。hint能够设置
ai_flags
,ai_family
,ai_socktype
,ai_protocol
字段用做过滤- result指向一个包含addrinfo结构链表的表头
![]()
- 预先建立进/线程池,动态改变池大小
- 单个进程处理多个客户端(I/O多路复用,信号驱动,或者epoll)
- 服务器集群
- DNS轮转负载共享,缓存,没法良好的负载均衡,没法确保高可用等一些问题
- 负载均衡
用来消除运行大量很是用服务器进程的须要tcp
- 监视一组指定套接字端口,按须要启动其它服务
- 简化了启动其它服务的编程工做
inetd
守护进程一般在系统启动时运行。以后执行以下步骤
- 在
/etc/inetd.conf
中指定的每项服务,inetd都会建立一个恰当类型的套接字,而后绑定到指定端口上,TCP的话会listen- 经过
select
监视select
阻塞直到有请求,在TCP中会先accept建立新的socket- inetd调用
fork
建立一个新的进程,exec()
启动服务器程序,在exec()
以前
- 除了socket描述符外,关闭其余继承的文件描述符
- 在0,1,2上复制套接字文件描述符,关闭套接字文件描述符自己(这样相应代码直接从标准输出输入读取写入客户端信息就行了)
- 为启动进程设定与用户和组ID
- 若是是tcp而且
accept
建立了socket fd,inetd关闭这个描述符- 继续
select
监视