仓库:https://gitee.com/tgwTTT/linux-learning-dai/tree/master/socketTcp码

Tcpsocket介绍

TCPSocket 编程是 Linux 网络开发的基础,主要用于实现可靠的网络通信。服务端通过监听端口,等待客户端连接,双方通过套接字进行数据收发。TCP 协议保证了数据的可靠传输和顺序性,广泛应用于 Web 服务、即时通讯、文件传输等场景。

TCP 服务端编程流程

典型的 TCP 服务端编程流程包括以下几个步骤:

  1. 创建套接字
    使用socket 创建一个 TCP 套接字,指定协议族(如 IPv4)和类型(流式)。
  2. 绑定端口
    通过bind将套接字绑定到本地 IP 和端口,使其能接收客户端请求。
  3. 监听连接
    使用listen让套接字进入监听状态,准备接受客户端连接。
  4. 接受连接
    通过accept 阻塞等待客户端连接,每有一个连接就返回一个新的套接字。
  5. 数据收发
    使用read和write实现与客户端的数据交互。
  6. 关闭连接
    通信结束后,关闭套接字,释放资源。

接口介绍:

socket():

socket()打开⼀个⽹络通讯端⼝,如果成功的话,就像open()⼀样返回⼀个⽂件描述符;

应⽤程序可以像读写⽂件⼀样⽤read/write在⽹络上收发数据;

如果socket()调⽤出错则返回-1;

对于IPv4, family参数指定为AF_INET;

对于TCP协议,type参数指定为SOCK_STREAM, 表⽰⾯向流的传输协议

bind()

服务器程序所监听的⽹络地址和端⼝号通常是固定不变的,客⼾端程序得知服务器程序的地址和端⼝号后就可以向服务器发起连接; 服务器需要调⽤bind绑定⼀个固定的⽹络地址和端⼝号;

bind()成功返回0,失败返回-1。

bind()的作⽤是将参数sockfd和myaddr绑定在⼀起, 使sockfd这个⽤于⽹络通讯的⽂件描述符监听myaddr所描述的地址和端⼝号;

struct sockaddr *是⼀个通⽤指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,⽽它们的⻓度各不相同,所以需要第三个参数addrlen指定结构体的⻓度;

listen

listen()声明sockfd处于监听状态, 并且最多允许有backlog个客⼾端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这⾥设置不会太⼤(⼀般是5)

listen()成功返回0,失败返回-1;

accept:

三次握⼿完成后, 服务器调⽤accept()接受连接;

如果服务器调⽤accept()时还没有客⼾端的连接请求,就阻塞等待直到有客⼾端连接上来;

addr是⼀个传出参数,accept()返回时传出客⼾端的地址和端⼝号; • 如果给addr 参数传NULL,表⽰不关⼼客⼾端的地址;

addrlen参数是⼀个传⼊传出参数(value-result argument), 传⼊的是调⽤者提供的, 缓冲区addr的⻓度以避免缓冲区溢出问题, 传出的是客⼾端地址结构体的实际⻓度(有可能没有占满调⽤者提供的缓冲区);

connect

客⼾端需要调⽤connect()连接服务器;

connect和bind的参数形式⼀致, 区别在于bind的参数是⾃⼰的地址, ⽽connect的参数是对⽅的地址;

connect()成功返回0,出错返回-1;

简单的echosever举例:

namespace Tcp
{
    class Tcpsocket;
    using task_t = std::function;
    const int max_thread_num=10;
    class ThreadData{
        public:
            ThreadData(int sockfd,NetAddr clientaddr,Tcpsocket* tcpsocket):sockfd(sockfd),tcpsocket(tcpsocket),clientaddr(clientaddr){}
        public:
            int sockfd;
            NetAddr clientaddr;
            Tcpsocket* tcpsocket;
    };
    class Tcpsocket : NoCopy
    {
    public:
        Tcpsocket(uint16_t port) : _isrunning(false), _listenfd(-1), _port(port) {}
        ~Tcpsocket() {}
        void Service(int sockfd, NetAddr clientaddr)
    {
        char buf[1024];
        while (true)
        {
            memset(buf, 0, sizeof(buf));
            int n = read(sockfd, buf, sizeof(buf));
            if (n <= 0){
                LOG(LogLevel::INFO) << "client " << clientaddr.stringaddr() << " disconnected";
                break;
            }
            buf[n] = 0;
            LOG(LogLevel::INFO) << "#"<tcpsocket->Service(data->sockfd,data->clientaddr);
            delete data;
            return nullptr;
        }
        void Run()
        {
            _isrunning = true;
            while (_isrunning)
            {
                sleep(1);
                // 获取连接
                struct sockaddr_in peer;
                socklen_t clientlen = sizeof(peer);
                int connfd = accept(_listenfd, (struct sockaddr *)&peer, &clientlen);
                if (connfd < 0)
                {
                    LOG(LogLevel::ERROR) << "accept socket failed";
                    continue;
                }
                NetAddr clientaddr(peer);
                LOG(LogLevel::INFO) << "accept socket success" << clientaddr.stringaddr();
                // 处理链接
            //     pid_t id = fork();
            //     if (id < 0)
            //     {
            //         LOG(LogLevel::FATAL) << "fork eeror";
            //         exit(1);
            //     }
            //     else if (id == 0)
            //     {
            //         // 子进程
            //         // 我们不想让子进程访问listen套接字
            //         close(_listenfd);
            //         if (fork() > 0)
            //         { // 再次fork,子进程退出
            //             exit(0);
            //         }
            //         Service(connfd, clientaddr); // 孤儿进程,系统回收
            //         exit(0);
            //     }
            //     else
            //     {
            //         // 父进程
            //         close(connfd);
            //         int rid = waitpid(id, nullptr, 0);
            //         // 父进程
            //         (void)rid;
            //     }
            ThreadData *data=new ThreadData(_listenfd,_listenaddr,this);
            pthread_t tid;
            pthread_create(&tid, nullptr,Rountine,data);
            //线程池版适合短服务
        //    ThreadPool pool(max_thread_num);
        //     pool.Enqueue([this,&connfd,&clientaddr](){
        //         Service(connfd, clientaddr);
        //     });
        }
            _isrunning = false;
    }
        void Stop()
        {
            this->_isrunning = false;
        }

    public:
        bool _isrunning;
        int _listenfd;
        NetAddr _listenaddr;
        uint16_t _port;
    };
}

client:

void Usage(std::string argv){

    std::cout<<"Usage: "<<argv<<" “<

}

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

    if(argc<3){

        Usage(argv[0]);

        return 1;

    }

    if(argc>3){

        LOG(LogLevel::WARNING)<<"参数过多";

    }

    int sockfd=socket(AF_INET,SOCK_STREAM,0);

    if(sockfd<0){

        LOG(LogLevel::ERROR)<<"Socket creation failed";

        return 1;

    }

    //bind,不需要显示绑定也不需要监听

    std::string serverip=argv[1];

    uint16_t serverport=std::stoi(argv[2]);

    //直接发起连接请求,请求成功后会自动发起绑定

    NetAddr peer(serverip,serverport);

    if(connect(sockfd,(struct sockaddr*)&peer.getAddr(),sizeof(peer.getAddr()))<0){

        LOG(LogLevel::ERROR)<<"Connect failed";

        return 1;

    }

    while(true){

        std::string line;

        std::cout<<"#please Enter message:"<

        std::getline(std::cin,line);

        if(line==”quit”){

            break;

        }

        write(sockfd,line.c_str(),line.size());

        char recvbuf[1024];

        int recvlen=read(sockfd,recvbuf,1024);

        if(recvlen<0){

            LOG(LogLevel::ERROR)<<"Read failed";

            return 1;

        }

        std::cout<<"Recv:"<

    }

    close(sockfd);

    return 0;

}

TCP 与 UDP Socket 编程对比

特性TCP SocketUDP Socket
是否连接面向连接(需三次握手)无连接(无需建立连接)
可靠性可靠,保证顺序和完整性不可靠,可能丢包、乱序
传输方式字节流,适合大数据量传输数据报,适合短消息、广播
编程流程需监听、accept、收发、关闭直接收发,无需监听和 accept
并发处理通常需多线程/进程或异步模型直接处理,每个数据报独立
应用场景Web服务器、文件传输、IMDNS、视频流、实时游戏、广播

UDP 服务端流程简述:

  1. 创建套接字:socket(AF_INET, SOCK_DGRAM, 0)
  2. 绑定端口:bind()
  3. 接收数据:recvfrom()
  4. 发送数据:sendto()
  5. 无需监听和 accept,数据报独立处理。

今天的文章就更新到这,如有错误欢迎指出!!!