您的位置:首页 > 其它

读书笔记之 简单时间获取客户/服务程序

2006-10-16 13:12 639 查看
读书笔记之 简单时间获取客户/服务程序

by:kvew www.smatrix.org (/bbs & /kvew)

程序清单如下:

===========================time_client.c============================

#include<netinet/in.h> // include structure sockaddr_in
#include<sys/socket.h> // include structure sockaddr
#include<arpa/inet.h> // inet_pton
#include<string.h> // bzero
#include<unistd.h> //STDOUT
#include<stdio.h>
#include<stdlib.h>
#define MAXLINE 4096

typedef struct sockaddr SA; // 这里主要是为了缩短代码长度

int
main(int argc, char ** argv)
{
int sockfd,n;
char recvline[MAXLINE+1];
struct sockaddr_in servaddr;

if(argc != 2)
{
printf("usage: a.out <IP address>/n");
exit(1);
}
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
{
printf("socket error/n");
exit(1);
}
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET; //设置协议族为AF_INET(IPv4协议族)
servaddr.sin_port = htons(13); //设置端口
if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr)<=0)
{
printf("inet_pton error for %s/n", argv[1]);
exit(1);
}
if(connect(sockfd,(SA*)&servaddr,sizeof(servaddr)) < 0)
{
printf("connect error/n");
exit(1);
}
while((n=read(sockfd,recvline,MAXLINE)) > 0)
{
recvline
= 0; // null terminate
if(fputs(recvline,stdout) == EOF)
{
printf("fputs error/n");
exit(1);
}
}
if(n < 0)
{
printf("read error/n");
exit(1);
}
exit(0);
}
//------------------------------------------time_client.c

以上是时间获取客户端程序,关于各个头文件已经在注释中说明了.程序具体说明如下

int sockfd,n;

这里定义两个整型,sockfd将记录socket函数返回的描述符,n记录read函数返回的字节数

char recvline[MAXLINE+1];

这个数组用来存放接收到的数据.后面一个加一,这是因为在打印到终端的时候,需要一个标记结束的字符

struct sockaddr_in servaddr;

sockaddr_in是IPv4套接口地址结构(网际套接口地址结构) 定义在头文件<netinet/in.h>中

struct in_addr{
in_addr_t s_addr; /* 32-bit IPv4 address */
/* network byte ordered */
};

struct sockaddr_in {
uint8_t sin_len; /* length of structure (16) */
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 16-bit TCP or UDP port number*/
/* network byte orderd */
struct in_addr sin_addr; /* 32-bit IPv4 address */
/* network byte orderd */
char sin_zero[8]; /* unused */

};

主机字节序(host byte order)和网络字节序(network byte order)的区别

首先建立一个套接口

sockfd = socket(AF_INET,SOCK_STREAM,0)

此处设定协议族为AF_INET(IPv4 协议),套接口类型为SOCK_STREAM(字节流套接口),协议设置为0,采用缺省配置

socket函数原型为:

#include<sys/socket.h>
int socket(int family, int type, int protocal);
返回: 非负描述字 -- 成功 , -1 -- 出错

其中family参数指明协议族

family 说明
AF_INET IPv4 协议
AF_INET6 IPv6 协议
AF_LOCAL UNIX域协议
AF_ROUTE 路由套接口
AF_KEY 密钥套接口

type参数指明套接口类型

type 说明
SOCK_STREAM 字节流套接口
SOCK_DGRAM 数据报套接口
SOCK_SEQPACKET 有序分组套接口
SOCK_RAW 原始套接口

procal指明某个协议类型常值.或者设置为0,以选择给定family和type组合的系统缺省值

protocal 说明
IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议

这里注意,不是所有的family与type的组合都是有效的

socket函数成功时返回一个非负整数值,它与文件描述符类似.称其为套接口描述符(socket descriptor),简称套接字(sockfd).

bzero(&servaddr,sizeof(servaddr));

将初始化套接口地址为0

bzero函数原型为:

#include<string.h>
void bzero(void* dest, size_t nbytes);

此函数将目标中指定书目的字节置0.

htons(13) 此处将主机字节序转换为网络字节序

两种字节序列间的转换使用以下四个函数:

#include<netinet/in.h>
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);

其中h为host,n为net,s为short,可看做一16位的值(如TCP/UDP端口号),l为long,可看做一32位的值(如IPv4地址)

inet_pton(AF_INET,argv[1],&servaddr.sin_addr)

将我们输入的目标IP参数(IPv4),转换为二进制存储到servaddr.sin_addr中.

此函数原型为:

#include<arpa/inet.h>
int inet_pton(int family, const char* strptr, void *addrptr);
返回: 1 -- 成功, 0 - 输入的不是有效表达格式, -1 - 出错

它将转换由指针strptr所指的串,并存到addrptr中(二进制结果),地址表达格式可以为IPv4(AF_INET),也可以是IPv6(AF_INET6).

然后开始建立与服务器的连接

