学了APUE里的套接字,敲了实验楼里的epoll例子。却刚意识到从未用POSIX socket API来自己真正写个C/S端。
例子也就参照老掉牙的echo,time:客户端发送cmd,服务端根据收到的cmd字符串执行相应的获取时间、回显等功能。
C/S编程模型:
先上服务端代码:
#include <sys/socket.h> //socket
#include <sys/types.h> //setsockopt
#include <unistd.h> //close
#include <netinet/in.h> //sockaddr_in
#include <arpa/inet.h> //inet_addr
#include <stdio.h> //perror
#include <stdlib.h> //perror
#include <string.h> //strlen,strncmp,strcmp
#include <time.h> //time
#define SERV_PORT 8888
#define SERV_ADDR "127.0.0.1"
#define ERROR(par) {perror((par));exit(-1);}
#define MAXLEN 128
char recvmesg[MAXLEN];
char sendmesg[MAXLEN];
int main(){
memset(recvmesg,0,sizeof(recvmesg));
memset(sendmesg,0,sizeof(sendmesg));
sockaddr_in skadd_in;
skadd_in.sin_family=PF_INET;
skadd_in.sin_port=htons(SERV_PORT);
skadd_in.sin_addr.s_addr=inet_addr(SERV_ADDR);
int sockfd=socket(PF_INET,SOCK_STREAM,0);
if(sockfd<0)ERROR("socket err");
//set socket option to avoid err
int on=1;
if((setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)ERROR("setsocketopt err");
if(bind(sockfd,(sockaddr*)&skadd_in,sizeof(skadd_in))<0)ERROR("bind err");
if(listen(sockfd,5)<0)ERROR("listen err");
int confd;
if((confd=accept(sockfd,NULL,NULL)) <0)ERROR("accept err");
printf("accept ok!!\n");
while(1){
if(recv(confd,recvmesg,sizeof(recvmesg),0)<=0)ERROR("recv err");
printf("recv ok!!\n");
if(0==strncmp(recvmesg,"echo",strlen("echo"))){
printf("recv cmd: echo\n");
do{
memset(recvmesg,0,sizeof(recvmesg));
if(recv(confd,recvmesg,sizeof(recvmesg),0)<=0)ERROR("recv err");
printf("echo mode:cur recvmesg is %s!!\n",recvmesg);
memcpy(sendmesg,recvmesg,sizeof(recvmesg)+1);
send(confd,sendmesg,strlen(sendmesg),0);
memset(sendmesg,0,sizeof(sendmesg));
}while(0!=strncmp(recvmesg,"eof",strlen("eof")));
}
else if(0==strncmp(recvmesg,"time",strlen("time"))){
printf("recv cmd: time\n");
time_t timenow;
time(&timenow);
memcpy(sendmesg,ctime(&timenow),sizeof(ctime(&timenow)+1));
send(confd,sendmesg,strlen(sendmesg),0);
}
else if(0==strncmp(recvmesg,"quit",strlen("quit"))){
memcpy(sendmesg,"server closed",sizeof("server closed")+1);
send(confd,sendmesg,strlen(sendmesg),0);
break;
}else{
printf("recv cmd:others\n");
memcpy(sendmesg,"cmd invaild",sizeof("cmd invaild")+1);
send(confd,sendmesg,strlen(sendmesg),0);
}
}
close(sockfd);
return 0;
}
然后是客户端:
#include <sys/socket.h> //socket
#include <unistd.h> //close
#include <netinet/in.h> //sockaddr_in
#include <arpa/inet.h> //inet_addr
#include <stdio.h> //perror
#include <stdlib.h> //perror
#include <string.h> //strlen,strncmp,strcmp
#define SERVERIP "127.0.0.1"
#define SERVERPORT 8888
#define ERROR(par) {perror((par));exit(-1);}
#define MAXLEN 128
char message[MAXLEN];
int main()
{
memset(message,0,sizeof(message));
sockaddr_in servaddr;
servaddr.sin_family=PF_INET;
servaddr.sin_port=htons(SERVERPORT);
servaddr.sin_addr.s_addr=inet_addr(SERVERIP);
int sockfd=socket(PF_INET,SOCK_STREAM,0);
if(sockfd<0)ERROR("socket err");
if(connect(sockfd,(sockaddr*)&servaddr,sizeof(servaddr))<0)ERROR("connect err");
while(1){
printf("enter your cmd(echo/time/quit): ");
memset(message,0,sizeof(message));
fgets(message,MAXLEN,stdin);
send(sockfd,message,strlen(message),0);
if(0==strncmp(message,"echo",strlen("echo"))){
do{
memset(message,0,sizeof(message));
fgets(message,MAXLEN,stdin);
send(sockfd,message,strlen(message),0);
memset(message,0,sizeof(message));
if(recv(sockfd,message,MAXLEN,0)<0)ERROR("recv err");
printf("%s\n",message);
}while(0!=strncmp(message,"eof",strlen("eof")));
}
else{
if(recv(sockfd,message,MAXLEN,0)<0)ERROR("recv err");
printf("%s\n",message);
if(0==strncmp(message,"server closed",strlen("server closed"))){
break;
}
}
}
close(sockfd);
return 0;
}
注意点:
1.同一机器上实现客户端和服务端,使用回送地址(本机地址)作为服务端IP。
2.使用setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))设置套接字选项。一般一端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR让端口释放后立即可用。SO_REUSEADDR用于对TCP套接字处于TIME_WAIT状态下的socket,允许重复绑定使用。server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项。
3.“<”运算符优先级高于“=”,对accpet返回值赋值需要加括号。否则recv时会造成错误。
4.使用recv,send进行buf操作后需要对buf清零。memset和bzero皆可。
效果: