套接字编程4(tcp)

Posted 李憨憨_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了套接字编程4(tcp)相关的知识,希望对你有一定的参考价值。

用类实例化对象

1.创建套接字

        bool Socket(){
            _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if(_sockfd < 0){
                perror("socket error");
                return false;
            }
            return true;
        }

2.绑定地址信息

        bool Bind(const string &ip, const uint16_t port){
            sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(&ip[0]);
            socklen_t len = sizeof(sockaddr);
            int ret = bind(_sockfd, (sockaddr*)&addr, len);
            if(ret < 0){
                perror("bind error");
                return false;
            }
            return true;
        }

3.监听套接字

        bool Listen(int backlog = LISTEN_BACKLOG){
            //listen(描述符,同一时间连接数)
            int ret = listen(_sockfd, backlog);
            if(ret < 0){
                perror("listen error");
                return false;
            }
            return true;
        }

4.新建连接

        bool Connect(const string &ip, const uint16_t port){
            sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(&ip[0]);
            socklen_t len = sizeof(sockaddr);
            //connect(描述符, 服务端地址, 地址长度)
            int ret = connect(_sockfd, (sockaddr*)&addr, len);
            if(ret < 0){
                perror("connect error");
                return false;
            }
            return true;
        }

5.获取新建连接

        bool Accept(TcpSocket *sock, string *ip = NULL, uint16_t *port = NULL){
            //int accept(监听套接字, 获取客户端地址, 长度)
            sockaddr_in addr;
            socklen_t len = sizeof(sockaddr_in);
            int newfd = accept(_sockfd, (sockaddr*)&addr, &len);
            if(newfd < 0){
                perror("newfd error");
                return false;
            }
            sock->_sockfd = newfd;
            if(ip != NULL){
                *ip = inet_ntoa(addr.sin_addr);
            }
            if(port != NULL){
                *port = ntohs(addr.sin_port);
            }
            return true;
        }

6.接收数据

        bool Recv(string *buf){
            //int recv(描述符, 空间, 数据长度, 标志位)
            //返回值: 实际获取大小, 0-连接断开, -1-出错了
            char tmp[4096] = {0};
            int ret = recv(_sockfd, tmp, 4096, 0);
            if(ret < 0){
                perror("recv error");
                return false;
            }else if(ret == 0){
                printf("peer shutdown");
                return false;
            }
            buf->assign(tmp, ret);
            return true;
        }

7.发送数据

        bool Send(const string &data){
            //int send(描述符, 数据, 长度, 标志位)
            int total = 0;
            while(total < data.size()){
                int ret = send(_sockfd, &data[0] + total, data.size() - total, 0);
                if(ret < 0){
                    perror("send error");
                    return false;
                }
                total += ret;
            }
            return true;
        }

8.关闭套接字

        bool Close(){
            if(_sockfd != -1){
                close(_sockfd);
            }
            return true;
        }

服务端

#include "tcpsocket.hpp"
using namespace std;

int main(int argc, char *argv[]){
    //通过程序运行参数指定服务端要绑定的地址
    //./tcp_srv 192.168.2.2 9000
    if(argc != 3){
        printf("usage: ./tcp_srv 192.168.2.2 9000\\n");
        return -1;
    }
    string srvip = argv[1];
    uint16_t srvport = stoi(argv[2]);
    TcpSocket lst_sock;//监听套接字
    //1.创建套接字
    CHECK_RET(lst_sock.Socket());
    //2.绑定地址信息
    CHECK_RET(lst_sock.Bind(srvip, srvport));
    //3.开始监听
    CHECK_RET(lst_sock.Listen());
    while(1){
        //4.获取新建连接
        TcpSocket cli_sock;
        string cli_ip;
        uint16_t cli_port;
        bool ret = lst_sock.Accept(&cli_sock, &cli_ip, &cli_port);
        if(ret == false){
            continue;
        }
        cout << "get new conn: " << cli_ip << "-" << cli_port << endl;
        //5.收发数据--使用获取的新建套接字进行通信
        string buf;
        ret = cli_sock.Recv(&buf);
        if(ret == false){
            cli_sock.Close();
            continue;
        }
        cout << "client say: " << buf << endl;
        buf.clear();
        cout << "server say: ";
        cin >> buf;
        ret = cli_sock.Send(buf);
        if(ret == false){
            cli_sock.Close();
        }
    }
    //6.关闭套接字
    lst_sock.Close();
}