connect(sockfd,(SA*)&servaddr,sizeof(servaddr))

connect函数原型:

#include<sys/socket.h>
int connect(ini sockfd, const struct sockaddr* servaddr, socklen_t addrlen);
返回: 0 - 成功, -1 - 出错

这里sockfd是前面提到的由socket函数返回的套接口描述字,接着是一个指向套接口地址结构的指针和该结构的大小.套接口地址结构中必需包含有服务器的IP地址和端口号.端口号前面已经设定了为13,IP地址在inet_pton函数中通过命令行参数argv[1]设定.

建立连接后,循环调用read(sockfd,recvline,MAXLINE)来读取字节流

read函数原型:

#include<unistd.h>
int read(int filedes, char* buff, unsigned nbytes);
返回: numbers of bytes read, 0 if end, -1 on error

即返回0的时候为读结束.有很多情况下导致实际读入字节数小于所要求读入的字节数.比如在这里,从网络缓冲区中读入字节流,其中的字节数小于该缓冲区的大小.这个时候返回实际读入的字节数后,接着就返回0.

在读入的同时将这些字节打印到终端上

fputs(recvline,stdout)

以上为时间获取程序客户端的流程.

===================================time_serv.c==================

/////////////// a time server program /////////////
////////////// unix network programming 1.5 /////////////

#include<netinet/in.h> // include structure sockaddr_in
#include<sys/socket.h> // include structure sockaddr
#include<string.h> // bzero
#include<unistd.h> //STDOUT
#include<sys/un.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define MAXLINE 4096
#define LISTENQ 1024 //设置内核中监听队列的大小,将在listen函数中说明

typedef struct sockaddr SA;

int
main(int argc, char ** argv)
{
int listenfd,connfd;
char buff[MAXLINE];
struct sockaddr_in servaddr;
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);
bind(listenfd,(SA*)&servaddr,sizeof(servaddr));
listen(listenfd,LISTENQ); //LISTENQ
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);
}

}
//---------------------------------time_client.c

和客户端一样,服务端程序首先也创建一个套接口

listenfd = socket(AF_INET,SOCK_STREAM,0)

然后初始化为0

bzero(&servaddr,sizeof(servaddr));

在待捆绑到该TCP套接口的网际套接口地址结构中填入通配地址(INADDR_ANY)

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

然后是服务器的端口号

servaddr.sin_port = htons(13);

然后调用bind函数把一个本地协议地址赋予一个套接口

bind(listenfd,(SA*)&servaddr,sizeof(servaddr));

bind函数原型:

#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
返回: 0 - 成功, -1 - 出错

之后调用listen函数,指示内核应接收指向该套接口的连接请求.

listen(listenfd,LISTENQ);

listen函数仅由TCP服务器调用.当socket函数创建一个套接口时,它被假设为一个主动套接口,即是一个将调用connect函数发起连接的客户端接口.而listen函数把一个未连接的套接口转换成一个 被动套接口,指示内核接收指向该套接口的连接请求.这将导致套接口状态从closed转换到listen.

listen函数原型:

#include<sys/socket.h>
int listen(int sockfd, int backlog);
返回: 0 - 成功, -1 - 出错

此函数应该在调用socket和bind函数之后,在accept之前.

对于 backlog 参数,内核为任何一个给定的监听套接口维护两个队列,未完成队列和已完成队列.两个队列之和不能超过 backlog.起初 backlog 设置为5,但现在的服务器每天处理几百万个连接,可以通过一个listen的包裹函数Listen来允许用变量LISTENQ来覆些有调用者指定的值.在这里我没有用包裹函数(需要自己实现).

然后无限循环调用accept函数接收字节流

connfd = accept(listenfd,(SA*)NULL,NULL);

accept函数由TCP服务器调用,用于从已完成连接队列队头返回一个已完成连接,若已完成队列为空,那么将被投入睡眠

accept函数原型:

#include<sys/socket.h>
int accept(int sockfd, struct sockaddr* cliaddr, socklen_t *addrlen);
返回: 非负描述字 - 成功, -1 - 出错

参数cliaddr和addrlen用来返回已连接的对端进程(客户)的协议地址.addrlen是一个值-结果参数:调用前我们将由*addrlen所引用的整数值置为由cliaddr所指的套接口地址结构的长度,返回时,该整数值即为由内核存在该套接口地址结构内的确切字节数.

调用snprintf函数将时间信息写入缓冲区

snprintf(buff,sizeof(buff),"%.24s/r/n",ctime(&ticks));

snprintf函数要求第二个参数为缓冲区大小,检查是否越界,从而代替sprintf函数,以防止缓冲区溢出.

最后调用write函数把缓冲区中的字节写入已经连接的套接口中去

write(connfd,buff,strlen(buff));

==========================================测试=========================

在本机上测试如下

[root@localhost unix_network]# ./time_serv &
[1] 12487
[root@localhost unix_network]# ./time_client 192.168.1.58
Mon Oct 16 13:01:21 2006
[root@localhost unix_network]#
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: