導讀
本篇實現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。
上實驗結果圖片解解饞
伺服器啟動
用戶端啟動,太快了,結果都出來了
伺服器處理結束,退出
計算機要求:客戶需要傳遞尾碼運算式簡單運算(如),伺服器直接運行就即可。
缺陷:此計算機只服務於一個客戶,其他不給予處理;此計算機進一步改進可以實現接受不只一個客戶的請求。
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 1024int 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/