赞
踩
要求:
建立TCP连接的基本步骤:
参考:https://blog.csdn.net/QQ1402369668/article/details/86090092
服务端:
int listenSocket=socket(AF_INET,SOCK_STREAM,0);
//AF_INET :表示IPv4域
//SOCK_STREAM:表示使用TCP协议
//0 :默认参数
openSUSE 用户手册中对socket()的描述:
struct sockaddr_in addrSrc,addrClient;
addrSrc.sin_family=AF_INET;
addrSrc.sin_port=htons(6666);//服务端的监听端口
addrSrc.sin_addr.s_addr=INADDR_ANY;//转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP
//绑定
bind(listenSocket,(struct sockaddr*)&addrSrc,sizeof(struct sockaddr_in));
//监听
listen(listenSocket,5);//5 表示最多允许和5个客户端建立TCP连接
此时服务端的监听套接字准备完毕,下一步是在该监听套接字上监听客户端的请求,在代码中体现为:在死循环中调用accept()函数,若监听到了一个客户端的连接请求,accept()函数便会返回一个新的套接字描述符,此后便用这个连接套接字与客户端进行通信。
由于要求使用并发处理,则在服务端监听到了一个请求后便会创建一个子进程与客户端通信,父进程专门用于监听TCP连接请求,子进程专门用于与客户端交互,比如进行计算等操作。
服务端完整代码:
#include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<stdio.h> #include<stdlib.h> #include<fcntl.h> #include<netinet/in.h> #include<arpa/inet.h> #include<sys/ioctl.h> #include<string.h> int main() { //创建一个socket int listenSocket=socket(AF_INET,SOCK_STREAM,0); pid_t childpid; //配置ip port 协议 struct sockaddr_in addrSrc,addrClient; addrSrc.sin_family=AF_INET; addrSrc.sin_port=htons(6666); addrSrc.sin_addr.s_addr=INADDR_ANY; //绑定 bind(listenSocket,(struct sockaddr*)&addrSrc,sizeof(struct sockaddr_in)); //监听 listen(listenSocket,5); int connfd=0; int len=sizeof(struct sockaddr_in); for(;;) { connfd=accept(listenSocket,(struct sockaddr*)&addrClient,&len); char *ipStr=inet_ntoa(addrClient.sin_addr);//将用整数类型表示的地址转换为字符串型,便于打印观察 printf("connect is %s\n",ipStr); if ( (childpid = fork()) == 0)// 在子进程中进行加法操作 { close(listenSocket);//子进程继承父进程的所有资源,但是子进程只负责进行加法运算,不需要使用监听套接字 char recvBuf[100]={0}; long int sum=0; //接受子进程发过来的两个加数 int ret=recv(connfd,recvBuf,sizeof(recvBuf),0); //打印查看结果 printf("%s\n",recvBuf); //解析所接受到的两个加数,由于是以字符形式发送的,故需要转换为整数型,才能进行加法运算 char *ptr; //用空格将字符串数组分隔开,分别获取两个加数,相当于python中split()函数 ptr=strtok(recvBuf," "); while(ptr!=NULL) { sum+=strtol(ptr,NULL,10); ptr = strtok(NULL, ","); } //将运算结果格式化后返回给客户端 char Add_Result[1024]={0}; sprintf(Add_Result,"the add result is: %ld",sum); send(connfd,Add_Result,strlen(Add_Result)+1,0); //退出子进程 exit(0); } //在父进程中关闭连接套接字,父进程只负责监听连接请求,剩下的处理工作只交给子进程完成,故父进程本身不需要连接套接字 close(connfd); } }
客户端
struct sockaddr_in addrSrc;
memset(&addrSrc,0,sizeof(struct sockaddr_in));
addrSrc.sin_family=AF_INET;
addrSrc.sin_port=htons(6666);
addrSrc.sin_addr.s_addr=inet_addr("127.0.0.1");
与服务端不同的是: 地址结构体中ip地址和端口号是要访问的服务端的地址和端口号
客户端完整代码
#include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<stdio.h> #include<stdlib.h> #include<fcntl.h> #include<netinet/in.h> #include<arpa/inet.h> #include<sys/ioctl.h> #include<string.h> int main(int argc,int **argv) { //创建一个socket int clientSocket=socket(AF_INET,SOCK_STREAM,0); //配置ip port 协议 struct sockaddr_in addrSrc; memset(&addrSrc,0,sizeof(struct sockaddr_in)); addrSrc.sin_family=AF_INET; addrSrc.sin_port=htons(6666); addrSrc.sin_addr.s_addr=inet_addr("127.0.0.1"); connect(clientSocket,(const struct sockaddr *)&addrSrc,sizeof(struct sockaddr_in)); send(clientSocket,argv[1],strlen(argv[1]),0); send(clientSocket," ",1,0);//两个数之间发送一个空格,用于分割两个数 send(clientSocket,argv[2],strlen(argv[2]),0); //接受服务端的运算结果 char recvBuf[1024]={0}; recv(clientSocket,recvBuf,sizeof(recvBuf)-1,0); //显示到屏幕上 printf("recv from server is :%s\n",recvBuf); //关闭套接字 close(clientSocket); return 0; }
运行结果:(先运行服务端,在运行客户端)
改进:使用信号机制
由于父进程一直在accept()函数上监听客户端的请求,有请求时,父进程只负责3次握手建立TCP连接,之后的运算便完全交给子进程来处理,父进程还是回到监听套接字上监听,没有请求时,便阻塞在accept()函数上。当子进程运算完后便退出了,此时父进程无法正常回收子进程,导致子进程变成了系统中的僵尸进程,消耗着系统资源。
解决方法:
使用信号机制,子进程结束时,会向父进程发送一个SIGCHLD信号,但是默认情况下,父进程在收到该信号时都会忽略,不进行任何操作。
信号也称为软件中断,对应的就有中断处理函数,即可以自定义每当收到SIGCHLD信号时,父进程进行什么操作。
注意事项:
由于父进程一直在调用accept()函数,accept()函数本质上时一个慢系统调用,意思就是:它如果被信号打断的话,系统是不会再自动返回到accept()函数里面继续执行的。所以必须在程序中通过执行代码再返回到accept()中,这样父进程在中断处理函数中回收完了子进程,还可以继续回到accept()函数上继续监听。
服务端完整代码
#include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<stdio.h> #include<stdlib.h> #include<fcntl.h> #include<netinet/in.h> #include<arpa/inet.h> #include<sys/ioctl.h> #include<string.h> #include "signal.h" #include "errno.h" //中断处理函数,父进程若收到了SIGCHLD信号,就调用该函数回收子进程,并打印出子进程的pid号 void sig_chld(int signo) { pid_t pid; int stat; while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0) printf("child %d terminated\n", pid); return; } int main() { //创建一个socket int listenSocket=socket(AF_INET,SOCK_STREAM,0); pid_t childpid; //配置ip port 协议 struct sockaddr_in addrSrc,addrClient; addrSrc.sin_family=AF_INET; addrSrc.sin_port=htons(6666); addrSrc.sin_addr.s_addr=INADDR_ANY; //绑定 bind(listenSocket,(struct sockaddr*)&addrSrc,sizeof(struct sockaddr_in)); //监听 listen(listenSocket,5); //调用signal()函数,当收到SIGCHLD信号时,调用自己编写的sig_chld函数,注:该函数需要在accept()调用之前调用,并且只能调用一次 signal(SIGCHLD, sig_chld); int connfd=0; int len=sizeof(struct sockaddr_in); for(;;) { connfd=accept(listenSocket,(struct sockaddr*)&addrClient,&len); if(connfd<0) { //由于accept()是满系统调用,判断如果是信号打断了该函数的话,则继续continue,进入下一轮循环,重新调用accept()函数 if (errno == EINTR) continue; /* back to for() */ else printf("accept error"); } char *ipStr=inet_ntoa(addrClient.sin_addr); printf("connect is %s\n",ipStr); if ( (childpid = fork()) == 0)// in the child process { close(listenSocket); char recvBuf[100]={0}; long int sum=0; int ret=recv(connfd,recvBuf,sizeof(recvBuf),0); //puts(recvBuf); printf("recvBuf=%s\n",recvBuf); char *ptr; ptr=strtok(recvBuf," "); while(ptr!=NULL) { sum+=strtol(ptr,NULL,10); ptr = strtok(NULL, ","); } printf("sum=%ld",sum); char Add_Result[1024]={0}; sprintf(Add_Result,"the add result is: %ld",sum); send(connfd,Add_Result,strlen(Add_Result)+1,0); exit(0); } //关闭套接字in father process close(connfd); } }
客户端代码同上
运行结果:
客户端发起请求,并获得了服务端返回的结果
服务端:显示了回收的子进程pid号
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。