当前位置:   article > 正文

【网络编程】demo版TCP网络服务器实现_tcp demo

tcp demo

一、引入

UDP和TCP的区别:

对于TCP协议有几个特点:

1️⃣ 传输层协议
2️⃣ 有连接(正式通信前要先建立连接)
3️⃣ 可靠传输(在内部帮我们做可靠传输工作)
4️⃣ 面向字节流

对于UDP协议有几个特点:

1️⃣ 传输层协议
2️⃣ 无连接
3️⃣ 不可靠传输
4️⃣ 面向数据报

可以看到TCP对比UDP会建立链接

其他的接口跟UDP其实没什么区别:【网络编程】demo版UDP网络服务器实现

二、服务端实现

2.1 创建套接字socket

在通信之前要先把网卡文件打开。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

RETURN VALUE
On success, a file descriptor for the new socket is returned.  
On error, -1 is returned, and errno is set appropriately.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这个函数的作用是打开一个文件,把文件和网卡关联起来。

参数介绍:

domain:一个域,标识了这个套接字的通信类型(网络或者本地)。
在这里插入图片描述
只用关注上面两个类,第一个AF_UNIX表示本地通信,而AF_INET表示网络通信。
type:套接字提供服务的类型。
在这里插入图片描述
这一章我们讲的式TCP,所以使用SOCK_STREAM
protocol:想使用的协议,默认为0即可,因为前面的两个参数决定了,就已经决定了是TCP还是UDP协议了。

返回值:

成功则返回打开的文件描述符(指向网卡文件),失败返回-1。

而从这里我们就联想到系统中的文件操作,未来各种操作都要通过这个文件描述符,所以在服务端类中还需要一个成员变量表示文件描述符。

#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include "log.hpp"

class TCPServer
{
static const uint16_t gport = 8080;
public:
    TCPServer(cosnt uint16_t& port = gport)
        : _sock(-1)
        , _port(port)
    {}

    void InitServer()
    {
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if(_sockfd == -1)
        {
            std::cerr << "create socket error" << std::endl;
            exit(1);
        }
        std::cout << "create socket success" << std::endl;
    }

    void start()
    {}
private:
    int _sock;
    uint16_t _port;
};
  • 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

2.2 绑定bind

#include <sys/socket.h>

int bind(int socket, const struct sockaddr *address,
       socklen_t address_len);

RETURN VALUE
Upon successful completion, bind() shall return 0; 
otherwise, -1 shall be returned and errno set to indicate the error.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

参数介绍:

socket:创建套接字的返回值。
address:通用结构体(【网络编程】socket套接字有详细介绍)。
address_len:传入结构体的长度。

所以我们要先定义一个sockaddr_in结构体填充数据,在传递进去。
在这里插入图片描述
然后就是跟UDP一样,先初始化结构体,再处理IP和端口。
要注意IP要绑定任意IP也就是INADDR_ANY
至于为什么再上一章【网络编程】demo版UDP网络服务器实现有过详细讲解。