客户端

#include "tcpstocket.hpp"
using namespace std;

int main(int argc, char *srgv){
    //通过参数传入要连接的服务端的地址信息
    if(argc != 3){
        printf("usage ./tcp_cli srvip srvport\\n");
        return -1;
    }
    string srvip = srgv[1];
    uint16_t srvport = stoi(argv[2]);
    TcpSocket cli_sock;
    //1.创建套接字
    CHECK_RET(cli_sock.Socket());
    //2.绑定地址信息
    //3.向服务端发起连接
    CHECK_RET(cli_sock.Connect(srvip, srvport));
    while(1){
        //4.收发数据
        string buf;
        cout << "client say: ";
        cin >> buf;
        CHECK_RET(cli_sock.Send(buf));
        buf.clear();

        CHECK_RET(cli_sock.Recv(&buf));
        cout << "server say: " << buf << endl;
    }
    //5.关闭套接字
    CHECK_RET(cli_sock.Close());
    return 0;
}

存在问题

在这里插入图片描述
这个流程里面存在一个问题:
  当我们现在有一个客户端, 这个客户端说我给你发送一个请求过来了(开始监听之后我们就可以发送请求了), 连接请求一过来, 这个连接的建立在内核里面就完成了. 而我们accept(获取新建连接)接口也是一个阻塞接口(如果有新建连接我就获取, 如果没有我就等着). 所以这时候我们新建连接建立成功了之后, 然后使用新连接接收数据, 使用新连接发送数据, 走到这一步之后需要注意第一次循环结束了.
它走上来又要获取新建连接, 这时候这个客户端已经连接成功了, 但是程序因为走了一圈, 给你通信了一次之后, 走到获取新建连接这一步了. 但是这个时候就算你这个客户端给我发送数据, 我是走不下去去接收的, 也走不下去去发送, 因为我的程序流程就走到获取新建连接这里不动了, 我在等待下一个新的客户端. 所以我们再穿件一个客户端又能通信一次, 第二个客户端也卡在了新建连接又不动了.
所以处理这个问题的本质是什么?
accpet与recv以及send都是阻塞接口, 任意一个接口的调用, 都有可能导致服务端流程阻塞.
本质原因: 当前的服务端, 因为不知道什么时候有新连接到来, 什么时候哪个客户端有数据到来, 因此流程只能固定的去调用接口, 但是这种调用方式有可能会造成阻塞

解决方案

多执行流并发处理
  为每个客户端都创建一个执行流负责与这个客户端进行通信
  好处:
   1.主线程卡在获取新建连接这里, 但是不影响客户端的通信
   2,某个客户端的通信阻塞, 也不会影响主线程以及其他线程
在主线程中, 获取新建连接,一旦获取到了则创建一个执行流, 通过这个新建连接与客户端进行通信.
多线程: 普通线程与主线程数据共享, 指定入口函数执行
  主线程不能随意释放套接字, 因为资源共享, 一旦释放其他线程无法使用.
多进程: 子进程复制了父进程, 但是数据独有.
  1.注意僵尸进程的处理.
  2.注意父子进程数据独有, 父进程用不到新建套接字因此创建子进程之后直接释放掉, 否则会造成资源泄露.

以上是关于套接字编程4(tcp)的主要内容,如果未能解决你的问题,请参考以下文章

套接字编程4(tcp)

第4章 基本tcp套接字编程

C/C++ 网络编程4: 基本TCP套接字编程

2-4:套接字(Socket)编程之TCP通信

Linux:UDP Socket编程(代码实战)

Linux:UDP Socket编程(代码实战)