【计算机网络】网络编程套接字socket--UDP/TCP简单服务器实现/TCP协议通信流程

news2024/11/16 0:44:07

文章目录

  • 一、预备知识
    • 1.IP和端口号
    • 2.TCP协议和UDP协议
    • 3.网络字节序
  • 二、socket编程接口
    • 1.socket 常见API
    • 2.sockaddr结构
  • 三、UDP服务器
    • 相关重要接口介绍
      • sendto
      • recvfrom
      • popen
    • 1.udpServer.hpp
    • 2.udpServer.cc
    • 3.udpClient.hpp
    • 4.udpClient.cc
    • 5.onlineUser.hpp
  • 四、TCP服务器
    • socket API
    • TCP服务器简单实现
      • 1.tcpServer.hpp
      • 2.tcpServer.cc
      • 3.tcpClient.hpp
      • 4.tcpClient.cc
      • 5.Thread.hpp
      • 6.ThreadPool.hpp
      • 7.log.hpp
      • 8.LockGuard.hpp
      • 9.daemon.hpp
      • 10.Task.hpp
  • 五、TCP协议通讯流程

一、预备知识

1.IP和端口号

在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址

端口号(port)是传输层协议的内容.

端口号是一个2字节16位的整数;

端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;

IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;

一个端口号只能被一个进程占用

传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要

发给谁";

2.TCP协议和UDP协议

TCP(Transmission Control Protocol 传输控制协议):传输层协议 有连接 可靠传输 面向字节流

UDP(User Datagram Protocol 用户数据报协议):无连接 不可靠传输 面向数据报

3.网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏

移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;

接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;

因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.

TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.

不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;

如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可

在这里插入图片描述

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换

在这里插入图片描述

#include <arpa/inet.h>

// 主机转网络
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
//网络转主机
uint32_t ntosl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。

例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。

如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;

如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

二、socket编程接口

1.socket 常见API

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器) 
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

2.sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain

Socket. 然而, 各种网络协议的地址格式并不相同

在这里插入图片描述

IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址.

IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.

socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;

sockaddr 结构

在这里插入图片描述

sockaddr_in 结构

在这里插入图片描述

虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址.

in_addr结构

在这里插入图片描述

地址转换函数

IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换;

字符串转in_addr的函数:
在这里插入图片描述

int inet_aton(const char *cp, struct in_addr *inp);

in_addr转字符串的函数:

在这里插入图片描述

char *inet_ntoa(struct in_addr in);

其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr。

在这里插入图片描述

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);
struct in_addr inet_makeaddr(int net, int host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);

使用案例:

#include <cstdio>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
    struct sockaddr_in addr;
    // 字符串转in_addr的函数:
    inet_aton("127.0.0.1", &addr.sin_addr);
    uint32_t *ptr = (uint32_t *)(&addr.sin_addr);
    printf("addr:%x\n", *ptr);
    // in_addr转字符串的函数:
    printf("addr_str: %s\n", inet_ntoa(addr.sin_addr));
    return 0;
}

在这里插入图片描述

关于inet_ntoa

inet_ntoa这个函数返回了一个char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果. 那么是

否需要调用者手动释放呢?

在这里插入图片描述

man手册上说, inet_ntoa函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放.

那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码

#include <cstdio>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
    struct sockaddr_in addr1;
    struct sockaddr_in addr2;
    addr1.sin_addr.s_addr = 0;
    addr1.sin_addr.s_addr = 0xffffffff;
    char *ptr1 = inet_ntoa(addr1.sin_addr);
    char *ptr2 = inet_ntoa(addr2.sin_addr);
    printf("ptr1: %s, ptr2: %s\n", ptr1, ptr2);
    return 0;
}

运行结果如下:

在这里插入图片描述

因为inet_ntoa把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆盖掉上一次的结果.

思考: 如果有多个线程调用 inet_ntoa, 是否会出现异常情况呢?

在APUE中, 明确提出inet_ntoa不是线程安全的函数;

但是在centos7上测试, 并没有出现问题, 可能内部的实现加了互斥锁;

在多线程环境下, 推荐使用inet_ntop, 这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题;

多线程调用inet_ntoa代码示例如下:

#include <cstdio>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

void *Func1(void *p)
{
    struct sockaddr_in *addr = (struct sockaddr_in *)p;
    while (1)
    {
        sleep(1);
        char *ptr = inet_ntoa(addr->sin_addr);
        printf("addr1: %s\n", ptr);
    }
    return NULL;
}
void *Func2(void *p)
{
    struct sockaddr_in *addr = (struct sockaddr_in *)p;
    while (1)
    {
        sleep(1);
        char *ptr = inet_ntoa(addr->sin_addr);
        printf("addr2: %s\n", ptr);
    }
    return NULL;
}
int main()
{
    pthread_t tid1 = 0;
    struct sockaddr_in addr1;
    struct sockaddr_in addr2;
    addr1.sin_addr.s_addr = 0;
    addr2.sin_addr.s_addr = 0xffffffff;
    pthread_create(&tid1, NULL, Func1, &addr1);
    pthread_t tid2 = 0;
    pthread_create(&tid2, NULL, Func2, &addr2);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

在这里插入图片描述

这里暂时没有出现问题。

三、UDP服务器

以下使用UDP协议来实现处理三个不同的业务服务器:

1.英汉翻译服务器

2.执行shell指令的服务器

3.在线聊天的服务器

相关重要接口介绍

sendto

#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
// sockfd:文件描述符
// buf:发送缓冲区
// len:缓冲区的大小
// flags一般被置为0
// dest_addr:接收端的网络协议的地址
// addrlen:协议地址的长度

在这里插入图片描述

recvfrom

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                  struct sockaddr *src_addr, socklen_t *addrlen);
// sockfd:文件描述符
// buf:发送缓冲区
// len:缓冲区的大小
// flags一般被置为0
// dest_addr:发送端的网络协议的地址
// addrlen:协议地址的长度

