---
UNP的书写的之伟大,真是令我佩服不已,醍醐灌顶,当然很多知识由于所学有限,这里也用不上,这里只是从中摘取源代码中的一部分进行整合使其满足我们的工程需要。UNP的书写的之伟大,真是令我佩服不已,醍醐灌顶,当然很多知识由于所学有限,这里也用不上,这里只是从中摘取源代码中的一部分进行整合使其满足我们的工程需要。
零、目标
编写一个简单的TCP客户端从服务器获取时间的一对一通信功能。
一、代码架构
在common.h中(包含常用头文件和一些常量定义,用着方便)有以下的头文件:
sys/types.h 此头文件是系统类型的定义,如:int8_t int16_t int32_t int64_t等等
sys/socket.h 这是socket的接口,在其中引入bits/socket.h,其中定义了各种常量。
netinet/in.h 定义了各种地址结构体和常量。
arpa/inet.h 定义了地址转换的函数。
在error.c和error.h中,声明和定义了常用的错误输出。
其它的头文件是常用头文件。还有一些常数定义以及结构体的简称。以后会随时添加。
二、具体实现
这里使用上篇文章里包裹函数的概念,将Socket有关的函数(wrapsock.c)、文件读写有关的函数(wrapunix.c)、标准输入输出有关的函数(wrapstdio.c)、错误信息相关的函数(error.c),针对每一个进行包裹、封装,对所有的函数在头文件(common.h)中声明,由此实现了既能清晰表明具体实现功能,又针对每个函数错误处理进行操作。
注意:在unp工程中,所有包裹函数的首字母是大写的,以和库函数相区分。
common.h
#ifndef __OUR_COMMON_HDR_H #define __OUR_COMMON_HDR_H #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #define SA struct sockaddr /* socket wrapper functions */ int Accept(int,SA *,socklen_t *); void Bind(int,const SA *,socklen_t); void Connect(int,socklen_t); void Listen(int,int); int Socket(int,int,int); /* Unix wrapper functions */ ssize_t Read(int,void *,size_t); void Write(int,size_t); void Close(int); /* Std I/O wrapper functions */ void Fputs(const char *,FILE *); /* Error output functions */ void err_quit(const char *fmt,...); void err_ret(const char *fmt,...); void err_sys(const char *fmt,...); void err_dump(const char *fmt,...); void err_msg(const char *fmt,...); #endif
wrapsock.c
#include "common.h" int Accept(int fd,SA *sa,socklen_t *salenptr) { int n; again: if ((n = accept(fd,sa,salenptr)) < 0) { #ifdef EPROTO if (errno == EPROTO || errno == ECONNABORTED) #else if (errno == ECONNABORTED) #endif goto again; else err_sys("accept error"); } return n; } void Bind(int fd,const SA *sa,socklen_t salen) { if (bind(fd,salen) < 0) err_sys("bind error"); } void Connect(int fd,socklen_t salen) { if (connect(fd,salen) < 0) err_sys("connect error"); } void Listen(int fd,int backlog) { char *ptr; if ((ptr = getenv("LISTENQ")) != NULL) backlog = atoi(ptr); if (listen(fd,backlog) < 0) err_sys("listen error"); } int Socket(int family,int type,int protocol) { int n; if ((n = socket(family,type,protocol)) < 0) err_sys("socket error"); return n; }
wrapunix.c
#include "common.h" ssize_t Read(int fd,void *ptr,size_t nbytes) { ssize_t n; if ((n = read(fd,ptr,nbytes)) == -1) err_sys("read error"); return n; } void Write(int fd,size_t nbytes) { if (write(fd,nbytes) != nbytes) err_sys("write error"); } void Close(int fd) { if (close(fd) == -1) err_sys("close error"); }
wrapstdio.c
#include "common.h" void Fputs(const char *ptr,FILE *stream) { if (fputs(ptr,stream) == EOF) err_sys("fputs error"); }error.c
#include <errno.h> /* for definition of errno */ #include <stdarg.h> /* ANSI C header file */ #include "common.h" #define ERRMSG_LINE 2048 static void err_doit(int,const char *,va_list); void err_ret(const char *fmt,...) { va_list ap; va_start(ap,fmt); err_doit(1,fmt,ap); va_end(ap); return; } void err_sys(const char *fmt,ap); va_end(ap); exit(1); } void err_dump(const char *fmt,ap); va_end(ap); abort(); exit(1); } void err_quit(const char *fmt,fmt); err_doit(0,ap); va_end(ap); exit(1); } void err_msg(const char *fmt,ap); va_end(ap); return; } static void err_doit(int errnoflag,const char *fmt,va_list ap) { int errno_save; char buf[ERRMSG_LINE]; errno_save = errno; vsprintf(buf,ap); if (errnoflag) sprintf(buf+strlen(buf),": %s",strerror(errno_save)); strcat(buf,"\n"); fflush(stdout); fputs(buf,stderr); fflush(stderr); return; }
#include "common.h" #include <time.h> #define MAXLINE 4096 /* buffer length */ #define LISTENQ 1024 /* 2nd argument to listen() */ int main(int argc,char **argv) { int listenfd,connfd; struct sockaddr_in servaddr; char buff[MAXLINE]; time_t ticks; 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(IPPORT_DAYTIME); Bind(listenfd,(SA *) &servaddr,sizeof(servaddr)); Listen(listenfd,LISTENQ); for (;;) { connfd = Accept(listenfd,(SA *) NULL,NULL); ticks = time(NULL); snprintf(buff,sizeof(buff),"%s\r\n",ctime(&ticks)); Write(connfd,buff,strlen(buff)); Close(connfd); } }
daytimecpcli.c
#include "common.h" #define MAXLINE 4096 /* buffer length */ int main(int argc,char **argv) { int sockfd,n; char recvline[MAXLINE + 1]; struct sockaddr_in servaddr; if (argc != 2) err_quit("usage: ./daytimecpcli.c <IP address>"); sockfd = Socket(AF_INET,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(IPPORT_DAYTIME); if (inet_pton(AF_INET,argv[1],&servaddr.sin_addr) <= 0) err_quit("inet_pton error for %s",argv[1]); Connect(sockfd,sizeof(servaddr)); while ((n = Read(sockfd,recvline,MAXLINE)) > 0) { recvline[n] = 0; Fputs(recvline,stdout); } exit(0); }
从最后两个文件我们可以看到,服务器监听的端口为 IPPORT_DAYTIME ,监听来自同一网络的任意IP。
客户端连接该服务器可以通过回环方式或者真实IP来连接。
由于服务器、客户端使用的段口为保留端口(时间服务端口13),因此服务器必须以root权限运行程序。
三、代码验证
编译:
gcc daytimecpcli.c wrapsock.c wrapunix.c wrapstdio.c error.c -o daytimecpcli
gcc daytimecpsrv.c wrapsock.c wrapunix.c wrapstdio.c error.c -o daytimecpsrv
(这里并没有使用makefile或者automake,目的是更注重代码的分析)
测试:
服务器端:
客户端: