当前位置:   article > 正文

Linux网络编程——TCP连接实现服务端返回客户端发送的两数之和_编程实现,客户端输入两个数,服务端计算结果,并返回。要求服务端用多线程实现

编程实现,客户端输入两个数,服务端计算结果,并返回。要求服务端用多线程实现

Linux网络编程实验二

要求:

  • 服务端返回客户端发送的两个整数之和。
  • 服务器提供并发服务

建立TCP连接的基本步骤:

参考:https://blog.csdn.net/QQ1402369668/article/details/86090092

在这里插入图片描述
服务端:

  • 创建监听socket套接字,套接字就相当于用于通信的工具,比如要打电话给别人,首先得获得一个手机,在代码中体现为:
int listenSocket=socket(AF_INET,SOCK_STREAM,0);

//AF_INET    :表示IPv4域
//SOCK_STREAM:表示使用TCP协议
//0          :默认参数
  • 1
  • 2
  • 3
  • 4
  • 5
openSUSE 用户手册中对socket()的描述:
  • 1

在这里插入图片描述

  • 创建并初始化地址结构体,包括获得本机的IP地址和初始化指定的端口号
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
  • 1
  • 2
  • 3
  • 4
  • 绑定地址,监听端口
    //绑定
    bind(listenSocket,(struct sockaddr*)&addrSrc,sizeof(struct sockaddr_in)); 
   //监听
    listen(listenSocket,5);//5 表示最多允许和5个客户端建立TCP连接
  • 1
  • 2
  • 3
  • 4

此时服务端的监听套接字准备完毕,下一步是在该监听套接字上监听客户端的请求,在代码中体现为:在死循环中调用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);   
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

客户端

  • 创建套接字
    服务端希望与客户端通信,首先得获得一个“通信工具”——socket套接字
  • 创建并初始化地址结构体
	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");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

与服务端不同的是: 地址结构体中ip地址和端口号是要访问的服务端的地址和端口号

  • 调用connect()函数进行连接
  • 连接成功后便可向服务端发送数据,并接受服务端返回的运算结果打印到屏幕上

客户端完整代码

#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;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

运行结果:(先运行服务端,在运行客户端)
在这里插入图片描述
在这里插入图片描述

改进:使用信号机制

由于父进程一直在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);
        
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96

客户端代码同上

运行结果:

客户端发起请求,并获得了服务端返回的结果
在这里插入图片描述
服务端:显示了回收的子进程pid号
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/158707
推荐阅读
相关标签
  

闽ICP备14008679号