在这里插入图片描述

popen

#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
// 函数功能:将command指令让shell执行,然后将结果返回到一个文件中
// 返回值:保存结果的文件的地址
// command参数指定要执行的shell命令
// type参数指定管道的类型,可以是"r"或"w",分别表示读或写。

在这里插入图片描述

1.udpServer.hpp

#pragma once

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

namespace server
{
    using namespace std;
    const static string defaultIp = "0.0.0.0";
    const static int gnum = 1024;

    typedef function<void(int, string, uint16_t, string)> func_t;

    enum
    {
        SOCK_ERR = 2,
        BIND_ERR,
        USAGE_ERR,
        OPEN_ERR
    };

    class udpServer
    {
    public:
        udpServer(const func_t &callback, const uint16_t &port, const string &ip = defaultIp)
            : _callback(callback), _port(port), _ip(ip), _sockfd(-1)
        {
        }

        void initeServer()
        {
            // 1.创建socket
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                cerr << "socket error:" << errno << ":" << strerror(errno) << endl;
                exit(SOCK_ERR);
            }
            cout << "socket sucess"
                 << ":" << _sockfd << endl;

            // 2. 绑定port,ip(TODO)
            // 未来服务器要明确的port,不能随意改变
            struct sockaddr_in local;     // 定义了一个变量,栈,用户
            bzero(&local, sizeof(local)); // 初始化
            local.sin_family = AF_INET;
            local.sin_port = htons(_port); // 你如果要给别人发消息,你的port和ip要不要发送给对方
            // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. string->uint32_t 2. htonl(); -> inet_addr
            local.sin_addr.s_addr = htonl(INADDR_ANY); // 任意地址bind,服务器的真实写法

            int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
            if (n == -1)
            {
                cerr << "bind error:" << errno << ":" << strerror(errno) << endl;
                exit(BIND_ERR);
            }
            cout << "bind sucess" << endl;

            // UDP Server 的预备工作完成
        }

        void start()
        {
            // 服务器的本质其实就是一个死循环
            char buffer[gnum];
            for (;;)
            {
                // 读取数据
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer); // 必填

                ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
                // 1. 数据是什么 2. 谁发的?
                if (s > 0)
                {
                    buffer[s] = 0;
                    string clientip = inet_ntoa(peer.sin_addr); // 1. 网络序列 2. int->点分十进制IP
                    uint16_t clientport = ntohs(peer.sin_port);
                    string message = buffer;

                    cout << clientip << "[" << clientport << "]" << message << endl;

                    // 我们只把数据读上来就完了吗?对数据做处理
                    _callback(_sockfd, clientip, clientport, message);
                }
            }
        }

        ~udpServer()
        {
        }

    private:
        uint16_t _port;
        string _ip; // 实际上,一款网络服务器,不建议指明一个IP
        int _sockfd;
        func_t _callback; // 回调
    };
}

2.udpServer.cc

#include "udpServer.hpp"
#include "onlineUser.hpp"

#include <iostream>
#include <memory>
#include <string>
#include <fstream>
#include <tuple>
#include <unordered_map>
#include <signal.h>

using namespace std;
using namespace server;

const string dictTxt = "./dict.txt";
unordered_map<string, string> dict;

static void Usage(const string &proc)
{
    cout << "\nUsage:\n\t" << proc << "local_port\n\n";
}

static bool cutString(const string &target, string *s1, string *s2, const string &sep)
{
    // apple:苹果
    auto pos = target.find(sep);
    if (pos == string::npos)
        return false;
    *s1 = target.substr(0, pos);
    *s2 = target.substr(pos + sep.size());

    return true;
}

static void initDict()
{
    ifstream in(dictTxt, ios::binary);
    if (!in.is_open())
    {
        cerr << "open file" << dictTxt << "error" << endl;
        exit(OPEN_ERR);
    }

    string line, key, value;
    while (getline(in, line))
    {
        if (cutString(line, &key, &value, ":"))
        {
            dict.insert(make_pair(key, value));
        }
    }

    in.close();
    cout << "load dict sucess" << endl;
}

void reload(int signo)
{
    (void)signo;
    initDict();
}

static void debugPrint()
{
    for (auto &dt : dict)
    {
        cout << dt.first << "#" << dt.second << endl;
    }
}

// demo1
void handlerMessage(int sockfd, string clientip, uint16_t clientport, string message)
{
    // 就可以对message进行特定的业务处理,而不关心message怎么来的 ---- server通信和业务逻辑解耦!
    string response_massage;
    auto iter = dict.find(message);
    if (iter == dict.end())
        response_massage = "unknown";
    else
        response_massage = iter->second;

    // 开始返回
    struct sockaddr_in client;
    bzero(&client, sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str());

    sendto(sockfd, response_massage.c_str(), sizeof(response_massage), 0, (struct sockaddr *)&client, sizeof(client));
}

// demo2
void execCommand(int sockfd, string clientip, uint16_t clientport, string cmd)
{
    if (cmd.find("rm") != string::npos || cmd.find("mv") != string::npos || cmd.find("rmdir") != string::npos)
    {
        cout << clientip << ":" << clientport << "正在执行一个非法操作" << cmd << endl;
        return;
    }

    string response_message;
    // 进程程序替换,执行完之后,将结果保存到cmd中
    FILE *fp = popen(cmd.c_str(), "r");
    if (fp == nullptr)
        response_message = cmd + "exec failed";
    char line[1024];
    while (fgets(line, sizeof(line), fp))
    {
        response_message += line;
    }

    fclose(fp);

    // 开始返回
    struct sockaddr_in client;
    bzero(&client, sizeof(client));

    client.sin_family = AF_INET;
    client.sin_addr.s_addr = inet_addr(clientip.c_str());
    client.sin_port = htons(clientport);

    sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr *)&client, sizeof(client));
}

