本文为senlie原创。转载请保留此地址:http://blog.csdn.net/zhengsenlie
最初代码:
这是一个简单的时间获取server程序。它和时间获取程序client一道工作。
它是 协议相关,把代码中出现的左边的字符串换为右边的,就变成了IPv6版本号的
IPv4 --> IPv6
sockaddr_in --> sockaddr_in6
AF_INET --> AF_INET6
sin_family --> sin6_family
sin_port --> sin6_port
sin_addr --> sin6_addr
#include "unp.h" #include <time.h> int main(int argc, char **argv) { int listenfd, connfd; struct sockaddr_in servaddr; char buff[MAXLINE]; time_t ticks; //1.创建 TCP 套接字 --> 为什么不检查一下创建成功还是失败呢 ? // --> 大写字母开头的函数,如 Socket 是包裹函数,里面已经进行了错误处理 listenfd = Socket(AF_INET, SOCK_STREAM, 0); //2.把server的众所周知的port捆绑到套接字 //使用 bzero 把套接字地址结构 servaddr 清零 //设置地址族为 AF_INET //设置 IP 地址为 INADDR_ANY 。假设server主机骨多个网络接口。server进程就能够在随意网络接口上接受客户连接 //设置port号为 13 bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(13); //绑定套接字 Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); //3.把套接字转换为监听套接字 //这样才干够接受来自客户的外来连接 //LISTENQ 指定最大客户连接数 Listen(listenfd, LISTENQ); //4.接受客户连接,发送应答 for ( ; ; ) { //server进程在 accept 调用中被挂起,等某个客户连接的到达。完毕 TCP 三次握手后 accept 才返回 connfd = Accept(listenfd, (SA *) NULL, NULL); //已连接描写叙述符 ticks = time(NULL); snprintf(buff, sizeof(buff), "%.24s ", ctime(&ticks)); Write(connfd, buff, strlen(buff)); //把当前时间写给客户 //5.终止连接 // close 调用引发 TCP 连接终止序列。总共发送四个 TCP 分组。 // 每一个方向一个 Fin,每一个 Fin 又由各自的对端确认 Close(connfd); } }
问题1:协议相关
改善:使用 getaddrinfo 和 tcp_listen 来同一时候支持 IPv4 和 IPv6
/** * TCP 协议无关。调用 getaddrinfo 和 tcp_listen **/ #include "unp.h" int tcp_listen(const char *host, const char *serv, socklen_t *addrlenp) { int listenfd, n; const int on = 1; struct addrinfo hints, *res, *ressave; //1.调用 getaddrinfo //协议地址话为 AF_UNSPEC ,套接字类型为 SOCK_STREAM //由于本函数供server使用,所以还要加一个 AI_PASSIVE 的标志 bzero(&hints, sizeof(struct addrinfo)); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if ( (n = getaddrinfo(host, serv, &hints, &res)) != 0) err_quit("tcp_listen error for %s, %s: %s", host, serv, gai_strerror(n)); ressave = res; //2.尝试每一个 addrinfo 结构直到成功或到达链表尾 do { //创建套接字 listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (listenfd < 0) continue; /* error, try next one */ //设置套接字选项 Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); //绑定套接字 if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0) break; //成功 //绑定失败,尝试下一个 Close(listenfd); } while ( (res = res->ai_next) != NULL); //3.检查是否失败 if (res == NULL) //socket() 或 bind() 得到的 errorno err_sys("tcp_listen error for %s, %s", host, serv); //4.把套接字转换为监听套接字 Listen(listenfd, LISTENQ); if (addrlenp) *addrlenp = res->ai_addrlen; /* return size of protocol address */ //5.调用 freeaddrinfo 清理由 getaddrinfo 返回的动态存储空间 freeaddrinfo(ressave); //6.返回建立的监听套接字 return(listenfd); } /* end tcp_listen */ int Tcp_listen(const char *host, const char *serv, socklen_t *addrlenp) { return(tcp_listen(host, serv, addrlenp)); } /** * TCP 协议无关,调用 getaddrinfo 和 tcp_listen **/ #include "unp.h" #include <time.h> int main(int argc, char **argv) { int listenfd, connfd; socklen_t len; char buff[MAXLINE]; time_t ticks; struct sockaddr_storage cliaddr; //1.利用 Tcp_listen 得到监听套接字 if (argc == 2) listenfd = Tcp_listen(NULL, argv[1], &addrlen); else if (argc == 3) listenfd = Tcp_listen(argv[1], argv[2], &addrlen); else err_quit("usage: daytimetcpsrv1 [ <host> ] <service or port>"); //2.server循环。接受客户连接。发送应答 for ( ; ; ) { len = sizeof(cliaddr); //server堵塞在 accept 调用。等待客户连接 connfd = Accept(listenfd, (SA *)&cliaddr, &len); printf("connection from %s ", Sock_ntop((SA *)&cliaddr, len)); //发送应答 ticks = time(NULL); snprintf(buff, sizeof(buff), "%.24s ", ctime(&ticks)); Write(connfd, buff, strlen(buff)); //关闭已连接套接字 Close(connfd); } }
问题2:一次仅仅能处理一个客户
改善:大多数 UDP server是迭代的。一次仅仅处理一个客户。大多数 TCP server是并发的。
并发最简单的技术是调用 fork 函数为每一个客户创建一个子进程。其它技术包含使用线程
取代 fork,或在server启动时预先 fork 一定数量的子进程
这部分的内容在我的其它博文 回射server程序 和 server程序设计范式 里有对应的代码演示样例
问题3:长时间执行
改善:daumon [todo]