C/S架构的简单时间获取程序的实现

Github地址:https://github.com/wangjiwang/C-S-

C/S 结构,即大家熟知的客户机和服务器结构。

本程序完成一个简单的时间获取功能,服务器依次调用socket,bind,listen三个函数准备监听描述符。

服务器进程调用accept函数,在accept调用中被投入睡眠,等待某个客户的连接到达并被内核接受。

TCP客户调用connect函数来建立与TCP服务器的连接,连接后调用read函数来读取服务器的应答,并用fputs函数输出结果显示在屏幕上。

运行时先运行服务器server.c程序,再运行客户端client.c程序(注意应加上自己电脑的IP地址),将会在屏幕上获取时间,如下图所示。

              (图1 服务器运行结果)

              (图2 客户端运行结果)

源代码如下

服务器程序:

#include <time.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define MAXLINE 4096
#define LISTENQ 1024

int main(int argc, char ** argv)
{
    int listenfd,connfd;
    struct sockaddr_in servaddr;
    char buff[MAXLINE];
    time_t ticks;

    //创建TCP套接字
    listenfd = socket(AF_INET,SOCK_STREAM,0);

    //填写一个网际套接字地址结构并调用bind函数,服务器的端口我们设置为9500
    //IP地址我们指定为127.0.0.1(这个要根据你自己的IP地址指定)
    bzero(&servaddr ,sizeof(servaddr ));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(9500);//端口的设置

    //bind函数把一个本地的协议地址赋予一个套接字。
    //第二个参数是一个指向特定于协议的地址结构的指针,第三个参数是该地址结构的长度
    bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr ));

    //调用listen函数把该套接字转换成监听套接字
    //第二个参数是指定这个监听描述符上排队的最大客户连接数
    listen(listenfd,LISTENQ);

    //调用accept函数
    //第一个参数叫监听套接字描述符,由socket创建,随后作用与bind和listen的第一个参数描述符
    //accept函数成功返回时,返回一个已连接套接字描述符
    //注意!监听套接字描述符与已连接套接字描述符是不一样的
    //一个服务器通常仅仅创建一个监听套接字,它在该服务的生命周期内一直存在。
    //内核会为每个与服务器成功连接的客户创建一个新的套接字,即已连接套接字
    //当服务器完成对某个客户的服务时,相应的连接套接字就被关闭掉
    for(;;){
        connfd = accept(listenfd ,(struct sockaddr*)NULL,NULL);

        ticks = time (NULL);
        snprintf(buff,sizeof(buff),"%.24s\r\n",ctime(&ticks));
        write(connfd,buff,strlen(buff));

        close(connfd);
        }
    return 0;
}

客户端程序:

#include <sys/types.h>         
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#define MAXLINE 4096

int main(int argc,char **argv)
{
    int sockfd,n;
    char recvline[MAXLINE+1];
    struct sockaddr_in servaddr;
    if (argc  != 2)
    {
        perror("usage:a.out<IPaddress>");
    }

    //socket函数创建一个网际(AF_INET)字节流(SOCK_STREAM )套接字
    //该函数返回一个小整数描述符,接下来的所有函数调用就用描述符来标识该套接字
    //如果调用函数函数失败,则返回-1,输出提示信息

    if ( (sockfd = socket(AF_INET,SOCK_STREAM,0) )<0)
    {
        perror("socket error");
    }

    //把服务器的IP地址和端口号填入一个网际套接字地址结构(一个名为servaddr的sockaddr_in结构变量)
    //使用bzero函数清零结构体,把地址族设为AF_INET,端口号设置为9500
    //IP地址为第一个命令行参数的值argv[1]
    //因为网际套接字地址结构的IP地址和端口号必须使用特定格式
    //所以调用htons函数(主机到网络短整数函数)去转换二进制端口号
    //再调用inet_pton 把命令行参数IP地址转换为合适的格式

    bzero (&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(9500);//连接服务器端口
    if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr)<=0)
        perror ("inet_pton error");

    //调用connect函数建立与服务器的TCP连接,所连接的服务器由该函数的第二个参数指向的套接字地址结构指定
    //该函数的第三个参数指定套接字地址结构的长度,我们用sizeof操作符来获取

    if (connect (sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))>0) {
        perror ("connect error ");
    }

    //使用read函数来读取服务器的应答,并用fputs函数输出结果
    //如果数据量大,read函数不能返回服务器的整个应答
    //因此,我们总是把read编写在某个循环中,当返回0(表明传输完成)和返回-1(表明发生错误)时循环结束
    while ((n = read(sockfd,recvline,MAXLINE))>0) {
        recvline[n]=0;
        if (fputs(recvline,stdout)==EOF) {
            perror ("fputs error");
        }
    }
    if (n < 0) {
        perror ("read error");
    }
        printf("服务端ip是127.0.0.1\n");
        printf("服务端监听端口是9500\n");
        printf("服务器发送的文件为wjw.txt\n");

    //exit终止程序运行,关闭该进程所有打开的描述符

    exit(0);
}

改程序编译及测试环境为CentOS6.8。

参考书籍:《UNIX网络编程 卷1:套接字联网API》(第三版 W.Richard Stevens Bill Fenner Andrew M.Rudoff)

 

posted @ 2017-03-09 15:23  E(X)  阅读(308)  评论(0)    收藏  举报