onlineUser onlineuser;

// demo3
void routeMessage(int sockfd, string clientip, uint16_t clientport, string message)
{
    if (message == "online")
        onlineuser.addUser(clientip, clientport);
    if (message == "offline")
        onlineuser.delUser(clientip, clientport);
    if (onlineuser.isOnline(clientip, clientport))
    {
        onlineuser.broadcastMessage(sockfd, clientip, clientport, message);
    }
    else
    {
        struct sockaddr_in client;
        bzero(&client, sizeof(client));

        client.sin_family = AF_INET;
        client.sin_addr.s_addr = inet_addr(clientip.c_str());
        client.sin_port = htons(clientport);

        string response = "你还没有上线,请先上线,运行: online";

        sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&client, sizeof(client));
    }
}

// ./udpServer port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);

    // string ip = argv[1];
    // signal(2, reload);
    // initDict();
    // debugPrint();

    // std::unique_ptr<udpServer> usvr(new udpServer(handlerMessage, port));
    // std::unique_ptr<udpServer> usvr(new udpServer(execCommand, port));
    std::unique_ptr<udpServer> usvr(new udpServer(routeMessage, port));
    usvr->initeServer();
    usvr->start();
    return 0;
}

3.udpClient.hpp

#pragma once

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

namespace client
{
    using namespace std;

    class udpClient
    {
    public:
        udpClient(const string &serverip, const uint16_t serverport)
            : _serverip(serverip), _serverport(serverport), _sockfd(-1), _quit(false)
        {
        }

        void initeClient()
        {
            // 创建socket
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                cerr << "socket error:" << errno << ":" << strerror(errno) << endl;
                exit(2);
            }

            // 2. client要不要bind[必须要的],client要不要显示的bind,需不需程序员自己bind?不需要!!!
            // 写服务器的是一家公司,写client是无数家公司 -- 由OS自动形成端口进行bind!-- OS在什么时候,如何bind
        }

        // demo1
        // void run()
        // {
        //     struct sockaddr_in server;
        //     memset(&server, 0, sizeof(server));
        //     server.sin_family = AF_INET;
        //     server.sin_addr.s_addr = inet_addr(_serverip.c_str());
        //     server.sin_port = htons(_serverport);

        //     string massage;
        //     while (!_quit)
        //     {
        //         cout << "Please Enter#";
        //         cin >> massage;

        //         sendto(_sockfd, massage.c_str(), sizeof(massage), 0, (struct sockaddr *)&server, sizeof(server));

        //         char buffer[1024];
        //         struct sockaddr_in tmp;
        //         socklen_t tmp_len = sizeof(tmp);
        //         ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&tmp, &tmp_len);
        //         if (n > 0)
        //             buffer[n] = 0;
        //         cout << "翻译的结果为:" << buffer << endl;
        //     }
        // }

        // void run()
        // {
        //     struct sockaddr_in server;
        //     memset(&server, 0, sizeof(server));
        //     server.sin_family = AF_INET;
        //     server.sin_addr.s_addr = inet_addr(_serverip.c_str());
        //     server.sin_port = htons(_serverport);

        //     string message;
        //     char cmdline[1024];
        //     while (!_quit)
        //     {
        //         cerr << "[hdp@VM-12-6-centos 2023-10-8]$";
        //         fgets(cmdline, sizeof(cmdline), stdin);
        //         cmdline[strlen(cmdline) - 1] = 0;
        //         message = cmdline;

        //         sendto(_sockfd, message.c_str(), sizeof(message), 0, (struct sockaddr *)&server, sizeof(server));

        //         char buffer[1024];
        //         struct sockaddr_in tmp;
        //         socklen_t tmp_len = sizeof(tmp);
        //         ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&tmp, &tmp_len);
        //         if (n > 0)
        //             buffer[n] = 0;
        //         cout << buffer << endl;
        //     }
        // }

        static void *readMessage(void *args)
        {
            int sockfd = *(static_cast<int *>(args));
            pthread_detach(pthread_self());
            while (true)
            {
                char buffer[1024];
                struct sockaddr_in tmp;
                socklen_t tmp_len = sizeof(tmp);
                ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&tmp, &tmp_len);
                if (n > 0)
                    buffer[n] = 0;
                cout << buffer << endl;
            }
            
            return nullptr;
        }

        void run()
        {
            pthread_create(&_reader, nullptr, readMessage, (void *)&_sockfd);

            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_serverip.c_str());
            server.sin_port = htons(_serverport);

            string message;
            char cmdline[1024];
            while (!_quit)
            {
                fprintf(stderr, "Enter# ");
                fflush(stderr);
                fgets(cmdline, sizeof(cmdline), stdin);
                cmdline[strlen(cmdline) - 1] = 0;
                message = cmdline;

                sendto(_sockfd, message.c_str(), sizeof(message), 0, (struct sockaddr *)&server, sizeof(server));
            }
        }
        ~udpClient()
        {
        }

    private:
        int _sockfd;
        string _serverip;
        uint16_t _serverport;
        bool _quit;
        pthread_t _reader;
    };
}

4.udpClient.cc

#include "udpClient.hpp"

#include <memory>

using namespace std;
using namespace client;

void Usage(const string& proc)
{
    cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
}

//  ./udpClient serverip serverport
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(2);
    }

    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    unique_ptr<udpClient> ucli (new udpClient(serverip,serverport));

    ucli->initeClient();
    ucli->run();

    return 0;
}

5.onlineUser.hpp

#pragma once

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

using namespace std;

class User
{
public:
    User(const string &ip, const uint16_t &port)
        : _ip(ip), _port(port)
    {
    }

