代码改变世界

基于TCP的C/S初级网络编程1

2012-08-02 21:29  捣乱小子  阅读(2648)  评论(2编辑  收藏  举报

导读

本篇实现C/S架构的“计算器”,与大家分享。

看了会网络编程,便不自觉YY了下:实现一个简单的计算器,客户端给出简单的运算,服务端负责运算。这一小项目做起来很有意思,而且难度不大,所以推荐初学者试着去做做。下面分享在实现上述“计算器”的过程。

简单的基于tcp协议的 C/S编程都离不开这几个函数:

服务端:socket,bind,listen,accept,recv,send
客户端:socket,connect,recv,send

因为“计算器”还设计涉及客户端的阻塞(因为客户端提交了运算要求过后,服务端可能要等会才能回送计算结果,这时要求客户端阻塞等候),所以涉及select函数。select函数用途广泛,很容易实现阻塞功能。介绍一个文档,有兴趣可以参考一下:http://wenku.baidu.com/view/0ea86ffdc8d376eeaeaa3198.html

客观测试环境

可以在一个主机上同时进行服务端和客户端的测试,只要客户在connect的时候用回环地址(或者本地静态IP地址)连接服务端就可以。

实现细节

socket不成功怎么办,bind不成功怎么办,listen不成功怎么办...都有相应的出错处理,编程过程中养成这种“考虑周细”的习惯(考虑所有的情况,比如出错的时候打印错误信息),对调试很有帮助。

http://www.gnu.org/software/libc/manual/html_node/Internet-Address-Formats.html

— Data Type: struct sockaddr_in

This is the data type used to represent socket addresses in the Internet namespace. It has the following members:

sa_family_t sin_family
This identifies the address family or format of the socket address. You should store the value AF_INET in this member. See Socket Addresses.
struct in_addr sin_addr
This is the Internet address of the host machine. See Host Addresses, and Host Names, for how to get a value to store here.
unsigned short int sin_port
This is the port number. See Ports.

注:sockaddr_in此类型数据在使用之前请务必bzero

其中sin_addr是结构体,

http://www.gnu.org/software/libc/manual/html_node/Host-Address-Data-Type.html

— Data Type: struct in_addr

This data type is used in certain contexts to contain an IPv4 Internet host address. It has just one field, named s_addr, which records the host address number as an uint32_t.

inet_pton和inet—_ntop方便点分十进制IP地址字符串和uint32_t(IP地址是4字节,应为网络字节序)的转换。

select

如上所述要求,“因为客户端提交了运算要求过后,服务端可能要等会才能回送计算结果,这时要求客户端阻塞等候”,select经常扮演阻塞的角色。
http://www.gnu.org/software/libc/manual/html_node/Waiting-for-I_002fO.html(文档很详细)
因此客户端提交运算要求之后,需要将其socket读功能阻塞,直到有数据(即服务端回送的结果)时才进行读取。如果用轮询的方法,很浪费CPU。

上实验结果图片解解馋

服务器启动

1_thumb.png

客户端启动,太快了,结果都出来了

2_thumb.png

服务器处理结束,退出

3_thumb.png

计算器要求:客户需要传递后缀表达式简单运算(如上图),服务器直接运行就即可。
缺陷:此计算器只服务于一个客户,其他不给予处理;此计算器进一步改进可以实现接受不只一个客户的请求。

client

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <string.h>

#define MAXSLEEP 1024

int connect_retry(int sockfd,const struct sockaddr * addr,socklen_t alen)
{
    int nsec;

    printf("connecting\n");
    for(nsec = 1; nsec <= MAXSLEEP; nsec<<=1)
    {
        if(connect(sockfd,addr,alen) == 0)
        {
            printf("connected\n");
            return 0;
        }// if
        if(nsec <= MAXSLEEP/2)//    delay
            sleep(nsec);
    }// for:
    return 0;
}

int main(int argc,char * argv[])
{
    if(argc != 4)
    {
        printf("you must input 4 arg\n");
        return 1;
    }// if

    int fd;    
    struct sockaddr_in si,server;
    char addr[20],buf[20],bufrecv[20];

    bzero(bufrecv,sizeof(bufrecv));
    sprintf(addr,"127.0.0.1");

    fd = socket(AF_INET,SOCK_STREAM,0);//   create socker fd;
    printf("socket ok\n");

//prepare server addr
    bzero(&server,sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(6000);
    inet_pton(AF_INET,addr,(void *)&server.sin_addr);
    printf("server ok\n");

//prepare request data
    bzero(buf,sizeof(buf));
    sprintf(buf,"%c%c%c",argv[1][0],argv[2][0],argv[3][0]);

//connect
    if(connect_retry(fd,(struct sockaddr *)&server,sizeof(server)) < 0)
    {
        printf("connect error\n");
        return 1;
    }// if

//send
    if(send(fd,buf,20,0) < 0) 
    {
        printf("client send error\n");
        return 1;
    }// if

//select
    fd_set readfd;
    FD_ZERO(&readfd);
    FD_SET(fd,&readfd);
    int t;

    if((t = select(FD_SETSIZE,&readfd,NULL,NULL,NULL)) < 0)
    {
        printf("select error\n");
        return 1;
    }// if

//recv
    bzero(bufrecv,sizeof(bufrecv));
    recv(fd,bufrecv,20,0);
    printf("%s\n",bufrecv);

    close(fd);
    return 0;
}

server

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

char bufret[20];

int initserver(int type,const struct sockaddr * addr,socklen_t alen,int qlen)
{
    int fd;
    int err = 0;

    if((fd = socket(addr->sa_family,type,0)) < 0)
        return -1;

    printf("binding\n");
    if(bind(fd,addr,alen) < 0)
    {
        err = errno;
        goto errout;
    }// if
    printf("bind succeed \n");

    if(type == SOCK_STREAM || type == SOCK_SEQPACKET)
    {
        printf("listening\n");
        if(listen(fd,1) < 0)
        {
            err = errno;
            printf("listen error\n");
            goto errout;
        }// if
    }// if
    printf("listened \n");
    return (fd);

errout:
    close(fd);
    errno = err;
    return -1;
}

int serve(int sockfd)
{
    int a,b;
    char op,buf[25];

    int ret,addrlen = sizeof(struct sockaddr_in),clfd; 
    struct sockaddr_in client;

    bzero(&client,sizeof(client));

//accept   
    printf("accepting\n");
    clfd = accept(sockfd,(struct sockaddr *)&client,&addrlen);

//recv
    printf("accepted\n");
    bzero(buf,sizeof(buf));
    recv(clfd,buf,20,0);
    printf("recived\n");

//calculate
    a = buf[0] - '0';
    b = buf[1] - '0';
    op = buf[2];

    switch(op)
    {
        case '+':ret = a + b;break;
        case '-':ret = a - b;break;
        case '*':ret = a * b;break;
        case '/':ret = a / b;break;
    }// switch

    sprintf(bufret,"the result:%d",ret);

//send
    printf("sending\n");
    if(send(clfd,bufret,20,0) < 0)
    {
        printf("server send error\n");
        return -1;
    }// if
    printf("sended,server end\n");
    return 0;
}

int main(int argc,char * argv[])
{
    int sockfd;
    char addr[20];

    bzero(addr,sizeof(addr));
    sprintf(addr,"127.0.0.1");

    struct sockaddr_in server;
    bzero(&server,sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(6000);
    //server.sin_addr.s_addr = htonl(INADDR_ANY);
    inet_pton(AF_INET,addr,(void *)&server.sin_addr);

//prepare server
    if((sockfd = initserver(SOCK_STREAM,(struct sockaddr *)&server,sizeof(server),1)) < 0)
    {
        printf("initserver error\n");
        return 0;
    }// if

    printf("serving\n");

//serve
    serve(sockfd);
    close(sockfd);
    return 0;
}

以上纯属笔者YY后的作品,还存在很多的缺陷与不足;抛砖引玉,与广大朋友分享。欢迎创意建议提议。另,如有错误,欢迎斧正。

本文完 2012-08-02

捣乱小子 http://www.daoluan.net/blog/