概述
简介
Web客户与服务器之间使用传输层的TCP协议通信。TCP转而使用网络层的IP协议通信,IP再通过某种形式的数据链路层通信。
客户和服务器通常是用户进程,而TCP和IP协议通常是内核中协议栈的一部分。
包裹函数:用于检查错误、输出适当的消息,以及在出错时终止程序的运行。
一个简单的时间获取客户程序
Xcode导入unp.h
- 修改项目Build settings中的Header Search Path和Library Search
Path,使填写的目录下包含unp.h,config.h, apue.h和error.c。这两个文件的获取需通过下载unpv13e; - 修改unp.h文件中的#include “../config.h” 为#include “config.h”
- 在新建的.c文件中 #include “unp.h”和#include “apue.h”,同时在apue.h中添加error.c。这里导入apue.h的原因是要用到错误处理函数err_xxx()。
时间获取– 客户端程序
- 利用Mac OS的Xcode运行此程序时,首先要进入root模式,获取最高权限,程序编译时会提示Bind Error:Permission Denied。
- 在wrapsock.c中需要添加包裹函数Write()和Close():
void Write(int fd, void *ptr, size_t nbytes)
{
if (write(fd, ptr, nbytes) != nbytes)
err_quit("write error");
}
void Close(int fd)
{
if (close(fd) == -1)
err_quit("close error");
}
3.编译时如果遇到err_quit()等类似err_xxx()函数报错,则需要修改error.c中的具体函数。查看err_xxx()是否存在于error.c中,error.c源代码如下:
#include "apue.h"
#include <errno.h> /* for definition of errno */
#include <stdarg.h> /* ISO C variable aruments */
static void err_doit(int, int, const char *, va_list);
/*
* Nonfatal error related to a system call.
* Print a message and return.
*/
void
err_ret(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, errno, fmt, ap);
va_end(ap);
}
/*
* Fatal error related to a system call.
* Print a message and terminate.
*/
void
err_sys(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, errno, fmt, ap);
va_end(ap);
exit(1);
}
/*
* Nonfatal error unrelated to a system call.
* Error code passed as explict parameter.
* Print a message and return.
*/
void
err_cont(int error, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, error, fmt, ap);
va_end(ap);
}
/*
* Fatal error unrelated to a system call.
* Error code passed as explict parameter.
* Print a message and terminate.
*/
void
err_exit(int error, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, error, fmt, ap);
va_end(ap);
exit(1);
}
/*
* Fatal error related to a system call.
* Print a message, dump core, and terminate.
*/
void
err_dump(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, errno, fmt, ap);
va_end(ap);
abort(); /* dump core and terminate */
exit(1); /* shouldn't get here */
}
/*
* Nonfatal error unrelated to a system call.
* Print a message and return.
*/
void
err_msg(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(0, 0, fmt, ap);
va_end(ap);
}
/*
* Fatal error unrelated to a system call.
* Print a message and terminate.
*/
void
err_quit(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(0, 0, fmt, ap);
va_end(ap);
exit(1);
}
/*
* Print a message and return to caller.
* Caller specifies "errnoflag".
*/
static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
char buf[MAXLINE];
vsnprintf(buf, MAXLINE-1, fmt, ap);
if (errnoflag)
snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
strerror(error));
strcat(buf, "\n");
fflush(stdout); /* in case stdout and stderr are the same */
fputs(buf, stderr);
fflush(NULL); /* flushes all stdio output streams */
}
4.若在Centos下运行也比较麻烦,我就试了一下,结果很多缺少很多unix的库函数:
客户端程序
#include "unp.h"
#include "apue.h"
#include <time.h>
#include "wrapsock.c"
int main()
{
int sockfd, n;
char recvline[MAXLINE + 1];
struct sockaddr_in servaddr;
char *argv = "192.168.0.105";
printf("Client is on\n");
if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(13); //置端口号为13,时间获取服务器的端口
if (inet_pton(AF_INET, argv, &servaddr.sin_addr) <= 0)
err_quit("inet_pton error for %s", argv);
if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
err_sys("connect error");
while ( (n = (int)read(sockfd, recvline, MAXLINE)) > 0) {
recvline[n] = 0; /* null terminate */
if (fputs(recvline, stdout) == EOF)
err_sys("fputs error");
}
if (n < 0)
err_sys("read error");
exit(0);
}
服务器端程序
#include "unp.h"
#include "apue.h"
#include <time.h>
#include "wrapsock.c"
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(13); /* daytime server */
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));//bind出错
Listen(listenfd, LISTENQ);
printf("Server is on\n");
for ( ; ; ) {
connfd = Accept(listenfd, (SA *) NULL, NULL);
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
Write(connfd, buff, strlen(buff));
Close(connfd);
}
}
运行结果
首先运行daytimetcpsrv.c, 然后再运行daytimetcpcli.c,在客户端程序中,需要把ip地址参数设置本机的ip地址。
同样的程序也可以在终端里执行。需要把上述的4个头文件与.c文件放在一个文件目录下。注意,需要用su -切换至root用户执行。执行结果如图:
心得
1.Bind Error:Permission Denied:这个错误是因为文件权限不足导致的。解决办法:若要使用Xcode编译,则需要利用root用户登录Mac;使用gcc编译,需要切换至root用户su -
2.Bind Error:Address already in use:这个错误是之前服务器程序已打开,而现在无法继续进行bind操作。解决方法:可以kill掉服务器进程。例如,daytimetcpsrv.c指定使用端口13,那么我们在命令行输入lsof -i:13可以查看相关使用该端口的进程pid,然后利用kill pid命令终止进程,就可以解决这个error。
错误处理:包裹函数
任何现实世界的程序都必须检查每个函数调用是否返回错误。因此上文引入error.c是相当必要的。error.c中定义了err_quit和err_sys函数输出一个出错信息并退出程序。