    string ip() { return _ip; }
    uint16_t port() { return _port; }
    ~User()
    {
    }

private:
    string _ip;
    uint16_t _port;
};

class onlineUser
{
public:
    onlineUser()
    {
    }
    ~onlineUser()
    {
    }

    bool addUser(const string &ip, const uint16_t &port)
    {
        string id = ip + "--" + to_string(port);
        users.insert(make_pair(id, User(ip, port)));
    }

    bool delUser(const string &ip, const uint16_t &port)
    {
        string id = ip + "--" + to_string(port);
        users.erase(id);
    }

    bool isOnline(const string &ip, const uint16_t &port)
    {
        string id = ip + "--" + to_string(port);
        return users.find(id) != users.end();
    }

    void broadcastMessage(int sockfd, const string &ip, const uint16_t &port, const string &message)
    {
        for (auto &user : users)
        {
            struct sockaddr_in client;
            bzero(&client, sizeof(client));

            client.sin_family = AF_INET;
            client.sin_addr.s_addr = inet_addr(ip.c_str());
            client.sin_port = htons(port);

            string s = ip + "--" + to_string(port) + "#" + message;
            sendto(sockfd, s.c_str(), s.size(), 0, (struct sockaddr *)&client, sizeof(client));
        }
    }

private:
    unordered_map<string, User> users;
};

四、TCP服务器

socket API

1.socket

#include <sys/types.h>
#include <sys/socket.h>
//创建通信的一端,返回一个文件描述符
int socket(int domain, int type, int protocol);

在这里插入图片描述

参数:

domain:

在这里插入图片描述

type:

在这里插入图片描述

protocol:

在这里插入图片描述

socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;

应用程序可以像读写文件一样用read/write在网络上收发数据;

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

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

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

protocol参数的介绍从略,指定为0即可

2.bind

在这里插入图片描述

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

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

bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号;

前面讲过,struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度

我们的程序中对myaddr参数是这样初始化的

在这里插入图片描述

struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;

1.将整个结构体清零;

2.设置地址类型为AF_INET;

3.网络地址为INADDR_ANY, 这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址;

4.端口号为SERV_PORT, 我们定义为9999

3.listen

在这里插入图片描述

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

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

4.accept

在这里插入图片描述

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

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

addr是一个传出参数,accept()返回时传出客户端的地址和端口号;

如果给addr 参数传NULL,表示不关心客户端的地址;

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

我们的服务器程序结构是这样的:

for(;;)
{
    struct sockaddr_in peer;
	socklen_t len = sizeof(peer);
	int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
}

5.connect

在这里插入图片描述

客户端需要调用connect()连接服务器;

connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;

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

setsockopt

在这里插入图片描述

函数是用于设置套接字选项的函数。它允许我们在创建套接字后对其进行配置和调整,以满足特定的需求该函数的原型如下:

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
// sockfd:表示待设置选项的套接字描述符。
// level:表示选项所属的协议层或者协议族,通常使用 SOL_SOCKET 表示操作套接字级别的选项。
// optname:表示要设置的选项名称。
// optval:是一个指向包含新选项值的缓冲区的指针。
// optlen:表示 optval 缓冲区的长度。
  • SO_REUSEADDR:允许多个套接字绑定到同一个地址(在服务器程序中常用)。
  • SO_KEEPALIVE:定期发送心跳信号以检测连接是否仍然活跃。
  • SO_RCVBUFSO_SNDBUF:分别设置接收和发送缓冲区的大小。
  • TCP_NODELAY:禁用 Nagle 算法,即禁用数据包的延迟发送。
  • IP_TTL:设置 IP 数据包的生存时间(TTL)。

使用 setsockopt 函数时,需要注意以下几点:

  1. 在调用 socket 函数创建套接字后,必须在 bind 或者 connect 函数之前使用 setsockopt 设置选项。
  2. level 参数通常为 SOL_SOCKET,用于设置套接字级别的选项。其他可能的值包括 IPPROTO_TCPIPPROTO_IP 等。
  3. optval 缓冲区的类型和大小取决于选项的要求。例如,如果选项需要一个整数值,则 optval 应该是一个指向整数的指针,并且 optlen 应该是 sizeof(int)
  4. 函数调用成功时,返回值为0;失败时,返回-1,并设置 errno 变量以指示错误类型

TCP服务器简单实现

以下实现的TCP服务器的功能是对客户端发送过来的数据进行回显

我们这里对网络套接字部分,锁,线程,线程池,守护进程的实现以及日志函数进行了封装实现。

但是在服务端会有如下版本:

1.单进程版本

2.多进程版本

3.多线程版本

4.线程池版本

5.守护进程

1.tcpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>

#include "log.hpp"
#include "Task.hpp"
#include "Thread.hpp"
#include "ThreadPool.hpp"

using namespace ThreadNs;
static const uint16_t gport = 8080;

class tcpServer
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };

    const static int gbacklog = 5;

    class ThreadData
    {
    public:
        ThreadData(tcpServer *self, int sock) : _self(self), _sock(sock)
        {
        }

    public:
        tcpServer *_self;
        int _sock;
    };

