要求:
建立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
#include
#include
#include
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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);
}
}
客户端代码同上
运行结果: