高并发服务器
图 12.1: 并发服务器开发
12.1 多进程并发服务器
使用多进程并发服务器时要考虑以下几点: 1.父最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符) 2.系统内创建进程个数(内存大小相关) 3.进程创建过多是否降低整体服务性能(进程调度)
12.1.1 server
/* server.c */
#include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <sys/types.h> #include "wrap.h"
#define MAXLINE 80 #define SERV_PORT 8000
136 第12章 高并发服务器
void do_sigchild(int num) {
while (waitpid(0, NULL, WNOHANG) > 0) ;
}
int main(void) {
struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN]; int i, n;
pid_t pid;
struct sigaction newact; newact.sa_handler = do_sigchild; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; sigaction(SIGCHLD, &newact, NULL);
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(listenfd, 20);
printf("Accepting connections ...\n"); while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
pid = fork(); if (pid == 0) {
Close(listenfd); while (1) {
n = Read(connfd, buf, MAXLINE); if (n == 0) {
printf("the other side has been closed.\n");
break; }
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
for (i = 0; i < n; i++) buf[i] = toupper(buf[i]);
Write(connfd, buf, n); }
Close(connfd); return 0;
12.1.2 client
12.1节 多进程并发服务器 137
}
else if (pid > 0) {
Close(connfd); }
else perr_exit("fork");
} Close(listenfd); return 0;
}
/* client.c */
#include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h"
#define MAXLINE 80 #define SERV_PORT 8000
int main(int argc, char *argv[]) {
struct sockaddr_in servaddr; char buf[MAXLINE];
int sockfd, n;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break; }
else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0; }
138 第12章 高并发服务器12.2 多线程并发服务器
在使用线程模型开发服务器时需考虑以下问题: 1.调整进程内最大文件描述符上限 2.线程如有共享数据,考虑线程同步 3.服务于客户端线程退出时,退出处理。(退出值,分离态) 4.系统负载,随着链接客户端增加,导致其它线程不能及时得到CPU
12.2.1 server
/* server.c */
#include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> #include "wrap.h"
#define MAXLINE 80 #define SERV_PORT 8000 struct s_info {
struct sockaddr_in cliaddr;
int connfd; };
void *do_work(void *arg) {
int n,i;
struct s_info *ts = (struct s_info*)arg; char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
/* 可以在创建线程前设置线程创建属性,设为分离态,哪种效率高内? */ pthread_detach(pthread_self());
while (1) {
n = Read(ts->connfd, buf, MAXLINE); if (n == 0) {
printf("the other side has been closed.\n");
break; }
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)), ntohs((*ts).cliaddr.sin_port));
for (i = 0; i < n; i++) buf[i] = toupper(buf[i]);
Write(ts->connfd, buf, n); }
Close(ts->connfd); }
int main(void) {
struct sockaddr_in servaddr, cliaddr;
12.2.2 client
12.2节 多线程并发服务器 139
socklen_t cliaddr_len; int listenfd, connfd; int i = 0;
pthread_t tid;
struct s_info ts[383];
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(listenfd, 20);
printf("Accepting connections ...\n"); while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); ts[i].cliaddr = cliaddr;
ts[i].connfd = connfd;
/* 达到线程最大数时,pthread_create出错处理, 增加服务器稳定性 */ pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
i++;
}
return 0; }
/* client.c */
#include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h"
#define MAXLINE 80 #define SERV_PORT 8000
int main(int argc, char *argv[]) {
struct sockaddr_in servaddr; char buf[MAXLINE];
int sockfd, n;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
140 第12章 高并发服务器
servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0)
printf("the other side has been closed.\n"); else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0; }
12.3 多路I/O转接服务器
12.3.1 三种模型性能分析 12.3.2 select
1.select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开 的文件描述符个数并不能改变select监听文件个数
2.解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用 的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力
#include <sys/select.h>
/* According to earlier standards */ #include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态 readfds:监控有读数据到达文件描述符集合,传入传出参数 writefds:监控写数据到达文件描述符集合,传入传出参数 exceptfds:监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数 timeout:定时阻塞监控时间,3种情况
1.NULL,永远等下去
2.设置timeval,等待固定时间 3.设置timeval里时间均为0,检查描述字后立即返回,轮询
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */ };
12.3节 多路I/O转接服务器 141
void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set);
把文件描述符集合里fd清0 测试文件描述符集合里fd是否置1 把文件描述符集合里fd位置1 把文件描述符集合里所有位清0
/* server.c */
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include "wrap.h"
#define MAXLINE 80 #define SERV_PORT 8000
int main(int argc, char *argv[]) {
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE]; ssize_t n;
fd_set rset, allset;
char buf[MAXLINE];
/* FD_SETSIZE 默认为 1024 */
char str[INET_ADDRSTRLEN]; socklen_t cliaddr_len; struct sockaddr_in
/* #define INET_ADDRSTRLEN 16 */ cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
Listen(listenfd, 20);
maxfd = listenfd; maxi = -1;
/* 默认最大128 */
/* 初始化 */
/* client[]的下标 */
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* 用-1初始化client[] */
FD_ZERO(&allset);
FD_SET(listenfd, &allset); /* 构造select监控文件描述符集 */
for ( ; ; ) {
rset = allset; /* 每次循环时都从新设置select监控信号集 */ nready = select(maxfd+1, &rset, NULL, NULL, NULL);
142 第12章 高并发服务器
if (nready < 0) perr_exit("select error");
if (FD_ISSET(listenfd, &rset)) { /* new client connection */
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
for (i = 0; i < FD_SETSIZE; i++) if (client[i] < 0) {
client[i] = connfd; /* 保存accept返回的文件描述符到client[]里 */
break; }
/* 达到select能监控的文件个数上限 1024 */ if (i == FD_SETSIZE) {
fputs("too many clients\n", stderr);
exit(1); }
FD_SET(connfd, &allset); /* 添加一个新的文件描述符到监控信号集里 */ if (connfd > maxfd)
maxfd = connfd; /* select第一个参数需要 */ if (i > maxi)
maxi = i;
if (--nready == 0) continue;
处理完的就绪文件描述符 */ }
for (i = 0; i <= maxi; i++) {
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
/* 当client关闭链接时,服务器端也关闭对应链接 */ Close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
} else { int j;
for (j = 0; j < n; j++) buf[j] = toupper(buf[j]);
Write(sockfd, buf, n); }
if (--nready == 0) break;
} }
} close(listenfd);
/* 更新client[]最大下标值 */
/* 如果没有更多的就绪文件描述符继续回到上面select阻塞监听,负责处理未
/* 检测哪个clients 有数据就绪 */
/* 解除select监控此文件描述符 */
server
12.3节 多路I/O转接服务器 143
return 0; }
/* client.c */
#include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h"
#define MAXLINE 80 #define SERV_PORT 8000
int main(int argc, char *argv[]) {
struct sockaddr_in servaddr; char buf[MAXLINE];
int sockfd, n;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0)
printf("the other side has been closed.\n"); else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0; }
client
pselect 给出pselect原型,此模型用的不多,有需要的同学可参考select模型自行编写C/ S
144 第12章 高并发服务器
#include <sys/select.h>
int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);
struct timespec {
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */ };
用sigmask替代当前进程的阻塞信号集,调用返回后还原原有阻塞信号集
12.3.3 poll
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd { int fd; short events; short revents;
};
/* 文件描述符 */
/* 监控的事件 */
/* 监控事件中满足条件返回的事件 */
POLLIN普通或带外优先数据可读,即POLLRDNORM | POLLRDBAND POLLRDNORM-数据可读
POLLRDBAND-优先级带数据可读
POLLPRI 高优先级可读数据
POLLOUT普通或带外数据可写 POLLWRNORM-数据可写 POLLWRBAND-优先级带数据可写
POLLERR 发生错误
POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件
nfds 监控数组中有多少文件描述符需要被监控
timeout 毫秒级等待
-1:阻塞等,#define INFTIM -1 Linux中没有定义此宏
0:立即返回,不阻塞进程 >0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值
如果不再监控某个文件描述符时,可以把pollfd中,fd设置为-1,poll不再监控此 pollfd,下次返回时,把revents设置为0。
ppoll GNU定义了ppoll(非POSIX标准),可以支持设置信号屏蔽字,大家可参考poll模型自 行实现C/S
12.3节 多路I/O转接服务器 145
#define _GNU_SOURCE /* See feature_test_macros(7) */ #include <poll.h>
int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts, const sigset_t *sigmask);
/* server.c */
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <poll.h> #include <errno.h> #include "wrap.h"
#define MAXLINE 80 #define SERV_PORT 8000 #define OPEN_MAX 1024
int main(int argc, char *argv[]) {
int
int
ssize_t
char buf[MAXLINE], str[INET_ADDRSTRLEN]; socklen_t clilen;
struct pollfd client[OPEN_MAX];
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(listenfd, 20);
client[0].fd = listenfd;
client[0].events = POLLRDNORM; /* listenfd监听普通读事件 */
i, j, maxi, listenfd, connfd, sockfd; nready;
n;
for (i = 1; i < OPEN_MAX; i++) client[i].fd = -1;
maxi = 0;
for ( ; ; ) {
nready = poll(client, maxi+1, -1);
/* 用-1初始化client[]里剩下元素 */
/* client[]数组有效元素中最大元素下标 */
/* 阻塞 */
146 第12章 高并发服务器
} }
}
return 0; }
if (client[0].revents & POLLRDNORM) { /* 有客户端链接请求 */ clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
}
/* 更新client[]中最大元素下标 */
/* 没有更多就绪事件时,继续回到poll阻塞 */
/* 检测client[] */
for (i = 1; i < OPEN_MAX; i++) if (client[i].fd < 0) { client[i].fd = connfd;
/* 找到client[]中空闲的位置,存放accept返回的connfd */
break; }
if (i == OPEN_MAX) perr_exit("too
client[i].events = if (i > maxi)
maxi = i;
if (--nready <= 0) continue;
many clients");
POLLRDNORM; /* 设置刚刚返回的connfd,监控读事件 */
for (i = 1; i <= maxi;
if ( (sockfd = client[i].fd) < 0)
i++) {
continue;
if (client[i].revents & (POLLRDNORM | POLLERR)) {
if ( (n = Read(sockfd, buf, MAXLINE)) < 0) {
if (errno == ECONNRESET) { /* 当收到 RST标志时 */
/* connection reset by client */ printf("client[%d] aborted connection\n", i); Close(sockfd);
client[i].fd = -1;
} else
perr_exit("read error");
} else if (n == 0) {
/* connection closed by client */ printf("client[%d] closed connection\n", i); Close(sockfd);
client[i].fd = -1;
} else {
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]); Writen(sockfd, buf, n);
}
if (--nready <= 0) break;
/* no more readable descriptors */
server
12.3节 多路I/O转接服务器 147
/* client.c */
#include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h"
#define MAXLINE 80 #define SERV_PORT 8000
int main(int argc, char *argv[]) {
struct sockaddr_in servaddr; char buf[MAXLINE];
int sockfd, n;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0)
printf("the other side has been closed.\n"); else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0; }
client
12.3.4 epoll
epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并 发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结 果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点 原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件 异步唤醒而加入Ready队列的描述符集合就行了。
目前epell是linux大规模并发网络程序中的热门首选模型。
epoll除了提供select/poll那种IO事件的电平触发(Level Triggered)外,还提 供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少
148 第12章 高并发服务器epoll_wait/epoll_pwait的调用,提高应用程序效率。
一个进程打开大数目的socket描述符
设置最大打开文件描述符限制
图 12.2: 最大打开文件个数设置
epoll API
1.创建一个epoll句柄,参数size用来告诉内核监听的文件描述符个数,跟内存大小有 关 #include
2.控制某个epoll监控的文件描述符上的事件:注册、修改、删除。
cat /proc/sys/fs/file-max
sudo vi /etc/security/limits.conf 写入以下配置,soft软限制,hard硬限制
* soft nofile 65536
* hard nofile 100000
int epoll_create(int size) size:告诉内核监听的数目
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd:为epoll_creat的句柄
op:表示动作,用3个宏来表示: EPOLL_CTL_ADD(注册新的fd到epfd), EPOLL_CTL_MOD(修改已经注册的fd的监听事件), EPOLL_CTL_DEL(从epfd删除一个fd);
event:告诉内核需要监听的事件 struct epoll_event {
__uint32_t events; /* Epoll events */
3.等待所监控文件描述符上有事件的产生,类似于select()调用。
12.3节 多路I/O转接服务器 149
epoll_data_t data; /* User data variable */ };
typedef union epoll_data { void *ptr;
int fd; uint32_t u32; uint64_t u64;
} epoll_data_t;
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭) EPOLLOUT:表示对应的文件描述符可以写
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来) EPOLLERR:表示对应的文件描述符发生错误
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来
说的 要再次把这个socket加入到EPOLL队列里
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) events:用来从内核得到事件的集合, maxevents:告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size, timeout:是超时时间
-1:阻塞 0:立即返回,非阻塞
>0:指定微秒 返回值:成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <errno.h> #include "wrap.h"
#define MAXLINE 80 #define SERV_PORT 8000 #define OPEN_MAX 1024
int main(int argc, char *argv[]) {
int i, j, maxi, listenfd, connfd, sockfd;
150 第12章 高并发服务器
int nready, efd, res;
ssize_t n;
char buf[MAXLINE], str[INET_ADDRSTRLEN]; socklen_t clilen;
int client[OPEN_MAX];
struct sockaddr_in cliaddr, servaddr; struct epoll_event tep, ep[OPEN_MAX];
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
Listen(listenfd, 20);
for (i = 0; i < OPEN_MAX; i++) client[i] = -1;
maxi = -1;
efd = epoll_create(OPEN_MAX);
if (efd == -1) perr_exit("epoll_create");
tep.events = EPOLLIN; tep.data.fd = listenfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep); if (res == -1)
perr_exit("epoll_ctl");
for ( ; ; ) {
nready = epoll_wait(efd, ep, OPEN_MAX, -1); if (nready == -1)
perr_exit("epoll_wait"); for (i = 0; i < nready; i++) {
if (!(ep[i].events & EPOLLIN)) continue;
/* 阻塞监听 */
if (ep[i].data.fd == listenfd) {
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
printf("received from %s at PORT %d \n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
for (j = 0; j < OPEN_MAX; j++) if (client[j] < 0) {
client[j] = connfd;
break; }
if (j == OPEN_MAX)
perr_exit("too many clients");
if (j > maxi) maxi = j;
/* save descriptor */
/* max index in client[] array */
server
12.3节 多路I/O转接服务器 151
tep.events = EPOLLIN; tep.data.fd = connfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep); if (res == -1)
perr_exit("epoll_ctl");
}
else {
sockfd = ep[i].data.fd;
n = Read(sockfd, buf, MAXLINE); if (n == 0) {
for (j = 0; j <= maxi; j++) { if (client[j] == sockfd) {
client[j] = -1;
break; }
}
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); if (res == -1)
perr_exit("epoll_ctl"); Close(sockfd);
printf("client[%d] closed connection\n", j); }
else {
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]); Writen(sockfd, buf, n);
} }
} }
close(listenfd); close(efd); return 0;
}
/* client.c */
#include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h"
#define MAXLINE 80 #define SERV_PORT 8000
int main(int argc, char *argv[]) {
struct sockaddr_in servaddr; char buf[MAXLINE];
int sockfd, n;
152 第12章 高并发服务器
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0)
printf("the other side has been closed.\n"); else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0; }
client
12.4 再议epoll
EPOLL事件有两种模型:
Edge Triggered (ET) 边缘触发只有数据到来,才触发,不管缓存区中是否还有数据。 Level Triggered (LT) 水平触发只要有数据都会触发。
例如: 1.我们已经把一个用来从管道中读取数据的文件描述符(RFD)添加到epoll描述符 2.这个时候从管道的另一端被写入了2KB的数据 3.调用epoll_wait,并且它会返回RFD,说明它已经准备好读取操作 4.然后我们读取了1KB的数据
5.调用epoll_wait......
Edge Triggered 工作模式: 如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调
用epoll_wait之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数 据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某 个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍 在存在于文件输入缓冲区内的剩余数据。epoll工作在ET模式的时候,必须使用非阻塞套接 口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最 好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。
i 基于非阻塞文件句柄
ii 只有当read或者write返回EAGAIN(非阻塞读,暂时无数据)时才需要挂起,等待。但 这并不是说每次read时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成, 当read返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据 了,也就可以认为此事读事件已处理完成。
Level Triggered 工作模式:
相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll,并且无 论后面的数据是否被使用,因此他们具有同样的职能。
详细解释ET, LT:
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这 种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操 作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要 小一点。传统的select/poll都是这种模型的代表.
ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描 述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就 绪,并且不会再为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个fd作IO 操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once).
实例一:基于管道epoll ET触发模式
12.4节 再议epoll 153
#include <stdio.h> #include <stdlib.h> #include <sys/epoll.h> #include <errno.h> #include <unistd.h>
#define MAXLINE 10
int main(int argc, char *argv[]) {
int efd, i;
int pfd[2];
pid_t pid;
char buf[MAXLINE], ch = 'a';
pipe(pfd); pid = fork();
if (pid == 0) { close(pfd[0]);
while (1) {
for (i = 0; i < MAXLINE/2; i++)
buf[i] = ch; buf[i-1] = '\n';
ch++;
for (; i < MAXLINE; i++)
buf[i] = ch; buf[i-1] = '\n';
ch++;
write(pfd[1], buf, sizeof(buf)); sleep(2);
}
close(pfd[1]); }
else if (pid > 0) {
struct epoll_event event;
struct epoll_event resevent[10]; int res, len;
154 第12章 高并发服务器
close(pfd[1]);
efd = epoll_create(10);
/* ET 边沿触发 ,默认是水平触发 */ event.events = EPOLLIN | EPOLLET;
/* event.events = EPOLLIN; */
event.data.fd = pfd[0];
epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event); while (1) {
res = epoll_wait(efd, resevent, 10, -1); printf("res %d\n", res);
if (resevent[0].data.fd == pfd[0]) {
len = read(pfd[0], buf, MAXLINE/2);
write(STDOUT_FILENO, buf, len); }
} close(pfd[0]); close(efd);
}
else {
perror("fork");
exit(-1); }
return 0; }
实例二:基于网络CS模型的epoll ET触发模式 server
/* server.c */
#include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/epoll.h> #include <unistd.h>
#define MAXLINE 10 #define SERV_PORT 8000
int main(void) {
struct sockaddr_in socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN]; int i, efd;
servaddr, cliaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
client
12.4节 再议epoll 155
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 20);
struct epoll_event event;
struct epoll_event resevent[10]; int res, len;
efd = epoll_create(10);
/* ET 边沿触发 ,默认是水平触发 */
event.events = EPOLLIN | EPOLLET;
/* event.events = EPOLLIN; */
printf("Accepting connections ...\n");
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port)); event.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); while (1) {
res = epoll_wait(efd, resevent, 10, -1); printf("res %d\n", res);
if (resevent[0].data.fd == connfd) {
len = read(connfd, buf, MAXLINE/2);
write(STDOUT_FILENO, buf, len); }
}
return 0; }
/* client.c */
#include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h>
#define MAXLINE 10
#define SERV_PORT 8000
int main(int argc, char *argv[]) {
struct sockaddr_in servaddr; char buf[MAXLINE];
int sockfd, i;
char ch = 'a';
156 第12章 高并发服务器
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (1) {
for (i = 0; i < MAXLINE/2; i++)
buf[i] = ch; buf[i-1] = '\n';
ch++;
for (; i < MAXLINE; i++)
buf[i] = ch; buf[i-1] = '\n';
ch++;
write(sockfd, buf, sizeof(buf)); sleep(10);
}
Close(sockfd);
return 0; }
实例三:基于网络CS非阻塞模型的epoll ET触发模式 server
/* server.c */
#include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/epoll.h> #include <unistd.h> #include <fcntl.h>
#define MAXLINE 10 #define SERV_PORT 8000
int main(void) {
struct sockaddr_in socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN]; int i, efd, flag;
servaddr, cliaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
12.5节 线程池并发服务器 157
bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 20);
struct epoll_event event;
struct epoll_event resevent[10]; int res, len;
efd = epoll_create(10);
/* ET 边沿触发 ,默认是水平触发 */
event.events = EPOLLIN | EPOLLET;
/* event.events = EPOLLIN; */
printf("Accepting connections ...\n");
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port)); flag = fcntl(connfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(connfd, F_SETFL, flag);
event.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); while (1) {
printf("epoll_wait begin\n");
res = epoll_wait(efd, resevent, 10, -1); printf("epoll_wait end res %d\n", res); if (resevent[0].data.fd == connfd) {
while ((len = read(connfd, buf, MAXLINE/2)) >0 ) write(STDOUT_FILENO, buf, len);
} }
return 0; }
client同实例二相同
12.5 线程池并发服务器
1.预先创建阻塞于accept多线程,使用互斥锁上锁保护accept 2.预先创建多线程,由主线程调用accept
12.6 UDP局域网服务器
1.读出每一个客户端发送过来的数据包,然后fork出子进程,由子进程去处理客户端请 求。
158 第12章 高并发服务器
2.客户端与服务器端交换多个数据报,服务器为每一个客户端链接创建新的socket,在 其上bind一个临时端口,然后用该socket处理对应客户端上的所有应答,这个办法要求在客 户查看服务器第一个应答中的源端口号。然后后面利用此端口号和服务器进行交互。
注意:UDP是有可能出现接收缓冲区满,再接收数据时丢包。
12.6.1 多播
组播组可以是永久的也可以是临时的。组播组地址中,有一部分由官方分配的,称为永 久组播组。永久组播组保持不变的是它的ip地址,组中的成员构成可以发生变化。永久组播 组中成员的数量都可以是任意的,甚至可以为零。那些没有保留下来供永久组播组使用的ip 组播地址,可以被临时组播组利用。
ip ad 查看网卡编号 if_nametoindex
12.6.2 server
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); addr中端口为0,则使用临时端口号
1.改变接收缓冲区大小。 int n = 220x1024
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)); 2.服务器应用层设计流量控制,控制发送数据速度。
224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议 使用;
224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet; 224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效; 239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <net/if.h> #define SERVER_PORT 8000 #define CLIENT_PORT 9000
12.6.3 client
12.6节 UDP局域网服务器 159
#define MAXLINE 1500 #define GROUP "239.0.0.2"
int main(void) {
//
bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
/*设置组地址*/
inet_pton(AF_INET, GROUP, &group.imr_multiaddr); /*本地任意IP*/
inet_pton(AF_INET, "0.0.0.0", &group.imr_address); /* eth0 --> 编号 命令:ip ad */ group.imr_ifindex = if_nametoindex("eth0");
setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group));
/*构造 client 地址 IP+端口 */
bzero(&clientaddr, sizeof(clientaddr)); clientaddr.sin_family = AF_INET; /* IPv4 */ inet_pton(AF_INET, GROUP, &clientaddr.sin_addr.s_addr); clientaddr.sin_port = htons(CLIENT_PORT);
while (1) {
fgets(buf, sizeof(buf), stdin);
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr));
sleep(1); }
close(sockfd);
int sockfd, i ;
struct sockaddr_in serveraddr, clientaddr; char buf[MAXLINE] = "itcast\n";
char ipstr[INET_ADDRSTRLEN];
socklen_t clientlen;
ssize_t len;
struct ip_mreqn group;
/* 构造用于UDP通信的套接字 */
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; /* IPv4 */ serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serveraddr.sin_port = htons(SERVER_PORT);
return 0; }
/* 16 Bytes */
/* 本地任意IP INADDR_ANY = 0 */
#include <netinet/in.h> #include <stdio.h> #include <sys/types.h>
160 第12章 高并发服务器
#include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <stdlib.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <net/if.h>
#define SERVER_PORT 8000 #define MAXLINE 4096
#define CLIENT_PORT 9000 #define GROUP "239.0.0.2"
int main(int argc, char *argv[]) {
struct sockaddr_in serveraddr, localaddr; int confd;
ssize_t len;
char buf[MAXLINE];
/* 组播结构体 */ struct ip_mreqn group;
//1.创建一个socket
confd = socket(AF_INET, SOCK_DGRAM, 0);
//2.初始化本地端地址
bzero(&localaddr, sizeof(localaddr));
localaddr.sin_family = AF_INET;
inet_pton(AF_INET, "0.0.0.0" , &localaddr.sin_addr.s_addr); localaddr.sin_port = htons(CLIENT_PORT);
bind(confd, (struct sockaddr *)&localaddr, sizeof(localaddr));
/*设置组地址*/
inet_pton(AF_INET, GROUP, &group.imr_multiaddr); /*本地任意IP*/
inet_pton(AF_INET, "0.0.0.0", &group.imr_address); /* eth0 --> 编号 命令:ip ad */ group.imr_ifindex = if_nametoindex("eth0");
/*设置client 加入多播组 */
setsockopt(confd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));
while (1) {
len = recvfrom(confd, buf, sizeof(buf), 0, NULL, 0); write(STDOUT_FILENO, buf, len);
}
close(confd);
return 0; }
12.7 socket IPC
socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机 制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过 loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协 议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷 贝到另一个进程。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯 设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP, 但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。
UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越 性,目前已成为使用最广泛的IPC机制,比如X Window服务器和GUI程序之间就是通过UNIX Domain Socket通讯的。
使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创 建 一 个 socket 文 件 描 述 符,address family 指 定 为 AF_UNIX,type 可 以 选 择 SOCK_DGRAM 或 SOCK_STREAM,protocol参数仍然指定为0即可。
UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体 sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地 址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果 调用bind()时该文件已存在,则bind()错误返回。
以下程序将UNIX Domain socket绑定到一个地址。
12.7.1 server
12.7节 socket IPC 161
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); #define offsetof(TYPE, MEMBER) ((int)&((TYPE *)0)->MEMBER)
#include <stdlib.h> #include <stdio.h> #include <stddef.h> #include <sys/socket.h> #include <sys/un.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h>
#define QLEN 10
/*
* Create a server endpoint of a connection. * Returns fd if all OK, <0 on error.
*/
int serv_listen(const char *name) {
int fd, len, err, rval; struct sockaddr_un un;
162 第12章 高并发服务器
/* create a UNIX domain stream socket */
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return(-1);
unlink(name); /* in case it already exists */
/* fill in socket address structure */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
/* bind the name to the descriptor */
if (bind(fd, (struct sockaddr *)&un, len) < 0) {
rval = -2;
goto errout; }
if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */ rval = -3;
goto errout; }
return(fd);
errout:
err = errno;
close(fd); errno = err; return(rval);
}
int serv_accept(int listenfd, uid_t *uidptr) {
int clifd, len, err, rval; time_t staletime;
struct sockaddr_un un;
struct stat statbuf;
len = sizeof(un);
if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
return(-1); /* often errno=EINTR, if signal caught */
/* obtain the client's uid from its calling address */
len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */ un.sun_path[len] = 0; /* null terminate */
if (stat(un.sun_path, &statbuf) < 0) { rval = -2;
goto errout; }
if (S_ISSOCK(statbuf.st_mode) == 0) { rval = -3; /* not a socket */ goto errout;
}
if (uidptr != NULL)
*uidptr = statbuf.st_uid; /* return uid of caller */
12.7节 socket IPC 163
unlink(un.sun_path); return(clifd);
errout:
err = errno;
close(clifd); errno = err; return(rval);
}
int main(void) {
int lfd, cfd, n, i; uid_t cuid;
char buf[1024];
/* we're done with pathname now */
lfd = serv_listen("foo.socket"); if (lfd < 0) {
switch (lfd) {
case -3:perror("listen"); break; case -2:perror("bind"); break; case -1:perror("socket"); break;
}
exit(-1); }
cfd = serv_accept(lfd, &cuid); if (cfd < 0) {
switch (cfd) {
case -3:perror("not a socket"); break; case -2:perror("a bad filename"); break; case -1:perror("accept"); break;
}
exit(-1); }
while (1) { r_again:
n = read(cfd, buf, 1024); if (n == -1) {
if (errno == EINTR) goto r_again;
}
else if (n == 0) {
printf("the other side has been closed.\n");
break; }
for (i = 0; i < n; i++) buf[i] = toupper(buf[i]);
write(cfd, buf, n); }
close(cfd); close(lfd); return 0;
}
164 第12章 高并发服务器12.7.2 client
#include <stdio.h> #include <stdlib.h> #include <stddef.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/socket.h> #include <sys/un.h> #include <errno.h>
#define CLI_PATH "/var/tmp/" /* +5 for pid = 14 chars */
/*
* Create a client endpoint and connect to a server. * Returns fd if all OK, <0 on error.
*/
int cli_conn(const char *name) {
int fd, len, err, rval; struct sockaddr_un un;
/* create a UNIX domain stream socket */
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return(-1);
/* fill socket address structure with our address */
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());
len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
unlink(un.sun_path); /* in case it already exists */ if (bind(fd, (struct sockaddr *)&un, len) < 0) {
rval = -2;
goto errout; }
/* fill socket address structure with server's address */ memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name); if (connect(fd, (struct sockaddr *)&un, len) < 0) {
rval = -4;
goto errout; }
return(fd);
errout:
err = errno;
close(fd); errno = err; return(rval);
12.8 其它常用函数
12.8.1 名字与地址转换
过时,仅用于IPv4,线程不安全 gethostbyname
12.8节 其它常用函数 165
}
int main(void) {
int fd, n;
char buf[1024];
fd = cli_conn("foo.socket"); if (fd < 0) {
switch (fd) {
case -4:perror("connect"); break; case -3:perror("listen"); break; case -2:perror("bind"); break; case -1:perror("socket"); break;
}
exit(-1); }
while (fgets(buf, sizeof(buf), stdin) != NULL) { write(fd, buf, strlen(buf));
n = read(fd, buf, sizeof(buf)); write(STDOUT_FILENO, buf, n);
} close(fd); return 0;
}
#include <stdio.h> #include <netdb.h> #include <arpa/inet.h> extern int h_errno;
int main(int argc, char *argv[]) {
struct hostent *host;
char str[128];
host = gethostbyname(argv[1]); printf("%s\n", host->h_name);
while (*(host->h_aliases) != NULL) printf("%s\n", *host->h_aliases++);
switch (host->h_addrtype) { case AF_INET:
while (*(host->h_addr_list) != NULL)
printf("%s\n", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str)));
166 第12章 高并发服务器
break; default:
printf("unknown address type\n");
break; }
return 0; }
gethostbyaddr
此函数只能获取域名解析服务器的url和/etc/hosts里登记的IP对应的域名。
#include <stdio.h> #include <netdb.h> #include <arpa/inet.h> extern int h_errno;
int main(int argc, char *argv[]) {
struct hostent *host; char str[128];
struct in_addr addr;
inet_pton(AF_INET, argv[1], &addr);
host = gethostbyaddr((char *)&addr, 4, AF_INET); printf("%s\n", host->h_name);
while (*(host->h_aliases) != NULL) printf("%s\n", *host->h_aliases++);
switch (host->h_addrtype) { case AF_INET:
while (*(host->h_addr_list) != NULL)
printf("%s\n", inet_ntop(AF_INET, (*host->h_addr_list++), str, sizeof(str)));
break; default:
printf("unknown address type\n");
break; }
return 0; }
getservbyname
getservbyport 根据服务程序名字或端口号获取信息,用的不多,以后用到的时候再来查询 getaddrinfo
getnameinfo
freeaddrinfo
趋势,可同时处理IPv4和IPv6,线程安全
12.8.2 套街口和地址关联
getsockname
根据accpet返回的sockfd,得到临时端口号
getpeername 根据accpet返回的sockfd,得到远端链接的端口号,在exec后可以获取客户端信息。