public:
    tcpServer(const uint16_t &port = gport)
        : _port(port), _listensock(-1)
    {
    }

    void initServer()
    {
        // 1. 创建socket文件套接字对象
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            logMessage(FATAL, "create socket error");
            exit(SOCKET_ERR);
        }
        logMessage(NORMAL, "create socket success:%d", _listensock);

        // 2. bind绑定自己的网络信息
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        int n = bind(_listensock, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            logMessage(FATAL, "bind socket error");
            exit(BIND_ERR);
        }
        logMessage(NORMAL, "bind socket success");

        // 3. 设置socket 为监听状态
        n = listen(_listensock, gbacklog);
        if (n < 0)
        {
            logMessage(FATAL, "listen socket error");
            exit(LISTEN_ERR);
        }
        logMessage(NORMAL, "listen socket success");
    }

    // version 1
    // void start()
    // {
    //     for (;;)
    //     {
    //         // 4. server 获取新链接
    //         struct sockaddr_in peer;
    //         socklen_t len = sizeof(peer);
    //         int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
    //         if (sock < 0)
    //         {
    //             logMessage(ERROR, "accetpt error,next");
    //             continue;
    //         }
    //         logMessage(NORMAL, "accept a new link success, get new sock: %d", sock);

    //         // version 1
    //         serviceIO(sock);
    //         close(sock);
    //     }
    // }

    // 2 多进程版(2)
    // void start()
    // {
    //     signal(SIGCHLD, SIG_IGN);
    //     for (;;)
    //     {
    //         // 4. server 获取新链接
    //         struct sockaddr_in peer;
    //         socklen_t len = sizeof(peer);
    //         int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
    //         if (sock < 0)
    //         {
    //             logMessage(ERROR, "accetpt error,next");
    //             continue;
    //         }
    //         logMessage(NORMAL, "accept a new link success, get new sock: %d", sock);

    //         // version 2 多进程版(2)
    //         pid_t id = fork();
    //         if (id == 0)
    //         {
    //             close(_listensock);
    //             // if (fork() > 0)
    //             //     exit(0);
    //             serviceIO(sock);
    //             close(sock);
    //             exit(0);
    //         }

    //         pid_t ret = waitpid(id, nullptr, 0);
    //         if (ret > 0)
    //         {
    //             std::cout << "wait sucess " << ret << std::endl;
    //         }
    //     }
    // }

    void start()
    {
        // ThreadPool<Task>::getInstance()->run();
        // logMessage(NORMAL, "Thread init success");
        for (;;)
        {
            // 4. server 获取新链接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
            if (sock < 0)
            {
                logMessage(ERROR, "accetpt error,next");
                continue;
            }
            logMessage(NORMAL, "accept a new link success, get new sock: %d", sock);

            pthread_t tid;
            ThreadData *td = new ThreadData(this, sock);

            pthread_create(&tid, nullptr, threadRoutine, td);
            pthread_join(tid, nullptr);

            // version4 线程池
            // ThreadPool<Task>::getInstance()->push(Task(sock, serviceIO));
        }
    }

    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->_self->serviceIO(td->_sock);
        close(td->_sock);
        delete td;

        return nullptr;
    }

    void serviceIO(int sock)
    {
        char buffer[1024];
        while (true)
        {
            ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                // 目前我们把读到的数据当成字符串
                buffer[n] = 0;
                std::cout << "recv message: " << buffer << std::endl;

                std::string outbuffer = buffer;
                outbuffer += "[server echo]";
                write(sock, outbuffer.c_str(), outbuffer.size());
            }
            else if (n == 0)
            {
                // 代表client退出
                logMessage(NORMAL, "client quit, me too!");
                break;
            }
        }
        close(sock);
    }
    ~tcpServer()
    {
    }

private:
    int _listensock;
    uint16_t _port;
};

2.tcpServer.cc

#include "tcpServer.hpp"
#include "daemon.hpp"

#include <memory>

void Usage(char *proc)
{
    std::cout << "\nUsage\n\t" << proc << "loacl_port\n\n";
}

// ./tcpserver 8080
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }

    uint16_t port = atoi(argv[1]);
    std::unique_ptr<tcpServer> tsvr(new tcpServer(port));
    tsvr->initServer();

    deamonSelf();
    tsvr->start();

    return 0;
}

3.tcpClient.hpp

#pragma once

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

class tcpClient
{
public:
    tcpClient(const std::string &serverip, const uint16_t &serverport)
        : _sock(-1), _serverip(serverip), _serverport(serverport)
    {
    }

    void initClient()
    {
        // 1. 创建socket
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            logMessage(FATAL, "create socket error");
            exit(2);
        }
    }
    void start()
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof server);

        server.sin_family = AF_INET;
        server.sin_port = htons(_serverport);
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());

        int n = connect(_sock, (struct sockaddr *)&server, sizeof server);
        if (n < 0)
        {
            logMessage(ERROR, "connect error");
        }
        else
        {
            std::string message;
            while (true)
            {
                std::cout << "Please Enter#";
                getline(std::cin, message);
                write(_sock, message.c_str(), message.size());

                char buffer[1024];
                int n = read(_sock, buffer, sizeof(buffer) - 1);
                if (n > 0)
                {
                    buffer[n] = 0;
                    std::cout << "server 回显# " << buffer << std::endl;
                }
                else
                    break;
            }
        }
    }
    ~tcpClient()
    {
        if (_sock > 0)
            close(_sock);
    }

private:
    int _sock;
    std::string _serverip;
    uint16_t _serverport;
};

4.tcpClient.cc

#include "tcpClient.hpp"

#include <memory>

void Usage(char *proc)
{
    std::cout << "\nUsage\n\t" << proc << "loacl_port\n\n";
}

// ./tcpClient serverip serverport
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    std::unique_ptr<tcpClient> tcli(new tcpClient(serverip,serverport));

    tcli->initClient();
    tcli->start();

    return 0;
}

5.Thread.hpp

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <cstring>
#include <cassert>
#include <pthread.h>

namespace ThreadNs
{
    typedef std::function<void *(void *)> func_t;
    const int num = 1024;
    class Thread
    {
    private:
        static void *start_routine(void *args)
        {
            Thread *td = static_cast<Thread *>(args);
            return td->callback();
        }

    public:
        Thread()
        {
            char buffer[num];
            snprintf(buffer, sizeof buffer, "thread-%d", threadnum++);
            _name = buffer;
        }

