Linux:tcp多进程&多线程通信

tcp是面向连接的通信方式,一旦连接断开就无法进行通信

问题:如何在代码中知道连接断开,体现在哪
recv返回值是0,不仅代表没有接收导数据,更重要表现连接断开

  • 如果代码中没有recv函数,也会知道连接断开
    客户端发数据,关闭服务端
    客户端第一次发送数据,会显示发送成功,第二次发送新数据,客户端才会退出
    数据不是直接发送出去的,而是在发送缓冲区中,由OS在合适的时候发送,多次没有发送成功,才能检测到发送不成功
  • 直接退出的原因:连接断开,send发送数据的时候,程序直接异常退出
    但是程序不能因为连接断开而退出程序
    通过修改信号防止程序退出

netstat -anptu
查看当前程序所有网络连接状态
a查看所有信息
n不以读完名称显示端口或地址,比如22端口被识别成ssh
t过滤只显示tcp套接字信息
u过滤只显示udp套接字信息

问题:有时候网络程序关闭后,无法立即启动,会bind绑定地址报错,绑定失败,地址已经被使用
如果服务端主动关闭,无法重新启动;因为主动关闭连接,该连接不会被立即释放(对应的地址和端口依然被占用),而是要等待一段时间
如果客户端主动关闭,

#include<iostream>
#include<stdio.h>
#include<string>
#include<unistd.h>

#include<arpa/inet.h>//字节序转换接口文件
#include<netinet/in.h>//地址结构类型以及协议类型宏文件
#include<sys/socket.h>//套接字接口文件
using namespace std;
class TcpSocket
{
private:
    int sockfd_;
public:
    TcpSocket():sockfd_(-1)
    {}
    ~TcpSocket()
    {
        Close();
        //直接屏蔽掉close函数,每个客户端只能通信一次
        //服务器的newsockfd不断开辟新对象,获取一个新连接收发数据完毕后,
        //就又获取新连接生成新套接字,上次的fd还在服务器中        
    }

    //创建套接字
    bool Socket()
    {
        sockfd_=socket(AF_INET,SOCK_STREAM,0);
        if(sockfd_<0)
        {
            cout<<"创建套接字失败"<<endl;
            return false;
        }
        return true;
    }

    //服务端绑定IP和端口
    //经过封装后,只需要传IP和端口(将本进程与本主机的IP和端口绑定)
    //bind函数只是服务器调用,固定服务器的IP和端口号
    bool Bind(const string& ip,uint16_t port)//port是16位整形
    {
        struct sockaddr_in addr;//定义结构体
        addr.sin_family=AF_INET;
        addr.sin_port=htons(port);//转换端口字节序
        addr.sin_addr.s_addr=inet_addr(ip.c_str());
        
        socklen_t len=sizeof(struct sockaddr_in);//地址长度

        int ret=bind(sockfd_,(struct sockaddr*)&addr,len);//将文件描述符和IP端口绑定
        if(ret<0)
        {
            cout<<"bind error"<<endl;
            return false;
        }
        return true;
    }

    //服务端监听
    //监听是将套接字状态设置成LISTEN状态,只有处于这个状态的套接字才能处理客户端的连接请求
    bool Listen(int backlog)
    {
        int ret=listen(sockfd_,backlog);
        if(ret<0)
        {
            cout<<"listen error"<<endl;
            return false;
        }
        return true;
    }
    bool Connect(const string& srvip,uint16_t srvport)
    {
        struct sockaddr_in addr;
        addr.sin_family=AF_INET;
        addr.sin_addr.s_addr=inet_addr(srvip.c_str());
        addr.sin_port=htons(srvport);
        socklen_t len=sizeof(struct sockaddr_in);

        int ret=connect(sockfd_,(struct sockaddr*)&addr,len);
        if(ret<0)
        {
            cout<<"connect连接失败"<<endl;
            return false;
        }
        return true;
    }

