自学内容网 自学内容网

Linux之socket编程(下)

目录

简单的TCP网络小程序


上一期我们已经学习了socket编程的相关接口,并且实现了一个简单的UDP网络小程序,本期的主要内容就是实现一个简单的TCP网络小程序。

简单的TCP网络小程序

tcp_sever.cc

#include<iostream>
#include<sys/types.h>
#include<cerrno>
#include<string>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<unistd.h>

int main(int args ,char* argv[])
{
    //1.创建套接字
    int listen_sock=socket(AF_INET,SOCK_STREAM,0);
    if(listen_sock<0)
    {
        std::cout<<"listen_socket create erro"<<errno<<std::endl;
        return 1;
    }

    //2.绑定IP+端口号
    struct sockaddr_in local;
    memset(&local,0,sizeof(local));
    local.sin_family=AF_INET;
    local.sin_addr.s_addr=INADDR_ANY;
    local.sin_port=htons(atoi(argv[1]));
    if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        std::cout<<"bind error"<<std::endl;
        return 2;
    }

    //3.TCP是面向连接的,所以得先监听
    const int back_log=5;
    if(listen(listen_sock,back_log)<0)
    {
        std::cout<<"listen error"<<std::endl;
        return 3;
    }

    //4.监听到连接之后,在进行获取连接,获取连接如果没有获取成功,需要不断进行获取,所以需要循环
    for(;;)
    {
        struct sockaddr_in peer;
        socklen_t len=sizeof(peer);
        int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
        if(new_sock<0)
        {
            continue;
        }

        //5.获取连接成功,提供服务
        while(1)
        {
            char buffer[1024];
            memset(buffer,0,sizeof(buffer));
            size_t s=read(new_sock,buffer,sizeof(buffer)-1);
            if(s>0)
            {
                  buffer[s]=0;
                  std::cout<<"#client 发送: "<<buffer<<std::endl;

                  //读到客户端发送的数据之后,向客户端发送返回的数据
                  std::string msg="sever echo: ";
                  msg+=buffer;
                  write(new_sock,msg.c_str(),msg.size());
            }
            else if(s==0)
            {
                std::cout<<"client quit"<<std::endl;
                break;
            }
            else{
                std::cout<<"read error"<<std::endl;
                break;
            }
        }



    }
     
    return 0;
}

其实tcp和udp的socket套接字代码很相似,只不过在tcp的sever端,多了监听连接和获取连接这两个步骤,同时获取连接的是持续不断地获取的,一次获取不到就继续获取。 

tcp_client.cc

#include<iostream>
#include<cerrno>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<unistd.h>

int main(int args,char* argv[])
{
    //1.创建socket套接字文件
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        std::cout<<"socket create error"<<errno<<std::endl;
        return 1;
    }

    struct sockaddr_in sever;
    memset(&sever,0,sizeof(sever));
    sever.sin_family=AF_INET;
    sever.sin_addr.s_addr=inet_addr(argv[1]);
    sever.sin_port=htons(atoi(argv[2]));
    //2.client不需要绑定IP和端口号,所以直接建立连接即可。
    if(connect(sock,(struct sockaddr*)&sever,sizeof(sever))<0)
    {
        std::cout<<"connect failed"<<std::endl;
        return 2;
    }

    //3.连接成功了,使用服务
    while(1)
    {

        std::cout<<"#请输入: ";

        char buffer[1024];
        memset(buffer,0,sizeof(buffer));
        fgets(buffer,sizeof(buffer-1),stdin);

        write(sock,buffer,sizeof(buffer)-1);
        size_t s=read(sock,buffer,sizeof(buffer)-1);

        if(s>0)
        {
            buffer[s]=0;
            std::cout<<" sever echo"<<buffer<<std::endl;
        }
    }

    return 0;
}

tcp的client端和udp的client端也类似,不过因为tcp是面向字节流的有连接通信,网络通信的类型变成了 SOCK_STREAM,同时tcp的client端新增了连接接口。

 运行结果如下。