        void start(func_t func, void *args)
        {
            _func = func;
            _args = args;
            int n = pthread_create(&_tid, nullptr, start_routine, this);
        }

        void join()
        {
            int n = pthread_join(_tid, nullptr);
            assert(n == 0);
            (void)n;
        }

        std::string threadname()
        {
            return _name;
        }
        void *callback()
        {
            return _func(_args);
        }
        ~Thread()
        {
        }

    private:
        std::string _name;
        void *_args;
        func_t _func;
        pthread_t _tid;

        static int threadnum;
    };

    int Thread::threadnum = 1;
}

6.ThreadPool.hpp

#pragma once

#include "Thread.hpp"
#include "LockGuard.hpp"

using namespace ThreadNs;

#include <vector>
#include <queue>
#include <iostream>

const int gnum = 3;

template <class T>
class ThreadPool;

template <class T>
class ThreadData
{
public:
    ThreadData(ThreadPool<T> *tp, const std::string &threadname)
        : _threadpool(tp), _threadname(threadname)
    {
    }

    ~ThreadData()
    {
    }

public:
    ThreadPool<T> *_threadpool;
    std::string _threadname;
};

template <class T>
class ThreadPool
{
private:
    static void* handleTask(void* args)
    {
        ThreadData<T>* td = static_cast<ThreadData<T>*>(args);
        while(true)
        {
            T t;
            {
                LockGuard lockguard(td->_threadpool->mutex());
                while(td->_threadpool->isQueueEmpty())
                {
                    td->_threadpool->threadWait();
                }
                t = td->_threadpool->pop();
            }
            std::cout << td->_threadname << " 获取了一个任务: " << t.toTaskString() << " 并处理完成,结果是:" << t() << std::endl;
        }

        delete td;
        return nullptr;
    }
public:
    bool isQueueEmpty() {return _task_queue.empty(); }
    void threadWait() { pthread_cond_wait(&_cond,&_mutex); }
    void lockQueue() {pthread_mutex_lock(&_mutex); }
    void unlockQueue() {pthread_mutex_unlock(&_mutex); }

    T pop()
    {
        T t = _task_queue.front();
        _task_queue.pop();
        return t;
    }
    pthread_mutex_t* mutex() { return &_mutex; }
public:
    ThreadPool(const int &num = gnum)
        : _num(num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
        for (int i = 0; i < _num; i++)
        {
            _threads.push_back(new Thread());
        }
    }

public:
    void run()
    {
        for (const auto &iter : _threads)
        {
            ThreadData<T> *td = new ThreadData<T>(this, iter->threadname());
            iter->start(handleTask, td);
            std::cout << iter->threadname() << " start..." << std::endl;
        }
    }

    void push(T& in)
    {
        LockGuard lockguard(&_mutex);
        _task_queue.push(in);
        pthread_cond_signal(&_cond);
    }

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

private:
    int _num;
    std::vector<Thread *> _threads;
    std::queue<T> _task_queue;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
};

7.log.hpp

#pragma once

#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

#define LOG_NORMAL "log.txt"
#define LOG_ERR "log.error"

#define NUM 1024

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <stdarg.h>

const char* to_levelstr(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
    case NORMAL:
        return "NORMAL";
    case WARNING:
        return "WARNING";
    case ERROR:
        return "ERROR";
    case FATAL:
        return "FATAL";
    default:
        return nullptr;
    }
}

void logMessage(int level, const char *format, ...)
{
    // [日志等级] [时间戳/时间] [pid] [messge]
    char logprofix[NUM];
    snprintf(logprofix, sizeof logprofix, "[%s][%ld][pid:%d]", to_levelstr(level), (long int)time(nullptr), getpid());

    char logcontent[NUM];
    va_list arg;
    va_start(arg, format);

    vsnprintf(logcontent, sizeof logcontent, format, arg);

    std::cout << logprofix << logcontent << std::endl;

    FILE *log = fopen(LOG_NORMAL, "a");
    FILE *error = fopen(LOG_ERR, "a");

    if (log && error)
    {
        FILE *cur = nullptr;
        if (level == DEBUG || level == NORMAL || level == WARNING)
            cur = log;
        if (level == ERROR || level == FATAL)
            cur = error;
        if (cur)
            fprintf(cur, "%s%s\n", logprofix, logcontent);
        fclose(log);
        fclose(error);
    }
}

8.LockGuard.hpp

#pragma once

#include <cassert>
#include <pthread.h>

class Mutex
{
public:
    Mutex(pthread_mutex_t *lock_p = nullptr)
        : _lock_p(lock_p)
    {
    }

    void lock()
    {
        if (_lock_p)
        {
            int n = pthread_mutex_lock(_lock_p);
            assert(n == 0);
            (void)n;
        }
    }

    void unlock()
    {
        if (_lock_p)
        {
            int n = pthread_mutex_unlock(_lock_p);
            assert(n == 0);
            (void)n;
        }
    }

    ~Mutex()
    {
    }

private:
    pthread_mutex_t *_lock_p;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex) : _mutex(mutex)
    {
        _mutex.lock();
    }

    ~LockGuard()
    {
        _mutex.unlock();
    }

private:
    Mutex _mutex;
};

9.daemon.hpp

#pragma once

#include <unistd.h>
#include <signal.h>
#include <cstdlib>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEV "/dev/null"

void deamonSelf(const char *curpath = nullptr)
{
    // 1. 让调用进程忽略掉异常的信号
    signal(SIGPIPE, SIG_IGN);

    // 2. 如何让自己不是组长,setsid
    if (fork() > 0)
        exit(0);
    // 子进程 -- 守护进程,精灵进程,本质就是孤儿进程的一种!
    pid_t n = setsid();
    assert(n != -1);

    // 3. 守护进程是脱离终端的,关闭或者重定向以前进程默认打开的文件

    int fd = open(DEV, O_WRONLY);
    if (fd >= 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
    }
    else
    {
        close(0);
        close(1);
        close(2);
    }

    // 4. 可选:进程执行路径发生更改

    if (curpath)
        chdir(curpath);
}

