面向连接的服务SOCK_STREAM、SOCK_SEQPACKET,在开始交换数据之前,需要在请求服务的进程套接字和提供服务的进程套接字之间建立一个连接:客户端通过调用connect.
int connect(int sockfd const struct *addr, socklen_t len);
在connect中所指定的地址是想与之通信的服务器地址,如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址。
apue上一个面向连接的例子:
int connect_retry(int sockfd, const struct sockaddr *addr, socklen_t alen, int n) /* n是尝试的时间*/ { int nsec; int nsleep = 2<<n; for(nsec = 1; nsec <= nsleep; nsec <<= 1) { if(connect(sockfd, addr, alen) == 0) return 0; if(nsec<= nsleep/2) sleep(nsec); } return (-1); }服务器进程调用listen宣告可以接受客户端的connect请求,函数原型:
int listen(int sockfd, int backlog); /*backlog 是服务器可以响应客户端请求的最大连接数*/the upper limit is specified as SOMAXCONN in <sys/socket.h>.
在服务器进程调用listen之后,套接字socket可以通过调用accept得到connect请求并建立连接。
int accept(int sockfd, struct sockaddr *addr, socklen_t n);
返回值是一个套接字描述符,连接到调用connect的客户端。这个新的套接字与原始的即accept函数sockfd具有相同的类型和地址族。原始的sockfd并没有关联这个连接,
而是继续保持可用的状态,并用于接收其他客户端连接请求。
如果没有请求处理, accept则会一直阻塞到一个连接请求的到,如果sockfd是非阻塞的,则accept返回-1。
apue的服务器套接字初始端点函数:
#include "apue.h" #include <errno.h> #include <sys/socket.h> int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen) { int fd; int err = 0; if ((fd = socket(addr->sa_family, type, 0)) < 0) return(-1); if (bind(fd, addr, alen) < 0) { err = errno; goto errout; } if (type == SOCK_STREAM || type == SOCK_SEQPACKET) { if (listen(fd, qlen) < 0) { err = errno; goto errout; } } return(fd); errout: close(fd); errno = err; return(-1); }先调用socket初始化套接字,然后给服务器进程套接字绑定一个众所周知的地址,如果是面向对象的连接则调用listen宣告可以接受connect请求。
建立连接之后,就是数据传送:
send,recv用于已经连接的套接字之间的通信。
sendto,recvfrom可以在无连接的套接字上指定一个地址。对于无连接的套接字,不能使用send,除非调用connect预先设置了目标地址。
sendmsg,recvmsg是更多选项的接受和传输。
apue上面向连接的服务端和客户端的例子:
服务端:
#ifndef HOST_NAME_MAX #define HOST_NAME_MAX 256 #endif extern int initserver(int, const struct sockaddr*, socklen_t, int); extern void daemonize(const char *cmd); void serve(int sockfd) { int clfd; FILE *fp; char buf[BUFLEN]; while(1) { clfd = accept(sockfd, NULL, NULL); if(clfd < 0) { syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno)); exit(1); } if((fp = popen("/usr/bin/uptime", "r")) == NULL) { sprintf(buf, "error: %s\n", strerror(errno)); send(clfd, buf, strlen(buf), 0); } else { while(fgets(buf, BUFLEN, fp)!= NULL) send(clfd, buf, strlen(buf), 0); pclose(fp); } close(clfd); } } int main() { struct addrinfo *ailist, *aip; struct addrinfo hint; int sockfd, err; int n; char *host; #ifdef _SC_HOST_NAME_MAX n = sysconf(_SC_HOST_NAME_MAX); if(n < 0) #endif n = HOST_NAME_MAX; host = malloc(n); if(host == NULL) { printf("malloc error\n"); exit(-1); } if(gethostname(host, n) < 0) { printf("gethostname error\n"); exit(-1); } daemonize("ruptimed"); hint.ai_flags = AI_PASSIVE; hint.ai_family = 0; hint.ai_socktype = SOCK_STREAM; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_addr = 0; hint.ai_canonname = 0; hint.ai_next = NULL; if((err = getaddrinfo(host, "ruptimed", &hint, &ailist)) != 0) { syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s", gai_strerror(err)); exit(1); } for(aip = ailist; aip != NULL; aip = aip->ai_next) { if((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN)) >= 0) { serve(sockfd); exit(0); } } exit(1); }
客户端:
void print_uptime(int sockfd) { int n; char buf[BUFLEN]; while((n = recv(sockfd, buf, BUFLEN, 0)) > 0) { write(STDOUT_FILENO, buf, n); } if(n < 0) { printf("recv error\n"); exit(-1); } } int main(int argc, char *argv[]) { struct addrinfo *ailist, *aip; struct addrinfo hint; int sockfd, err; if(argc != 2) { printf("usage: ruptime hostname\n"); exit(-1); } hint.ai_flags = 0; hint.ai_family = 0; hint.ai_socktype = SOCK_STREAM; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_addr = NULL; hint.ai_canonname = NULL; hint.ai_next = NULL; if((err =getaddrinfo(argv[1],"ruptimed", &hint, &ailist)) != 0) { printf("getaddrinfo error: %s \n", gai_strerror(err)); exit(-1); } for(aip = ailist; aip != NULL; aip = aip->ai_next) { if((sockfd = socket(aip->ai_family, SOCK_STREAM, 0)) < 0) { err = errno; continue; } if(connect_retry(sockfd, aip->ai_addr, aip->ai_addrlen, 1) < 0) err = errno; else { print_uptime(sockfd); exit(0); } } fprintf(stderr, "can't connect to %s: %s\n", argv[1], strerror(err)); exit(1); }
serve(int sockfd) { int n; socklen_t alen; FILE *fp; char buf[BUFLEN]; char abuf[MAXADDRLEN]; for (;;) { alen = MAXADDRLEN; if ((n = recvfrom(sockfd, buf, BUFLEN, 0, (struct sockaddr *)abuf, &alen)) < 0) { syslog(LOG_ERR, "ruptimed: recvfrom error: %s", strerror(errno)); exit(1); } if ((fp = popen("/usr/bin/uptime", "r")) == NULL) { sprintf(buf, "error: %s\n", strerror(errno)); sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)abuf, alen); } else { if (fgets(buf, BUFLEN, fp) != NULL) sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)abuf, alen); pclose(fp); } } }
print_uptime(int sockfd, struct addrinfo *aip) { int n; char buf[BUFLEN]; buf[0] = 0; if (sendto(sockfd, buf, 1, 0, aip->ai_addr, aip->ai_addrlen) < 0) err_sys("sendto error"); alarm(TIMEOUT); if ((n = recvfrom(sockfd, buf, BUFLEN, 0, NULL, NULL)) < 0) { if (errno != EINTR) alarm(0); err_sys("recv error"); } alarm(0); write(STDOUT_FILENO, buf, n); }
这几个例子都是通过getaddrinfo过获得套接字地址信息。
面向连接的请求在连接到来时,服务器就一判断所提供的服务、对于基于数据包的协议,因为没有连接,需要先发送个服务器一个数据,用来请求服务。
基于数据包的通信客户端的recvfrom会阻塞的等待服务器的数据,如果服务器没有开启,就会无限制等待,所以需要一个定时器来避免调用recvfrom无限制阻塞。