通过运行结果不难发现,当我们启动服务器进程时,一次只允许一个客户端进程去访问,这究竟是为什么呢?

这是因为在sever端只有一个执行流,且在while循环中死循环,无法跳出死循环去接收其它进程的连接,只要当前运行的一个客户端进程没有退出,sever端口就不能去持续的监听获取连接,所以就导致了一次只允许一个客户端进程去访问。     

要允许多个client进程去访问,就得在sever进程里创建多个执行流。

只需要改造tcp_sever.cc的代码即可。

多进程版本如下。

#include <iostream>
#include <sys/types.h>
#include <cerrno>
#include <string>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

void service(int new_sock)
{
    while (1)
    {
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));
        size_t s = read(new_sock, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << "#client 发送: " << buffer << std::endl;

            // 读到客户端发送的数据之后,向客户端发送返回的数据
            std::string msg = "sever echo: ";
            msg += buffer;
            write(new_sock, msg.c_str(), msg.size());
        }
        else if (s == 0)
        {
            std::cout << "client quit" << std::endl;
            break;
        }
        else
        {
            std::cout << "read error" << std::endl;
            break;
        }
    }
}

int main(int args, char *argv[])
{

    signal(SIGCHLD, SIG_IGN);
    // 1.创建套接字
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0)
    {
        std::cout << "listen_socket create erro" << errno << std::endl;
        return 1;
    }

    // 2.绑定IP+端口号
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = INADDR_ANY;
    local.sin_port = htons(atoi(argv[1]));
    if (bind(listen_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
    {
        std::cout << "bind error" << std::endl;
        return 2;
    }

    // 3.TCP是面向连接的,所以得先监听
    const int back_log = 5;
    if (listen(listen_sock, back_log) < 0)
    {
        std::cout << "listen error" << std::endl;
        return 3;
    }

    // 4.监听到连接之后,在进行获取连接,获取连接如果没有获取成功,需要不断进行获取,所以需要循环
    for (;;)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int new_sock = accept(listen_sock, (struct sockaddr *)&peer, &len);
        if (new_sock < 0)
        {
            continue;
        }

        // 5.获取连接成功,提供服务

        std::cout << "获取连接成功:" << new_sock << std::endl;
    

    // 创建子进程,让子进程去创建服务
    pid_t id = fork();
    if (id == 0)
    {
        // 子进程,子进程继承父进程的文件描述符,但是是两份文件描述符,都指向了打开的同一文件
        // 1.关闭父进程继承下来的监听套接字
        close(listen_sock);
        // 2.提供服务
        service(new_sock);
        // 3.服务提供结束之后,子进程关闭连接之后的新套接字
        close(new_sock);
        exit(0);
    }
    else if (id > 0)
    {
        // 父进程
        // 要不要等待子进程?
        // 如果是阻塞式等待,就必须等待子进程运行结束之后,父进程才能继续往下运行
        // 这样的话还是相当于是串行的连接,所以我们不予等待,再开始忽略子进程的sigchild即可
        // 父进程忽略子进程的sigchild信号之后,操作系统会自动回收子进程的相关资源,子进程同样
        // 不会成为僵尸进程
        //所以父进程啥都不用做
    }
    else
    {
        continue;
    }
}
    return 0;
}

与单进程版本的代码几乎类似,不过我们把创建服务的代码包装成了一个函数。且为了阻止父进程和子进程的串行化,我们让父进程忽略了子进程退出时的sigchild信号,这样就不用去等待子进程,可以在子进程执行服务代码的同时,让父进程继续接收其它进程的连接,这样就保证了多个client端进程可以同时访问sever端进程。 

运行结果如下。

运行结果符合预期,实现了多线程场景下,多个client端进程访问sever端进程。 

到此socket接口编程的学习到此结束。

本期内容到此结束^_^ 

 


原文地址:https://blog.csdn.net/qq_55958734/article/details/145301395

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!