10.Task.hpp

#pragma once

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

#include "log.hpp"

void serviceIO(int sock)
{
    char buffer[1024];
    while (true)
    {
        ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            // 目前我们把读到的数据当成字符串
            buffer[n] = 0;
            std::cout << "recv message: " << buffer << std::endl;

            std::string outbuffer = buffer;
            outbuffer += "[server echo]";
            write(sock, outbuffer.c_str(), outbuffer.size());
        }
        else if (n == 0)
        {
            // 代表client退出
            logMessage(NORMAL, "client quit, me too!");
            break;
        }
    }
    close(sock);
}

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

public:
    Task()
    {
    }
    Task(const int sock, func_t &func)
        : _sock(sock), _callback(func)
    {
    }

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

private:
    int _sock;
    func_t _callback;
};

注意事项:

由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配

客户端不是不允许调用bind(), 只是没有必要调用bind()固定一个端口号. 否则如果在同一台机器上启动多个客户端, 就会出现端口号被占用导致不能正确建立连接;

服务器也不是必须调用bind(), 但如果服务器不调用bind(), 内核会自动给服务器分配监听端口, 每次启动服务器时端口号都不一样, 客户端要连接服务器就会遇到麻烦

五、TCP协议通讯流程

下图是基于TCP协议的客户端/服务器程序的一般流程:

在这里插入图片描述

服务器初始化:

调用socket, 创建文件描述符;

调用bind, 将当前的文件描述符和ip/port绑定在一起; 如果这个端口已经被其他进程占用了, 就会bind失败;

调用listen, 声明当前这个文件描述符作为一个服务器的文件描述符, 为后面的accept做好准备;

调用accecpt, 并阻塞, 等待客户端连接过来

建立连接的过程:

调用socket, 创建文件描述符;

调用connect, 向服务器发起连接请求;

connect会发出SYN段并阻塞等待服务器应答; (第一次)

服务器收到客户端的SYN, 会应答一个SYN-ACK段表示"同意建立连接"; (第二次)

客户端收到SYN-ACK后会从connect()返回, 同时应答一个ACK段; (第三次)

这个建立连接的过程, 通常称为 三次握手;

数据传输的过程

建立连接后,TCP协议提供全双工的通信服务; 所谓全双工的意思是, 在同一条连接中, 同一时刻, 通信双方可以同时写数据; 相对的概念叫做半双工, 同一条连接在同一时刻, 只能由一方来写数据;

服务器从accept()返回后立刻调 用read(), 读socket就像读管道一样, 如果没有数据到达就阻塞等待;

这时客户端调用write()发送请求给服务器, 服务器收到后从read()返回,对客户端的请求进行处理, 在此期间客户端调用read()阻塞等待服务器的应答;

服务器调用write()将处理结果发回给客户端, 再次调用read()阻塞等待下一条请求;

客户端收到后从read()返回, 发送下一条请求,如此循环下去;

断开连接的过程:

如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段(第一次);

此时服务器收到FIN后, 会回应一个ACK, 同时read会返回0 (第二次);

read返回之后, 服务器就知道客户端关闭了连接, 也调用close关闭连接, 这个时候服务器会向客户端发送一个FIN; (第三次)

客户端收到FIN, 再返回一个ACK给服务器; (第四次)

这个断开连接的过程, 通常称为 四次挥手

在学习socket API时要注意应用程序和TCP协议层是如何交互的:

应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段

应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1364999.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高性能、可扩展、分布式对象存储系统MinIO的介绍、部署步骤以及代码示例

详细介绍 MinIO 是一款流行的开源对象存储系统&#xff0c;设计上兼容 Amazon S3 API&#xff0c;主要用于私有云和边缘计算场景。它提供了高性能、高可用性以及易于管理的对象存储服务。以下是 MinIO 的详细介绍及优缺点&#xff1a; 架构与特性&#xff1a; 开源与跨平台&am…

HTML---JavaScript操作DOM对象

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 本章目标 了解DOM的分类和节点间的关系熟练使用JavaScript操作DOM节点 访问DOM节点 能够熟练的进行节点的创建、添加、删除、替换等 能够熟练的设置元素的样式 能够灵活运用JavaScript获取元素…

SpringBoot学习(八)-SpringBoot + Dubbo + zookeeper

分布式DubboZookeeper 1、分布式理论 1&#xff09;什么是分布式系统&#xff1f; 在《分布式系统原理与范型》一书中有如下定义&#xff1a;“分布式系统是若干独立计算机的集合&#xff0c;这些计算机对于用户来说就像单个相关系统”&#xff1b; 分布式系统是由一组通过…

【AI视野·今日CV 计算机视觉论文速览 第284期】Fri, 5 Jan 2024

AI视野今日CS.CV 计算机视觉论文速览 Fri, 5 Jan 2024 Totally 62 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Learning to Prompt with Text Only Supervision for Vision-Language Models Authors Muhammad Uzair Khattak, Muhammad F…

2024.1.7-实战-docker方式给自己网站部署prometheus监控ecs资源使用情况-2024.1.7(测试成功)

实战-docker方式给自己网站部署prometheus监控ecs资源使用情况-2024.1.7(测试成功) 目录 最终效果 原文链接 https://onedayxyy.cn/docs/prometheus-grafana-ecs 参考模板 https://i4t.com/ https://grafana.frps.cn &#x1f530; 额&#xff0c;注意哦: 他这个是通过frp来…

【Flutter 开发实战】Dart 基础篇:从了解背景开始

想要学会用 Flutter 开发 App&#xff0c;就不可避免的要学习另一门很有意思的编程语言 —— Dart。很多小伙伴可能在学习 Flutter 之前可能都没听说过这门编程语言&#xff0c;我也是一样&#xff0c;还以为 Dart 是为了 Flutter 而诞生的&#xff1b;然而&#xff0c;当我们去…

网页设计与制作web前端设计html+css+js成品。电脑网站制作代开发。vscodeDrea 【企业公司宣传网站(HTML静态网页项目实战)附源码】

网页设计与制作web前端设计htmlcssjs成品。电脑网站制作代开发。vscodeDrea 【企业公司宣传网站&#xff08;HTML静态网页项目实战&#xff09;附源码】 https://www.bilibili.com/video/BV1Hp4y1o7RY/?share_sourcecopy_web&vd_sourced43766e8ddfffd1f1a1165a3e72d7605

分布式系统架构设计之分布式消息队列基础知识

随着微服务、大数据和云计算的普及&#xff0c;分布式系统已经成为现代软件架构的核心。在分布式系统中&#xff0c;各个组件间的通信和数据交换尤其重要&#xff0c;而消息队列正是实现这一目标的关键技术之一。 在分布式架构设计过程中&#xff0c;架构师们需要对消息队列有…

爬虫瑞数4案例:网上房地产

声明&#xff1a; 该文章为学习使用&#xff0c;严禁用于商业用途和非法用途&#xff0c;违者后果自负&#xff0c;由此产生的一切后果均与作者无关 一、瑞数简介 瑞数动态安全 Botgate&#xff08;机器人防火墙&#xff09;以“动态安全”技术为核心&#xff0c;通过动态封装…

深度解析基于模糊数学的C均值聚类算法

深度解析基于模糊数学的C均值聚类算法 模糊C均值聚类 (FCM)聚类步骤&#xff1a;FCM Python代码&#xff1a; 模糊C均值聚类 (FCM) 在数据挖掘和聚类分析领域&#xff0c;C均值聚类是一种广泛应用的方法。模糊C均值聚类&#xff08;FCM&#xff09;是C均值聚类的自然升级版。相…

创建Vue3项目

介绍 使用命令创建vue3项目 示例 第一步&#xff1a;执行创建项目命令 npm create vuelatest第二步&#xff1a;填写输入项 第三步&#xff1a;进入study-front-vue3文件夹 cd study-front-vue3第四步&#xff1a;执行npm命令安装依赖 npm install第五步&#xff1a;运行…

Vue中Vuex的环境搭建和原理分析及使用

Vuex的环境搭建 Vuex是Vue实现集中式数据管理的Vue的一个插件&#xff0c;集中式可以理解为一个老师给多个学生讲课。 Vue2.0版本的安装&#xff1a; npm i vuex3 使用Vuex需要在store中的index.js引入Vuex和main.js中引入store,目的是让vm和vc都能看到$store。实现多个组件…

2024--Django平台开发-Django知识点(三)

day03 django知识点 项目相关路由相关 urls.py视图相关 views.py模版相关 templates资源相关 static/media 1.项目相关 新项目 开发时&#xff0c;可能遇到使用其他的版本。虚拟环境 老项目 打开项目虚拟环境 1.1 关于新项目 1.系统解释器命令行【学习】 C:/python38- p…

大模型LLM训练的数据集

引言 2021年以来&#xff0c;大预言模型的开发和生产使用呈现出爆炸式增长。除了李开复、王慧文、王小川等“退休”再创业的互联网老兵&#xff0c;在阿里巴巴、腾讯、快手等互联网大厂的中高层也大胆辞职&#xff0c;加入这波创业浪潮。 通用大模型初创企业MiniMax完成了新一…

目标检测-One Stage-YOLOv4

文章目录 前言一、目标检测网络组成二、BoF&#xff08;Bag of Freebies&#xff09;1. 数据增强2.语义分布偏差问题3.损失函数IoUGIoUDIoUCIoU 三、BoS(Bag of Specials)增强感受野注意力机制特征融合激活函数后处理 四、YOLO v4的网络结构和创新点1.缓解过拟合&#xff08;Bo…

基础数据结构

1. 单链表 #include<iostream>using namespace std; const int N 1e5 10;int n; // 分别存储当前节点的值&#xff0c;当前节点下一个节点的值&#xff0c;头结点&#xff0c;id号 int value[N], nepoint[N], head, idx;void init(){head -1;idx 0;}// 1.H 将某个x插…

Docker学习与应用(三)-Docker镜像理解

1、Docker镜像讲解 1&#xff09;镜像是什么 镜像是一种轻量级、可执行的独立软件包&#xff0c;用来打包软件运行环境和基于运行环境开发的软件&#xff0c;他包含运行某个软件所需的所有内容&#xff0c;包括代码、运行时库、环境变量和配置文件。 所有的应用&#xff0c;…

在 PyCharm 中高效使用 GitHub Copilot

对于每一个developer来说&#xff0c;工具和插件对于提高开发效率至关重要。GitHub Copilot&#xff0c;作为一款先进的人工智能编程助手&#xff0c;能够在编写代码时提供实时建议和自动补全功能。结合 PyCharm 这一强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c…

web期末个人引导页透明版

效果图 代码 css代码 * {box-sizing: border-box; }body {color: #2b2c48;font-family: "Jost", sans-serif;background-image: url(../img/bg.jpg);background-repeat: no-repeat;background-size: cover;background-position: center;background-attachment: fix…

Windows系统任务栏应用图标显示成空白的解决方案

背景 任务栏应用图标为空白&#xff1a; 原因 Windows系统为了加快系统响应速度&#xff0c;在安装完应用第一次显示完应用图标后&#xff0c;会将应用的图标放入缓存中&#xff0c;以后每次显示应用直接在缓存中获取&#xff0c;如果缓存中的图标信息发生错误&#xff0c;…