void InitServer()
{
    _sock = socket(AF_INET, SOCK_STREAM, 0);
    if(_sockfd == -1)
    {
        std::cerr << "create socket error" << std::endl;
        exit(1);
    }
    std::cout << "create socket success" << std::endl;
    struct sockaddr_in si;
    // 初始化结构体
    bzero(&si, sizeof si);
    si.sin_family = AF_INET;
    si.sin_port = htons(_port);// 主机转网络序列
    si.sin_addr.s_addr = INADDR_ANY;
    if(bind(_sock, (struct sockaddr*)&si, sizeof si) < 0)
    {
        std::cout << "bind socket error" << std::endl;
        exit(1);
    }
    std::cout << "bind socket success" << std::endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

2.3 设置监听状态listen

TCP跟UDP的不同在这里就体现了出来。
要把socket套接字的状态设置为listen状态。只有这样才能一直获取新链接,接收新的链接请求。

举个例子:
我们买东西如果出现了问题会去找客服,如果客服不在那么就回复不了,所以规定了客服在工作的时候必须要时刻接收回复消息,这个客服所处的状态就叫做监听状态

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

RETURN VALUE
On success, zero is returned.  
On error, -1 is returned, and errno is set appropriately.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

关于第二个参数backlog后边讲TCP协议的时候介绍,目前先直接用。

static const int gbacklog = 10;
  • 1
void InitServer()
{
    _sock = socket(AF_INET, SOCK_STREAM, 0);
    if(_sock == -1)
    {
        std::cerr << "create socket error" << std::endl;
        exit(1);
    }
    std::cout << "create socket success" << std::endl;
    struct sockaddr_in si;
    // 初始化结构体
    bzero(&si, sizeof si);
    si.sin_family = AF_INET;
    si.sin_port = htons(_port);// 主机转网络序列
    si.sin_addr.s_addr = INADDR_ANY;
    if(bind(_sock, (struct sockaddr*)&si, sizeof si) < 0)
    {
        std::cout << "bind socket error" << std::endl;
        exit(1);
    }
    std::cout << "bind socket success" << std::endl;
    // 设置监听状态
    if(listen(_sock, gbacklog) < 0)
    {
        std::cout << "listen socket error" << std::endl;
        exit(1);
    }
    std::cout << "listen socket success" << std::endl;
}
  • 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

2.4 获取新链接accept

上面初始化完毕,现在开始就是要运行服务端,而TCP不能直接发数据,因为它是面向链接的,必须要先建立链接

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

RETURN VALUE
On success, these system calls return a nonnegative integer that is a descriptor for the accepted socket.  
On error, -1 is returned, and errno is set appropriately.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

参数介绍:

sockfd文件描述符,找到套接字
addr输入输出型参数,是一个结构体,用来获取客户端的信息。
addrlen输入输出型参数,客户端传过来的结构体大小。

返回值:

成功返回一个文件描述符
失败返回-1

而我们知道sockfd本来就是一个文件描述符,那么这个返回的文件描述符是什么呢?
举个例子:

我们去吃饭的时候会发现每个店铺门口都会有人来招揽顾客,这个人把我们领进去门店后,然后他就会继续站在门口继续招揽顾客,而我们会有里面的服务员来招待我们,给我们提供服务。

这里的揽客的人就是sockfd,而服务员就是返回值的文件描述符。
意思就是sockfd的作用就是把链接从底层获取上来,返回值的作用就是跟客户端通信。

从这里就知道了成员变量中的_sock并不是通信用的套接字,而是获取链接的套接字。为了方便观察,我们可以把所有的_sock换_listensock

void start()
{
    while(1)
    {
        // 获取新链接
        struct sockaddr_in si;
        socklen_t len = sizeof si;
        int sock = accept(_listensock, (struct sockaddr*)&si, &len);
        if(sock < 0)
        {
            // 获取链接失败无影响,继续获取即可
            std::cout << "accept error, continue" << std::endl;
            continue;
        }
        std::cout << "accept a new link success" << std::endl;
        std::cout << "sock: " << sock << std::endl;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2.5 获取信息与返回信息(文件操作)

上面获取到了通信用的套接字sock,而因为TCP通信是面向字节流的,所以后续通信全部是用文件操作(IO),因为文件也是面向字节流的

IO的操作我们可以封装一个函数。

void ServerIO(int sock)
{
    char buf[1024];
    // 接收消息       
    while(1)
    {
        ssize_t n = read(sock, buf, sizeof buf - 1);
        if(n > 0)
        {
            buf[n] = '\0';
            std::cout << "read a message: " << buf << std::endl;
            // 把消息发送回去
            std::string outbuf;
            outbuf += "Server[echo]#";
            outbuf += buf;
            write(sock, outbuf.c_str(), outbuf.size());
        }
        else if(n == 0)
        {
            // 代表客户端退出
            std::cout << "Client quit" << std::endl;
            break;
        }
    }
}
  • 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

当IO完后要记得关闭文件描述符sock,不然会导致可用描述符越来越少。
在这里插入图片描述
验证发现可以运行
在这里插入图片描述在这里插入图片描述

三、客户端实现

3.1 创建套接字socket

Socket可以看成在两个程序进行通讯连接中的一个端点,一个程序将一段信息写入Socket中,该Socket将这段信息发送给另外一个Socket中,使这段信息能传送到其他程序中。
所以客户端也需要一个套接字。

void initClient()
{
    _sock = socket(AF_INET, SOCK_STREAM, 0);
    if(_sock < 0)
    {
        std::cout << "create socket error" << std::endl;
        exit(1);
    }
    std::cout << "create socket success" << std::endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3.2 绑定问题

跟上一章一样要绑定,但是不能显示绑定

3.3 发起链接connect

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,
            socklen_t addrlen);
            
RETURN VALUE
If the connection or binding succeeds, zero is returned.  
On error, -1 is returned, and errno is set appropriately.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

参数说明:

这里的addraddrlen填入的是服务端信息。

在UDP通信中,客户端在sendto的时候会自动绑定IP和port,TCP这里就是在connect的时候绑定。

void start()
{
    struct sockaddr_in si;
    bzero(&si, sizeof si);
    si.sin_family = AF_INET;
    si.sin_port = htons(_serverport);
    si.sin_addr.s_addr = inet_addr(_serverip.c_str());
    if(connect(_sock, (struct sockaddr*)&si, sizeof si) < 0)
    {
        std::cout << "connect socket error" << std::endl;
    }
    else
    {
        std::string msg;
        while(1)
        {
            std::cout << "Please Enter#";
            std::getline(std::cin, msg);
            write(_sock, msg.c_str(), msg.size());
            // 从服务端读取数据
            char buf[1024];
            int n = read(_sock, buf, sizeof buf - 1);
            if(n > 0)
            {
                buf[n] = '\0';
                std::cout << buf << std::endl;
            }
            else
            {
                break;
            }
        }
    }
}
  • 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

最后在析构的时候要关掉文件描述符。

~TCPClient()
{
    if(_sock >= 0)
    {
        close(_sock);
    }  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

服务端:
在这里插入图片描述
客户端:
在这里插入图片描述
但此时如果我们在用一个客户端链接,会发现无法通信,除非第一个客户端退出。
在这里插入图片描述
这是因为获取新链接后会进入ServerIo中死循环。只要这个客户端不退出,就会一直给这个客户端提供服务。

那怎么保证多个客户端并行呢?

3.4 客户端并行

3.4.1 多进程版

因为fork后子进程会复制父进程的文件描述符。
这里注意子进程并不需要_listensock文件描述符,所以最好关闭。

pid_t id = fork();
if(id == 0)// child
{
    close(_listensock);
    ServerIO(sock);
    close(sock);
    exit(1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

接下来父进程怎么办呢?是等待吗?
如果父进程等待的话又会导致上面的情况,子进程不退出父进程就一直等待。

子进程退出时,会给父进程发送一个SIGCHLD,17号信号。所以有一种解决办法就是用signal函数,在回调函数中把waitpid的参数设置为-1(等待任意进程),就可以回收。

现在我们不用这种办法,我们可以这么写:

pid_t id = fork();
if(id == 0)// child
{
    close(_listensock);
    if(fork() > 0) exit(1);
    ServerIO(sock);
    close(sock);
    exit(1);
}
// father
pid_t ret = waitpid(id, nullptr, 0);
if(ret > 0)
{
    std::cout << "wait success" << ret << std::endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这里的意思就是创建孙子进程,父进程直接退出,让孙子进程执行ServerIO,此时孙子进程就会被操作系统收养,不用我们管,而父进程退出,外边的父进程也等待成功了。

结果演示:
客户端:
在这里插入图片描述
在这里插入图片描述
服务端:
在这里插入图片描述
其实下面的等待可以不用等待,因为SIGCHLD信号默认的处理方式是忽略。
在这里插入图片描述
在这里插入图片描述
这里看到客户端退出了但是文件描述符并没有被回收。
这里的原因是我们只关闭了子进程的文件描述符,没有关闭父进程:
在这里插入图片描述

3.4.2 多线程版

struct ThreadData
{
    TCPServer* _self;
    int _sock;
};

void start()
{
    while(1)
    {
        // 获取新链接
        struct sockaddr_in si;
        socklen_t len = sizeof si;
        int sock = accept(_listensock, (struct sockaddr*)&si, &len);
        if(sock < 0)
        {
            // 获取链接失败无影响,继续获取即可
            std::cout << "accept error, continue" << std::endl;
            continue;
        }
        std::cout << "accept a new link success" << std::endl;
        std::cout << "sock: " << sock << std::endl;
        // 多线程
        pthread_t tid;
        ThreadData* td = new ThreadData({this, sock});
        pthread_create(&tid, nullptr, thread_start, td);
    }
}

static void* thread_start(void* args)
{
    // 线程分离
    pthread_detach(pthread_self());
    ThreadData* tp = static_cast<ThreadData*>(args);
    tp->_self->ServerIO(tp->_sock);
    close(tp->_sock);
    delete tp;
}
  • 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

3.4.3 线程池版

前面我们写过线程池【linux】基于单例模式实现线程池,这里直接拿来用即可。
这里就要修改一下代码,因为ServerIO可以不属于类,所以可以把ServerIO放在任务Task.hpp中。

// Task.hpp
#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <cstdio>

void ServerIO(int sock)
{
    char buf[1024];
    // 接收消息       
    while(1)
    {
        ssize_t n = read(sock, buf, sizeof buf - 1);
        if(n > 0)
        {
            buf[n] = '\0';
            std::cout << "read a message: " << buf << std::endl;
            // 把消息发送回去
            std::string outbuf;
            outbuf += "Server[echo]#";
            outbuf += buf;
            write(sock, outbuf.c_str(), outbuf.size());
        }
        else if(n == 0)
        {
            // 代表客户端退出
            std::cout << "Client quit" << std::endl;
            break;
        }
    }
}

class Task
{
    typedef std::function<void(int)> func_t;
public:
    Task()
    {}

    Task(int sock, func_t func)
        : _sock(sock)
        , _func(func)
    {}

    void operator()()
    {
        _func(_sock);
    }

    std::string tostringTask()
    {
        char buf[64];
        snprintf(buf, sizeof buf, "%d %c %d = ?", _x, _op, _y);
        return buf;
    }
private:
    int _sock;
    func_t _func;
};

// ThreadPool.hpp
#pragma once

#include <vector>
#include <queue>
#include <mutex>
#include "mythread.hpp"
#include "mymutex.hpp"
#include "Task.hpp"

using std::cout;
using std::endl;

const int N = 5;

template <class T>
class ThreadPool;

template <class T>
struct ThreadData
{
    ThreadPool<T>* _tp;
    std::string _name;
    ThreadData(ThreadPool<T>* tp, const std::string& name)
        : _tp(tp)
        , _name(name)
    {}
};

template <class T>
class ThreadPool
{
private:
    static void* handlerTask(void* args)
    {
        ThreadData<T>* tdp = static_cast<ThreadData<T>*>(args);
        while(true)
        {
            tdp->_tp->lockqueue();
            while(tdp->_tp->isqueueempty())
            {
                tdp->_tp->threadwait();
            }
            T t = tdp->_tp->pop();
            tdp->_tp->unlockqueue();
            t();
        }
        delete tdp;
    }

    void lockqueue() volatile
    {
        pthread_mutex_lock(&_mutex);
    }

    void unlockqueue() volatile
    {
        pthread_mutex_unlock(&_mutex);
    }

    bool isqueueempty() volatile
    {
        return _tasks.empty();
    }

    void threadwait() volatile
    { 
        pthread_cond_wait(&_cond, &_mutex);
    }

    T pop() volatile
    {
        T res = _tasks.front();
        _tasks.pop();
        return res;
    }

    ThreadPool(int num = 5)
        : _num(num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
        // 创建线程
        for(int i = 0; i < _num; i++)
        {
            _threads.push_back(new Thread());
        }
    }

    ThreadPool(const ThreadPool<T>& ) = delete;
    ThreadPool<T> operator=(const ThreadPool<T>&) = delete;

public:

    void start() volatile
    {
        for(auto& t : _threads)
        {
            ThreadData<T>* td = new ThreadData<T>(this, t->GetName());
            t->start(handlerTask, td);
        }
    }

    void push(const T& in) volatile
    {
        LockAuto lock(&_mutex);
        _tasks.push(in);
        // 唤醒池中的一个线程
        pthread_cond_signal(&_cond);
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
        for(auto & e : _threads)
        {
            delete e;
        }
    }

    volatile static ThreadPool<T>* GetSingle()
    {
        if(_tp == nullptr)
        {
            _singlelock.lock();
            if(_tp == nullptr)
            {
                _tp = new ThreadPool<T>();
            }
            _singlelock.unlock();
        }
        return _tp;
    }

private:
    int _num;// 线程数量
    std::vector<Thread*> _threads;
    std::queue<T> _tasks;// 任务队列
    pthread_mutex_t _mutex;// 保护任务队列
    pthread_cond_t _cond;
    volatile static ThreadPool<T>* _tp;
    static std::mutex _singlelock;
};

template <class T>
volatile ThreadPool<T>* ThreadPool<T>::_tp = nullptr;

template <class T>
std::mutex ThreadPool<T>::_singlelock;

// TCPServer.hpp
#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <cstdlib>
#include <sys/wait.h>
#include <pthread.h>
#include "ThreadPool.hpp"
#include "log.hpp"

class TCPServer;
struct ThreadData
{
    TCPServer* _self;
    int _sock;
};

class TCPServer
{
static const uint16_t gport = 8080;
static const int gbacklog = 10;
public:
    TCPServer(const uint16_t& port = gport)
        : _listensock(-1)
        , _port(port)
    {}

    void InitServer()
    {
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if(_listensock == -1)
        {
            std::cerr << "create socket error" << std::endl;
            exit(1);
        }
        std::cout << "create socket success" << std::endl;
        struct sockaddr_in si;
        // 初始化结构体
        bzero(&si, sizeof si);
        si.sin_family = AF_INET;
        si.sin_port = htons(_port);// 主机转网络序列
        si.sin_addr.s_addr = INADDR_ANY;
        if(bind(_listensock, (struct sockaddr*)&si, sizeof si) < 0)
        {
            std::cout << "bind socket error" << std::endl;
            exit(1);
        }
        std::cout << "bind socket success" << std::endl;
        // 设置监听状态
        if(listen(_listensock, gbacklog) < 0)
        {
            std::cout << "listen socket error" << std::endl;
            exit(1);
        }
        std::cout << "listen socket success" << std::endl;
    }

    void start()
    {
        // 线程池初始化
        ThreadPool<Task>::GetSingle()->start();
        while(1)
        {
            // 获取新链接
            struct sockaddr_in si;
            socklen_t len = sizeof si;
            int sock = accept(_listensock, (struct sockaddr*)&si, &len);
            if(sock < 0)
            {
                // 获取链接失败无影响,继续获取即可
                std::cout << "accept error, continue" << std::endl;
                continue;
            }
            std::cout << "accept a new link success" << std::endl;
            std::cout << "sock: " << sock << std::endl;
            // 线程池
            ThreadPool<Task>::GetSingle()->push(Task(sock, ServerIO));


            // 多线程
            // pthread_t tid;
            // ThreadData* td = new ThreadData({this, sock});
            // pthread_create(&tid, nullptr, thread_start, td);
            
            // 多进程
            // pid_t id = fork();
            // if(id == 0)// child
            // {
            //     close(_listensock);
            //     if(fork() > 0) exit(1);
            //     ServerIO(sock);
            //     close(sock);
            //     exit(1);
            // }
            // close(sock);
            // father
            // pid_t ret = waitpid(id, nullptr, 0);
            // if(ret > 0)
            // {
            //     std::cout << "wait success " << ret << std::endl;
            // }


            // ServerIO(sock);
            // // 关闭使用完的文件描述符
            // close(sock);
        }
    }

    static void* thread_start(void* args)
    {
        // 线程分离
        pthread_detach(pthread_self());
        ThreadData* tp = static_cast<ThreadData*>(args);
        tp->_self->ServerIO(tp->_sock);
        close(tp->_sock);
        delete tp;
    }

private:
    int _listensock;
    uint16_t _port;
};
  • 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
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344

四、总结

对比UDP服务器,TCP服务器多了获取新链接和监听的操作,而因为TCP是面向字节流的,所以接收和发送数据都是IO操作,也就是文件操作。

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

闽ICP备14008679号