    //服务端获取新连接
    //tcp服务器,涉及到对多个socket进行操作(有多少客服端连接上,就有多少个socket)
    //每一个socket都需要accept,recv,send
    //这三个操作都是阻塞操作,accept如果没有新连接就阻塞,recv没有接收到消息就阻塞
    bool Accept(TcpSocket* newsocket ,string* cli_ip,uint16_t* cli_port)//用来接收客户端的IP和端口
    {
        struct sockaddr_in addr;
        socklen_t addrlen=sizeof(struct sockaddr_in);
        int newsockfd=accept(sockfd_,(struct sockaddr*)&addr,&addrlen);//newfd是进行实际通信的
        if(newsockfd<0)
        {
            cout<<"newfd error"<<endl;
            return false;
        }
        
        
        newsocket->sockfd_=newsockfd;
        *cli_ip=inet_ntoa(addr.sin_addr);//拿到客户端的IP和端口
        *cli_port=ntohs(addr.sin_port);

        return true;
    }

    //发送接口封装后,只需输入要发送的内容就行
    bool Send(const string& body)
    {
        ssize_t ret=send(sockfd_,body.c_str(),body.size(),0);
        if(ret<0)
        {
            cout<<"发送错误"<<endl;
            return false;
        }
        return true;
    }
    bool Recv(string* body)//recv返回值的三种含义 =0表示连接断开
                           //                  <0表示连接出错
                           //                  >0表示接收到数据
    {
        char tmp[1024];
        ssize_t ret=recv(sockfd_,tmp,1023,0);//0表示阻塞接收,缓冲区没数据就阻塞
        body->assign(tmp,ret);//从tmp中截取ret大小的数据放入body中
    }
    bool Close()
    {
        if(sockfd_!=-1)
        {
            close(sockfd_);
            sockfd_=-1;
        }
        return true;
    }
};

TCP客户端

#include"tcp.hpp"
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        cout<<"输入有误"<<endl;
        return -1;
    }
    TcpSocket sockfd;
    string ip=argv[1];
    uint16_t port=std::stoi(argv[2]);

    sockfd.Socket();
    sockfd.Connect(ip,port);

    while(1)
    {
        string buf;
        cout<<"客户端发送的数据:";
        fflush(stdout);
        cin>>buf;
        sockfd.Send(buf);

        buf.clear();
        sockfd.Recv(&buf);
        cout<<"客户端接收到的数据:"<<buf<<endl;
    }
    return 0;
}

TCP服务端


#include"tcp.hpp"
#include<signal.h>
void creatwork(TcpSocket sock);

int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        cout<<"输入有误"<<endl;
        return -1;
    }
    string ip=argv[1];
    uint16_t port=std::stoi(argv[2]);
    TcpSocket sockfd;

    sockfd.Socket();
    sockfd.Bind(ip,port);
    sockfd.Listen(10);

    //客户端发起的连接很多,所以需要循环获取
    while(1)
    {
        TcpSocket newsockfd;//这个新文件描述符是局部变量,循环一次结束就删除了
        string ip;
        uint16_t port;
        bool ret=sockfd.Accept(&newsockfd,&ip,&port);
        if(ret==false)
        {
            //不能让一个客户端连接出问题让程序崩溃退出
            continue;
        }

        //创建子进程
        creatwork(newsockfd);
    }
    return 0;
}



//父子进程代码共享,数据独有
//一旦创建了子进程,子进程会复制父进程,套接字父子进程各有一份
//子进程通过这个描述符与客户端通信
//父进程是不对该描述符操作,子进程将自己文件描述符关闭后不影响父进程,需要将父进程的描述符关闭
void creatwork(TcpSocket sock)
{
    pid_t pid=fork();
    if(pid>0)
    {
        sock.Close();
        return;
    }
    else if(pid==0)
    {
        while(1)
        {
            string buf;
            bool ret=sock.Recv(&buf);
            cout<<"服务端接收的数据为:"<<buf<<endl;

            buf.clear();
            fflush(stdout);
            cout<<"服务端发送的数据:";
            cin>>buf;
            sock.Send(buf);

            if(ret==false)
            {
                sock.Close();
                exit(-1);
            }
        }
    }
    exit(1);//子进程连接错误,或者收到数据错误,break跳出循环退出
            //不能让子进程退出后运行和父进程一样的代码
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值