两种网络编程API
- 套接口(sockets),有时称为“Berkeley套接口”,因为它源自Berkeley Unix。
- XTI(X/Open传输接口),它是对AT&T开发的传输层接口(TLI)经少量修改的产物。
- 以后我们将遇到术语套接口(socket)的许多不同用法。首先,我们正在使用的应用程序编程接口称为套接口API(sockets API)。它是应用层到传输层的接口。
大多数的网络应用系统包括两部分:客户(client)与服务器(server),两者是通过通信链接的,其中又涉及到了网络通信协议,TCP/IP协议族(又称网际协议族)
在实际生活中,客户与服务器无需处于一个局域网内通信,也可以通过路由器(router,广域网的架构设备)作为传输节点,进行局域网转接到广域网中去。最大的广域网是因特网。
示例代码(一个简单的时间/日期客户程序)
//这个头文件含有许多大部分网络程序都需要的系统头文件,并定义了所用到的各种常值(如MAXLINE)
include "unp.h"
//命令行参数:形参是命令行参数。ANSI(美国国家标准局)C编译器。
int main(int argcchar* *argv)
{
int sockfd.n;
char recvline[MAXLINE+1];
struct sockadd_in servaddr;
if (argc ! = 2)
//系统输入
err-quit("usage: a.out<IPaddess>" );
//创建TCP套接口:利用socket函数创建网际(AF_INET)字节流和(SOCK_STREAM)套接口,它是TCP套接口的特色名字。
//该函数返回一小整数描述字,在以后的其他函数调用中(如随后的connect和read调用),我们就用它来标识这个套接口。
if ( (sockfd= socket(AF_INET,SOCK_STREAM,0))<0)
//如果socket函数调用失败,我们就调用自己的err-sys放弃执行。该函数输出我们提供的出错消息和所发生系统错误的描述信息
//(如“Protocol not supported(协议不受支持)”等)后,终止进程的执行。
err-sys("socket error" );
//指定服务器IP地址和端口:bzero函数将整个结构都清零
bzero(&servadd,sizeof(servaddr));
//置地址族为AF_INET
servaddr.sin_family = AF_INET;
//端口设置为daytime服务器的端口13
servaddr.sin_port = htons(13);
//置IP地址为命令行的第一个参数值(argv[1]),inet_pton变换命令行参数为合适的格式。
if (inet_pton(AF_INET.argv[1],&servaddr,sin-addr)<=0)
err_quit("inet_pton error for %s",argv[1];
//connect函数用在TCP套接口上的功能是:跟由它的第二个参数所指的套接口地址结构
//对应的服务器建立连接。connect的第三个参数说明套接口地址结构的长度,对于网
//际套接口地址结构。
if(connect(sockfd,(SA * )&servaddr,sizeof(servaddr))<0)
err_sys("connect error");
//SA=struct sockaddr,即通用套接口地址结构。套接口函数每次需要一个指向套接口
//地址结构的指针时,这个指针必须转换成指向一个通用套接口地址结构的指针类型。
while ( (n = read(sockfd,recvline,MAXLINE))>0){
recvline[n]=0;
//读入并输出服务器的应答:我们使用read函数读服务器的应答,并用标准的I/O
//函数fputs输出结果。我们必须注意,TCP是一种无记录边界的字节流协议。
if(fputs(recvline,stdout) == EOF)
err_sys("fputs error" );
}
if(n<0)
err_sys("read error" );
//exit语句终止程序。Unix在进程终止时关闭所有描述字,其中包括TCP套接口描述字。
exit(0);
}
错误处理:包裹函数
我们知道,使用err_quit还有err_sys可以输出出错消息并中止程序,但我们有时候只需要输错报错信息而不中止程序的运行,这就需要用到包裹函数了。
因为多数情况下程序终止于一个错误,我们可以定义包裹函数来简化我们的程序。包裹函数调用实际函数,检查返回值,发生错误时终止进程。确定包裹函数名的约定是大写实际函数名的第一个字符,如:
sockfd= Socket(AF_INET,SOCK_STREAM,0)
Socket的包裹函数为:
int Socket(int family ,int type,int protocol)
int n;
if( (= socket(family,type,protocol))<0)
err_sys("socket orror" );
return (n);
在后续,我们通常会将errno的值当成函数返回值返回,当成指示。
int n;
if((n= pthread mutex-lock(&ndone_mutex))!=0)
errnon=n, err_sys("pthread mutex_lock error" );
Unix errno值
每当在一个Unix函数(如socket函数)中发生错误时,全局变量errno将被置成一个指示错误类型的正数﹐函数本身则通常返回一1。err.sys检查errno变量并输出其相应的出错消息(例如,当errno值等于ETIMEDOUT时,将配带着输出“Connection timed out(连接超时)”)。
errno的值只在函数发生错误时设置。如果函数不返回错误,errno的值就无定义。在Unix中,所有的错误值都是常值,具有以E打头的全大写字母名字,在头文件(sys/errno.n中定义。值0不表示任何错误。
示例代码(一个简单的时间/日期服务器程序)
include "unp.h"
include<time.h>
int main(int argc,char * * argy)
{
int listenfd,connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
time-t ticks;
//创建TCP套接口
Listenfd = Socket(AF_INET,SOCk_STREAM,0);
bzero(&servaddr ,sizeof(servaddr ));
servaddr.sin_family = AF_INET;
//捆绑服务器的总所周知端口到套接口:指定IP地址INADDR.ANY,允许服务器主机有多个接口
servaddr.sin_addr.s_addr = htonl(INADDR.ANY);
servaddr.sin_port=htons(13);
Bind(listenfd,(SA * ) &servaddr,sizeof(servaddr));
//把套接口变换成监听套接口:通过调用listen函数将此套接口变换成一个监听套接口,它使
//系统内核接受来自客户的连接。socket、 bind和listen是任何TCP服务器用于准备监听描述字
//(listening,descriptor)通常的三个步骤。常值LISTENQ在头文件unp.h中定义,它指定系统内
//核允许在这个监听描述字上排队的最大客户连接数。
Listen(listenfd,LISTENQ);
//接受客户连接,发送应答:服务器进程在调用Accept函数后处于睡眠状态,它等待客户的连接
//和内核对它的接受。TCP连接使用三路握手(three一way handshake)来建立,当握手完毕时,
//accept函数返回,其返回值是一个称为已连接描述字(connected descriptor)的新描述字(connfd)。
//此描述字用于与新客户的通信。accept为每个连接到服务器的客户返回一个新的已连接描述字。
for( ; ;){
connfd = Accept(listenfd,(SA *)NULL,NULL);
//time函数返回当前时间(自Unix纪元1970年以来的秒数),ctime可以将其转换成可读时间格式
ticks = time(NULL);
snprintf(buff,sizeof(buff)," %.24s\r\n" ,ctime(&ticks));
//Write函数将结果返回给客户
Write(connfd,buff,strlen(buff));
//终止连接:服务器通过调用close关闭与客户的连接。它引发通常的TCP连接终止序列:
//每个方向上发送一个FIN,每个FIN又由对方确认。
Close(connfd);
}