导读
本篇实现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
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 anuint32_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。
上实验结果图片解解馋
服务器启动
客户端启动,太快了,结果都出来了
服务器处理结束,退出
计算器要求:客户需要传递后缀表达式简单运算(如上图),服务器直接运行就即可。 缺陷:此计算器只服务于一个客户,其他不给予处理;此计算器进一步改进可以实现接受不只一个客户的请求。
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
Dylan http://daoluan.github.io/blog/
02 August 2012 会持续更新