Chinaunix首页 | 论坛 | 博客
  • 博客访问: 51703
  • 博文数量: 22
  • 博客积分: 655
  • 博客等级: 上士
  • 技术积分: 180
  • 用 户 组: 普通用户
  • 注册时间: 2006-03-31 14:43
文章分类

全部博文(22)

文章存档

2014年(1)

2007年(21)

我的朋友

分类: C/C++

2007-06-12 15:15:12


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

by:kvew  (/bbs & /kvew)
        
程序清单如下:

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


#include // include structure sockaddr_in
#include // include structure sockaddr
#include  // inet_pton
#include     // bzero
#include     //STDOUT
#include
#include
#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 \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[n] = 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套接口地址结构(网际套接口地址结构) 定义在头文件

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
    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
    void    bzero(void* dest, size_t nbytes);
        
此函数将目标中指定书目的字节置0.

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

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

    #include
    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
    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
    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
    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 // include structure sockaddr_in
#include // include structure sockaddr
#include     // bzero
#include     //STDOUT
#include
#include
#include
#include
#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
    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
    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
    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]#


2.下面是一个UDP接收程序

#include
#include
#include
#include
#include
char * host_name="192.168.1.237";
int portno=7100;
unsigned char message[8196];
main(){
    int sin_len;
    int port=portno;
    int socket_descriptor;
    int i,j;
    struct sockaddr_in sin;
    struct hostent *server_host_name;
    server_host_name=gethostbyname(host_name);
    bzero(&sin,sizeof(sin));

    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = htonl(INADDR_ANY);
    sin.sin_port=htons(port);
    socket_descriptor=socket(AF_INET,SOCK_DGRAM,0);
    bind(socket_descriptor,(struct sockaddr *)&sin,sizeof(sin));
    while(1){
        sin_len=sizeof(sin);
        j=recvfrom(socket_descriptor,message,256,0,
            (struct sockaddr *)&sin,&sin_len);
        printf("size = %d\n",j);
        for(i=0;i            printf("%02x ",message[i]);
        }
        printf("\n");
        if(strncmp(message,"stop",4)==0) break;
    }
    close(socket_descriptor);
}
3.下面是一个UDP发送程序的例子
#include
#include
#include
#include
#include
int port = 7100;
char *hostname="192.168.1.237";
main(){
    int socket_descriptor;
    int iter=0;
    int process;
    char buf[80];
    struct sockaddr_in address;
    socket_descriptor=socket(AF_INET,SOCK_DGRAM,0);
    memset(&address,0,sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr(hostname);
    address.sin_port=htons(port);
    process=1;
    do{
        sprintf(buf,"data packet with ID %d\n",iter);
        if(iter>20){
            sprintf(buf,"stop\n");
            process=0;
        }
        sendto(socket_descriptor,buf,strlen(buff),0,
            (struct sockaddr *)&address,sizeof(address));
        iter++;
    }while(process);
}
 
 
 
阅读(1539) | 评论(1) | 转发(0) |
0

上一篇:bsd+pf

下一篇:tcpdump

给主人留下些什么吧!~~