计算机网络 —— 网络编程(TCP)

计算机网络 —— 网络编程(TCP)

  • TCP和UDP的区别
      • TCP (Transmission Control Protocol)
      • UDP (User Datagram Protocol)
  • 前期准备
  • listen (服务端)
      • 函数原型
      • 返回值
      • 使用示例
      • 注意事项
  • accpect (服务端)
      • 函数原型
      • 返回值
      • 注意事项
  • connect (客户端)
      • 函数原型
      • 返回值
      • 注意事项
  • send 和 recv
      • `send()` 函数
        • 函数原型
        • 返回值
      • `recv()` 函数
        • 函数原型
        • 返回值
  • accpect为啥要返回一个新的文件描述符?

我们之前了解过了UDP的网络编程接口,今天我们要来了解一下TCP网络接口。

TCP和UDP的区别

TCP(传输控制协议)和UDP(用户数据报协议)是两种常用的传输层协议,它们用于在网络中传输数据。尽管它们都是基于IP(互联网协议)之上的传输层协议,但两者在设计目标、功能特性以及应用场景上有着显著的区别。

TCP (Transmission Control Protocol)

  1. 连接导向:TCP 是面向连接的协议,在数据传输前需要建立连接(三次握手),确保通信双方都准备好接收数据。
  2. 可靠性:TCP 提供可靠的数据传输服务,通过确认机制(ACK)、重传机制和流量控制来保证数据包按序无误地到达接收端。
  3. 有序交付:TCP 会按照发送顺序将数据包传递给应用层,即使某些数据包后到也会被正确排序。
  4. 流控与拥塞控制:TCP 实现了复杂的流量控制和拥塞控制算法,如慢启动、拥塞避免等,以优化网络资源利用并防止网络拥塞。
  5. 高开销:由于提供了多种保障机制,TCP 的头部较大,处理过程也更复杂,因此相对UDP来说具有更高的CPU和带宽开销。
  6. 适用于场景:适合对数据完整性要求高的应用,例如文件传输(FTP)、电子邮件(SMTP)、网页浏览(HTTP/HTTPS)等。

UDP (User Datagram Protocol)

  1. 无连接:UDP 是无连接的协议,不需要在发送数据之前建立连接,可以直接发送数据报文。
  2. 不可靠性:UDP 不提供可靠性保证,它不会重传丢失的数据包,也不保证数据包的顺序。
  3. 无序交付:UDP 按照接收到的顺序将数据交给应用层,可能会出现乱序现象。
  4. 低开销:相比TCP,UDP 头部较小,没有复杂的握手过程和确认机制,所以它的处理速度更快,开销更低。
  5. 适用于场景:适合对实时性要求较高或对少量数据丢失不敏感的应用,如视频流媒体、在线游戏、DNS查询等。

我们要关注的是TCP在发送数据之前要三次握手建立连接,所以TCP和UDP的网络接口主要差别就是在这个建立连接上,大家如果想详细了解一下TCP和UDP之间的区别可以看看这几篇文章:

https://blog.csdn.net/qq_67693066/article/details/139620649

https://blog.csdn.net/qq_67693066/article/details/139623241

https://blog.csdn.net/qq_67693066/article/details/139626168

前期准备

TCP前期的准备跟UDP是差不多的,所以我们这边按照套路写一下就行了:

#pragma once
#include<iostream>
#include<cstring>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

const static uint16_t defaultport = 8888;

class TcpServer
{
    public:
    TcpServer(const uint16_t port = defaultport)
        :_port(port)
    {

    }

    void Init()
    {
        //创建套接字
        _listen_socketfd = socket(AF_INET,SOCK_STREAM,0);

        if(_listen_socketfd < 0)
        {
            std::cout << "Create listensocket fail!" << std::endl;
        }

        //初始化本地服务器信息
        struct sockaddr_in local;
        local.sin_family = AF_INET; //IPV4
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        //绑定
        if(bind(_listen_socketfd,(struct sockaddr*)&local,sizeof(local)))
        {
            std::cout << "Bind fail!" << std::endl;
            exit(1);
        }

        std::cout << "Bind successfully and the listensocketfd is " << 
        _listen_socketfd << std::endl;
    }

    void Start()
    {
        while(true)
        {
            
        }
    }

    ~TcpServer()
    {

    }


    private:
    //端口号
    uint16_t _port; 

    //监听套接字
    int _listen_socketfd = -1;
};
#include<iostream>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<cerrno>
#include<stdio.h>
#include<cstring>

void Usage()
{
    std::cout <<"Usage ./TcpClient server_ip server_port"<< std::endl;
}

int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        Usage();
        return 1;
    }

    //创建客户端的套接字
    int socketfd = socket(AF_INET,SOCK_STREAM,0);
    if(socketfd < 0)
    {
        std::cout << "Create socketfd fail" << std::endl;
        exit(1);
    }

    std::cout << "Create socketfd successfully and the socketfd is " <<
    socketfd << std::endl;

    //填充服务器端的信息
    uint16_t serverport = std::stoi(argv[2]);
    std::string serverip = argv[1];

    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    if(inet_pton(AF_INET,serverip.c_str(),&server.sin_addr) == 1)
    {
        std::cout << "IPV4 converstion is successful!" << std::endl; 
    }
    else
    {
        perror("Invalid IPv4 address");
    }
}
#include"TcpServer.hpp"
#include<memory>

void Usage()
{
    std::cout << "Usage ./TcpServer port" << std::endl;
}

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        Usage();
        return 1;
    }

    //创建智能指针
    uint16_t serverport = std::stoi(argv[1]);
    std::unique_ptr<TcpServer> usr = std::make_unique<TcpServer>(serverport);

    usr->Init();
    usr->Start();

    return 0;
}

接下来我们要写的部分就是和UDP不一样的部分了:

listen (服务端)

TCP建立连接的时候,服务器要进入监听状态,监听客户端是否有链接请求,listen就是完成这部分工作的:

listen() 函数是TCP服务器端编程中的一个重要步骤,它用于将套接字转换为监听状态,以便接受来自客户端的连接请求。一旦调用了 listen(),该套接字就会开始排队等待连接请求,并准备好通过 accept() 来处理这些请求。

函数原型

#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);
  • sockfd:这是由 socket() 函数创建并已经绑定了地址信息(通过 bind())的套接字描述符。
  • backlog:这是监听队列的最大长度,即在服务器开始拒绝新的连接之前,可以有多少个未完成的连接(半连接)。这个值并不是绝对的,操作系统可能会根据实际情况调整它。通常,设置一个合理的值即可,例如5到10之间。

返回值

  • 如果成功,listen() 返回 0。
  • 如果发生错误,则返回 -1,并设置 errno 以指示具体的错误原因。

使用示例

以下是一个完整的C语言代码片段,展示了如何使用 listen() 函数:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080
#define BACKLOG 10 // 监听队列的最大长度

int main() {
    int server_fd;
    struct sockaddr_in address;

    // 创建套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置地址结构体
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定套接字到指定地址和端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 将套接字设置为监听状态
    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server is listening on port %d\n", PORT);

    // 接下来可以调用 accept() 来接收连接
    // ...

    // 关闭套接字
    close(server_fd);
    return 0;
}

注意事项

  1. 绑定后监听:确保在调用 listen() 之前已经成功调用了 bind() 函数来绑定套接字到特定的地址和端口。
  2. 选择合适的 backlog:虽然 backlog 参数指定了监听队列的最大长度,但实际的队列长度可能会受到操作系统的限制。一般来说,除非有特殊需求,否则不需要设置非常大的 backlog 值。
  3. 非阻塞模式:如果你希望 accept() 不会阻塞,可以在调用 listen() 之后将套接字设置为非阻塞模式,但这需要额外的处理逻辑来应对可能的 EAGAIN 或 EWOULDBLOCK 错误。

通过 listen() 函数,服务器能够准备接受客户端的连接请求,并通过后续的 accept() 调用来建立与客户端的实际连接。这使得服务器可以同时处理多个客户端的连接请求,而不会因为等待某个客户端而导致其他客户端被忽视。

我们补全代码:

#pragma once
#include<iostream>
#include<cstring>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

const static uint16_t defaultport = 8888;
const static int BACKLOG = 5;
class TcpServer
{
    public:
    TcpServer(const uint16_t port = defaultport)
        :_port(port)
    {

    }

    void Init()
    {
        //创建套接字
        _listen_socketfd = socket(AF_INET,SOCK_STREAM,0);

        if(_listen_socketfd < 0)
        {
            std::cout << "Create listensocket fail!" << std::endl;
        }

        //初始化本地服务器信息
        struct sockaddr_in local;
        local.sin_family = AF_INET; //IPV4
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        //绑定
        if(bind(_listen_socketfd,(struct sockaddr*)&local,sizeof(local)))
        {
            std::cout << "Bind fail!" << std::endl;
            exit(1);
        }

        std::cout << "Bind successfully and the listensocketfd is " << 
        _listen_socketfd << std::endl;

        //监听
        if(listen(_listen_socketfd,BACKLOG) < 0)
        {
            std::cout << "Listen fail!" << std::endl;
            exit(1);
        }

        std::cout << "Listen successfully! and the listensocketfd is " <<
        _listen_socketfd << std::endl;
    }

    void Start()
    {
        while(true)
        {

        }
    }

    ~TcpServer()
    {

    }


    private:
    //端口号
    uint16_t _port; 

    //监听套接字
    int _listen_socketfd = -1;

};

在这里插入图片描述

accpect (服务端)

accept() 函数用于TCP服务器端编程中,它从已完成连接队列中提取下一个连接请求,并创建一个新的套接字来与客户端通信。这个新的套接字专用于与特定客户端之间的数据交换,而原始的监听套接字则继续等待其他连接请求。

函数原型

#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd:这是由 socket() 创建并已经调用 bind()listen() 设置为监听状态的套接字描述符。
  • addr(可选):这是一个指向 struct sockaddr 结构的指针,用于接收客户端地址信息。如果不需要获取客户端地址信息,可以传入 NULL
  • addrlen(可选):这是一个指向 socklen_t 类型变量的指针,表示 addr 参数所指向结构的大小。调用 accept() 之前应该初始化这个值为结构体的大小;函数返回时,它会被更新为实际填充的地址结构的大小。如果 addrNULL,那么 addrlen 也应该是 NULL

返回值

  • 如果成功,accept() 返回一个新套接字描述符,该描述符用于与客户端进行通信。
  • 如果发生错误,则返回 -1,并设置 errno 以指示具体的错误原因。

注意事项

  1. 阻塞行为:默认情况下,accept() 是一个阻塞调用,即如果没有待处理的连接请求,它将一直等待直到有新的连接到来。如果你希望 accept() 不会阻塞,可以在调用 accept() 之前将套接字设置为非阻塞模式。
  1. 多线程或多进程处理:为了同时处理多个客户端连接,通常需要在每次接受到新连接后启动一个新的线程或子进程来处理该连接,这样主程序可以继续调用 accept() 来接收更多的连接。
  1. 关闭连接:当与客户端的通信完成后,记得关闭对应的客户端套接字 (new_socket)。监听套接字 (server_fd) 则保持打开状态,继续接收新的连接请求。
#pragma once
#include<iostream>
#include<cstring>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

const static uint16_t defaultport = 8888;
const static int BACKLOG = 5;
class TcpServer
{
    public:
    TcpServer(const uint16_t port = defaultport)
        :_port(port)
    {

    }

    void Init()
    {
        //创建套接字
        _listen_socketfd = socket(AF_INET,SOCK_STREAM,0);

        if(_listen_socketfd < 0)
        {
            std::cout << "Create listensocket fail!" << std::endl;
        }

        //初始化本地服务器信息
        struct sockaddr_in local;
        local.sin_family = AF_INET; //IPV4
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        //绑定
        if(bind(_listen_socketfd,(struct sockaddr*)&local,sizeof(local)))
        {
            std::cout << "Bind fail!" << std::endl;
            exit(1);
        }

        std::cout << "Bind successfully and the listensocketfd is " << 
        _listen_socketfd << std::endl;

        //监听
        if(listen(_listen_socketfd,BACKLOG) < 0)
        {
            std::cout << "Listen fail!" << std::endl;
            exit(1);
        }

        std::cout << "Listen successfully! and the listensocketfd is " <<
        _listen_socketfd << std::endl;
   
    }

    void Start()
    {
        while(true)
        {
            //接收(抓取链接)
            struct sockaddr_in temp;
            memset(&temp,0,sizeof(temp));
            socklen_t len = sizeof(temp);

            int new_socketfd = accept(_listen_socketfd,(struct sockaddr*
            )&temp,&len);

 
            char ip_str[INET_ADDRSTRLEN];
            const char* result = inet_ntop(AF_INET, &(temp.sin_addr), ip_str, sizeof(ip_str));
            std::string serverip = result ? ip_str : "Invalid address";

            if(new_socketfd < 0)
            {
                std::cout << "Accpect fail but try again" << std::endl;
                continue;
            }
            else
            {
                std::cout << "Accpect successfully and the new socketfd is "<<
                new_socketfd << std::endl;
            }


    }

    ~TcpServer()
    {

    }


    private:
    //端口号
    uint16_t _port; 

    //监听套接字
    int _listen_socketfd = -1;

};




通过 accept() 函数,服务器能够有效地管理并发连接,确保每个客户端都能得到及时的服务。

connect (客户端)

connect() 函数用于TCP客户端编程中,它尝试与指定的服务器建立连接。一旦成功建立了连接,客户端就可以通过这个套接字与服务器进行数据交换。connect() 是一个阻塞调用,意味着它会一直等待直到连接建立成功或失败。

函数原型

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd:这是由 socket() 创建的套接字描述符,尚未与任何远程地址关联。
  • addr:这是一个指向 struct sockaddr 结构的指针,包含要连接的服务器的地址信息。通常使用 struct sockaddr_instruct sockaddr_in6 来表示IPv4或IPv6地址。
  • addrlen:这是 addr 参数所指向结构体的大小(以字节为单位)。对于 sockaddr_in,这通常是 sizeof(struct sockaddr_in)

返回值

  • 如果成功,connect() 返回 0。
  • 如果发生错误,则返回 -1,并设置 errno 以指示具体的错误原因。常见的错误包括:
  • ECONNREFUSED:连接被服务器拒绝。
  • ETIMEDOUT:连接超时。
  • EINPROGRESS:在非阻塞模式下,连接正在尝试建立但尚未完成。
#include<iostream>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<cerrno>
#include<stdio.h>
#include<cstring>

void Usage()
{
    std::cout <<"Usage ./TcpClient server_ip server_port"<< std::endl;
}

int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        Usage();
        return 1;
    }

    //创建客户端的套接字
    int socketfd = socket(AF_INET,SOCK_STREAM,0);
    if(socketfd < 0)
    {
        std::cout << "Create socketfd fail" << std::endl;
        exit(1);
    }

    std::cout << "Create socketfd successfully and the socketfd is " <<
    socketfd << std::endl;

    //填充服务器端的信息
    uint16_t serverport = std::stoi(argv[2]);
    std::string serverip = argv[1];

    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    
    if(inet_pton(AF_INET,serverip.c_str(),&server.sin_addr) == 1)
    {
        std::cout << "IPV4 converstion is successful!" << std::endl; 
    }
    else
    {
        perror("Invalid IPv4 address");
    }

    //发起连接
    if(connect(socketfd,(struct sockaddr*)&server,sizeof(server))!=0)
    {
        std::cout << "Connect fail!" << std::endl;
        exit(1);
    }
    else
    {
        std::cout << "Connect successfully! and the socketfd is " <<
        socketfd << std::endl;
    }
    
    close(socketfd);

}

注意事项

  1. 非阻塞模式:如果你希望 connect() 不会阻塞,可以在调用 connect() 之前将套接字设置为非阻塞模式。在这种情况下,如果连接尚未完成,connect() 会立即返回 -1 并设置 errnoEINPROGRESS。你需要使用 select()poll() 或其他方法来检查连接是否已经建立成功。
  1. 错误处理:确保对 connect() 的返回值进行适当的错误处理。特别是要注意处理那些可能导致连接失败的情况,如服务器不可达或端口未开放等。
  1. 超时机制:为了防止程序长时间卡在 connect() 上,可以考虑实现超时机制。这可以通过设置套接字选项(如 SO_RCVTIMEOSO_SNDTIMEO)或者使用 select()poll() 来实现。
  1. 资源管理:无论连接是否成功,都应该确保在适当的时候关闭套接字以释放系统资源。

通过 connect() 函数,客户端能够主动发起与服务器的连接请求,从而开始双向的数据传输过程。这是TCP客户端编程中的关键步骤之一。

如果一切顺利就可以看到这样的结果:
在这里插入图片描述

send 和 recv

send()recv() 是用于TCP套接字通信的两个重要函数,分别用于发送和接收数据。它们是BSD套接字API的一部分,在POSIX兼容的操作系统(如Linux、macOS)中广泛使用。

send() 函数

send() 用于通过已建立连接的套接字发送数据。它类似于标准文件I/O中的 write() 函数,但提供了额外的控制选项。

函数原型
#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • sockfd:这是由 socket() 创建并已经通过 connect()accept() 建立了连接的套接字描述符。
  • buf:指向要发送的数据缓冲区的指针。
  • len:要发送的数据长度(以字节为单位)。
  • flags:提供对行为的额外控制,常用的标志包括:
  • MSG_DONTWAIT:使操作非阻塞,即使套接字本身是阻塞模式。
  • MSG_NOSIGNAL:防止SIGPIPE信号在写入已关闭的连接时生成。
  • MSG_MORE:指示有更多数据将被发送,有助于优化Nagle算法的行为。
返回值
  • 如果成功,send() 返回实际发送的字节数。这个值可能小于请求发送的字节数(例如,当套接字缓冲区满时),因此你可能需要循环调用 send() 直到所有数据都被发送。
  • 如果发生错误,则返回 -1,并设置 errno 以指示具体的错误原因。

recv() 函数

recv() 用于从已建立连接的套接字中读取数据。它类似于标准文件I/O中的 read() 函数,也提供了额外的控制选项。

函数原型
#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • sockfd:这是由 socket() 创建并已经通过 connect()accept() 建立了连接的套接字描述符。
  • buf:指向用来存储接收到的数据的缓冲区的指针。
  • len:要接收的最大数据量(以字节为单位)。
  • flags:提供对行为的额外控制,常用的标志包括:
  • MSG_WAITALL:等待直到接收到请求的所有数据。
  • MSG_DONTWAIT:使操作非阻塞,即使套接字本身是阻塞模式。
  • MSG_PEEK:预览数据而不实际移除它(即数据仍然保留在接收队列中)。
返回值
  • 如果成功,recv() 返回实际接收到的字节数。如果返回值为0,则表示对端已经关闭了连接。
  • 如果发生错误,则返回 -1,并设置 errno 以指示具体的错误原因。

我们将客户端和服务器端的代码补完:

#pragma once
#include<iostream>
#include<cstring>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

const static uint16_t defaultport = 8888;
const static int BACKLOG = 5;
class TcpServer
{
    public:
    TcpServer(const uint16_t port = defaultport)
        :_port(port)
    {

    }

    void Init()
    {
        //创建套接字
        _listen_socketfd = socket(AF_INET,SOCK_STREAM,0);

        if(_listen_socketfd < 0)
        {
            std::cout << "Create listensocket fail!" << std::endl;
        }

        //初始化本地服务器信息
        struct sockaddr_in local;
        local.sin_family = AF_INET; //IPV4
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        //绑定
        if(bind(_listen_socketfd,(struct sockaddr*)&local,sizeof(local)))
        {
            std::cout << "Bind fail!" << std::endl;
            exit(1);
        }

        std::cout << "Bind successfully and the listensocketfd is " << 
        _listen_socketfd << std::endl;

        //监听
        if(listen(_listen_socketfd,BACKLOG) < 0)
        {
            std::cout << "Listen fail!" << std::endl;
            exit(1);
        }

        std::cout << "Listen successfully! and the listensocketfd is " <<
        _listen_socketfd << std::endl;
   
    }

    void Start()
    {
        while(true)
        {
            //接收(抓取链接)
            struct sockaddr_in temp;
            memset(&temp,0,sizeof(temp));
            socklen_t len = sizeof(temp);

            int new_socketfd = accept(_listen_socketfd,(struct sockaddr*
            )&temp,&len);

 
            char ip_str[INET_ADDRSTRLEN];
            const char* result = inet_ntop(AF_INET, &(temp.sin_addr), ip_str, sizeof(ip_str));
            std::string serverip = result ? ip_str : "Invalid address";

            if(new_socketfd < 0)
            {
                std::cout << "Accpect fail but try again" << std::endl;
                continue;
            }
            else
            {
                std::cout << "Accpect successfully and the new socketfd is "<<
                new_socketfd << std::endl;
            }

            //开始服务
            while(true)
            {
                char buffer[1024]; //缓冲区

                int n = recv(new_socketfd,buffer,sizeof(buffer)-1,0);

                if(n > 0)
                {
                    buffer[n] = 0;
                    std::cout << "[" << serverip << "]# " << buffer << std::endl;  
                }
                else if(n == 0 || n < 0)
                {
                    std::cout << "Client quit" << std::endl;
                    break;
                }
                else
                {
                    std::cout << "Read fail" << std::endl;
                    break;
                }
            }
    
        }
    }

    ~TcpServer()
    {

    }


    private:
    //端口号
    uint16_t _port; 

    //监听套接字
    int _listen_socketfd = -1;

};
#include<iostream>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<cerrno>
#include<stdio.h>
#include<cstring>

void Usage()
{
    std::cout <<"Usage ./TcpClient server_ip server_port"<< std::endl;
}

int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        Usage();
        return 1;
    }

    //创建客户端的套接字
    int socketfd = socket(AF_INET,SOCK_STREAM,0);
    if(socketfd < 0)
    {
        std::cout << "Create socketfd fail" << std::endl;
        exit(1);
    }

    std::cout << "Create socketfd successfully and the socketfd is " <<
    socketfd << std::endl;

    //填充服务器端的信息
    uint16_t serverport = std::stoi(argv[2]);
    std::string serverip = argv[1];

    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);

    if(inet_pton(AF_INET,serverip.c_str(),&server.sin_addr) == 1)
    {
        std::cout << "IPV4 converstion is successful!" << std::endl; 
    }
    else
    {
        perror("Invalid IPv4 address");
    }

    //发起连接
    if(connect(socketfd,(struct sockaddr*)&server,sizeof(server))!=0)
    {
        std::cout << "Connect fail!" << std::endl;
        exit(1);
    }
    else
    {
        std::cout << "Connect successfully! and the socketfd is " <<
        socketfd << std::endl;
    }
    
    while(true)
    {
        std::string inbuffer;
        std::getline(std::cin,inbuffer);

        //向服务端发送信息
        int n = send(socketfd,inbuffer.c_str(),inbuffer.size(),0);
    }

    close(socketfd);

}

我们用本地回环测试一下:
在这里插入图片描述我们也可以用windows来测试一下:

#define _CRT_SECURE_NO_WARNINGS 1
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include<string>
#include<cstdio>
#include<stdio.h>

#pragma comment(lib, "Ws2_32.lib")

int main() {
    // 初始化Winsock
    WSADATA wsaData;
    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        std::cerr << "WSAStartup failed: " << iResult << std::endl;
        return 1;
    }

    // 创建套接字
    SOCKET SendSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (SendSocket == INVALID_SOCKET) 
    {
        std::cerr << "socket failed: " << WSAGetLastError() << std::endl;
        WSACleanup();
        return 1;
    }

    // 设置服务器地址和端口
    sockaddr_in RecvAddr;
    RecvAddr.sin_family = AF_INET;
    RecvAddr.sin_port = htons(8888); // 假设服务器在12345端口监听

    // 将服务器地址从文本转换为二进制形式
    inet_pton(AF_INET, "43.138.14.12", &RecvAddr.sin_addr); // 换成自己服务器的ip

    // 连接到服务器
    iResult = connect(SendSocket, (SOCKADDR*)&RecvAddr, sizeof(RecvAddr));
    if (iResult == SOCKET_ERROR) {
        std::cerr << "connect failed: " << WSAGetLastError() << std::endl;
        closesocket(SendSocket);
        WSACleanup();
        return 1;
    }

    // 发送消息到服务器
    while (true)
    {
        std::string message;
        std::cout << "Please enter# ";
        std::getline(std::cin,message);

        iResult = send(SendSocket, message.c_str(), message.size(), 0);
        if (iResult == SOCKET_ERROR) {
            std::cerr << "send failed: " << WSAGetLastError() << std::endl;
            closesocket(SendSocket);
            WSACleanup();
            return 1;
        }
        else {
            std::cout << "Message sent successfully: " << message << std::endl;
        }
    }


    // 关闭套接字
    closesocket(SendSocket);

    // 清理Winsock
    WSACleanup();

    return 0;
}

在这里插入图片描述

accpect为啥要返回一个新的文件描述符?

我们之前编写代码时,我们一开始定义的socket是listen的socket,但是我们执行accpect时会返回一个新的套接字描述符,这是为什么呢?

这里主要是为了支持并发连接

服务器通常需要同时处理多个客户端连接。如果 accept() 不返回新的文件描述符,而是使用原始监听套接字进行通信,那么每次只能与一个客户端通信,其他客户端将被阻塞,直到当前通信完成。通过为每个新连接创建一个新的文件描述符,服务器可以同时与多个客户端保持独立的通信会话

listen套接字是负责“揽客”的,只负责抓客户端发来的连接(有点像饭店门口招揽客人),真正提供服务的,是accpect执行后那个新的套接字(饭店里面的服务员才是真正提供服务)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

eNSP之家----ACL实验入门实例详解(Access Control List访问控制列表)(重要重要重要的事说三遍)

ACL实验&#xff08;Access Control List访问控制列表&#xff09;是一种基于包过滤的访问控制技术&#xff0c;它可以根据设定的条件对接口上的数据包进行过滤&#xff0c;允许其通过或丢弃。访问控制列表被广泛地应用于路由器和三层交换机。 准备工作 在eNSP里面部署设备&a…

PySide6基于QSlider实现QDoubleSlider

我在写小工具的时候&#xff0c;需要一个支持小数的滑动条。 我QSpinBox都找到了QDoubleSpinBox&#xff0c;QSlider愣是没找到对应的东西。 网上有好多对QSlider封装实现QDoubleSlider的文章。 似乎Qt真的没有这个东西&#xff0c;需要我们自行实现。 于是我也封装了一个&…

即插即用,无缝集成各种模型,港科大蚂蚁等发布Edicho:图像编辑一致性最新成果!

文章链接&#xff1a;https://arxiv.org/pdf/2412.21079 项目链接&#xff1a;https://ezioby.github.io/edicho/ 亮点直击 显式对应性引导一致性编辑&#xff1a;通过将显式图像对应性融入扩散模型的去噪过程&#xff0c;改进自注意力机制与分类器自由引导&#xff08;CFG&…

福建双色荷花提取颜色

提取指定颜色 HSV双色荷花代码验证 参照《OpenCV图像处理技术》 HSV 要用HSV的色调、饱和度和亮度来提取指定颜色。 双色荷花 农林大学金山校区观音湖 代码 import cv2 import numpy as npimgcv2.imread("./sucai6/hua.jpg") cv2.imshow("SRC",img) h…

关于重构一点简单想法

关于重构一点简单想法 当前工作的组内&#xff0c;由于业务开启的时间正好处于集团php-》go技术栈全面迁移的时间点&#xff0c;组内语言技术栈存在&#xff1a;php、go两套。 因此需求开发过程中通常要考虑两套技术栈的逻辑&#xff0c;一些基础的逻辑也没有办法复用。 在这…

【操作系统】课程 7设备管理 同步测练 章节测验

7.1知识点导图 它详细地展示了I/O系统的层次结构、I/O硬件和软件的组成以及它们的功能。下面是对图中内容的文字整理&#xff1a; I/O设备分类 按使用特性分类 输入设备&#xff1a;键盘、鼠标等输出设备&#xff1a;打印机、绘图仪等交互式设备&#xff1a;显示器等 按传输速率…

用 Python 绘制可爱的招财猫

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​​​​ ​​​​​​​​​ ​​​​ 招财猫&#xff0c;也被称为“幸运猫”&#xff0c;是一种象征财富和好运的吉祥物&#xff0c;经常…

【Vue.js 组件化】高效组件管理与自动化实践指南

文章目录 摘要引言组件命名规范与组织结构命名规范目录组织 依赖管理工具自动化组件文档生成构建自动引入和文档生成的组件化体系代码结构自动引入组件配置使用 Storybook 展示组件文档自动生成 代码详解QA 环节总结参考资料 摘要 在现代前端开发中&#xff0c;组件化管理是 V…

4.5 在C++节点中使用参数

本节沿用之前4.3 节小海龟控制例子。 4.5.1 参数声明与设置 打开src/demo_cpp_service/src/turtle_control.cpp文件 添加测试代码 this->declare_parameter("k",1.0);this->declare_parameter("max_speed",1.0);this->get_parameter("k&q…

Java agent

‌ Java Agent是一种特殊的Java程序&#xff0c;它可以在JVM启动时或运行时动态加载&#xff0c;用于监控和修改其他Java应用程序的行为‌。通过Java Agent&#xff0c;开发者可以在不修改目标应用程序源码的情况下&#xff0c;动态地插入功能&#xff0c;如性能分析、日志记录…

Cannot run program “docker“: CreateProcess error=2,系统找不到指定的文件

今天被这个问题坑了, 网上教程全是直接装插件就行 ,结果我连接可以成功 但是执行docker compose 就会出错, 检测配置 报错com.intellil,execution,process.ProcessNotCreatedException: Cannot run program “docker”: CreateProcess error2,系统找不到指定的文件 gpt 要我去…

二、模型训练与优化(4):模型优化-实操

下面我将以 MNIST 手写数字识别模型为例&#xff0c;从 剪枝 (Pruning) 和 量化 (Quantization) 两个常用方法出发&#xff0c;提供一套可实际动手操作的模型优化流程。此示例基于 TensorFlow/Keras 环境&#xff0c;示范如何先训练一个基础模型&#xff0c;然后对其进行剪枝和…

免费图片批量压缩工具-支持批量修改分辨率

工作需求&#xff0c;需要支持修改分辨率上限的同时进行图片压缩&#xff0c;设计此工具。 1.支持批量文件夹、子文件 2.支持最大分辨率上限&#xff08;高于设定分辨率的图片&#xff0c;强制修改为指定分辨率&#xff0c;解决大图的关键&#xff09; 3.自定义压缩质量&#x…

Github上传项目

写在前面&#xff1a; 本次博客仅仅是个人学习记录&#xff0c;不具备教学作用。内容整理来自网络&#xff0c;太多了&#xff0c;所以就不放来源了。 在github页面的准备&#xff1a; 输入标题。 往下滑&#xff0c;创建 创建后会跳出下面的页面 进入home就可以看到我们刚…

并发编程 之 Java内存模型(详解)

Java 内存模型&#xff08;JMM&#xff0c;Java Memory Model&#xff09;可以说是并发编程的基础&#xff0c;跟众所周知的Java内存区域(堆、栈、程序计数器等)并不是一个层次的划分&#xff1b; JMM用来屏蔽各种硬件和操作系统的内存访问差异&#xff0c;以实现让Java程序在各…

[QCustomPlot] 交互示例 Interaction Example

本文是官方例子的分析: Interaction Example 推荐笔记: qcustomplot使用教程–基本绘图 推荐笔记: 4.QCustomPlot使用-坐标轴常用属性 官方例子需要用到很多槽函数, 这里先一次性列举, 自行加入到qt的.h中.下面开始从简单的开始一个个分析. void qcustomplot_main_init(void); …

WPF控件Grid的布局和C1FlexGrid的多选应用

使用 Grid.Column和Grid.Row布局&#xff0c;将多个C1FlexGrid布局其中&#xff0c;使用各种事件来达到所需效果&#xff0c;点击复选框可以加载数据到列表&#xff0c;移除列表的数据&#xff0c;自动取消复选框等 移除复选框的要注意&#xff01;&#xff01;&#xff01;&am…

04、Redis深入数据结构

一、简单动态字符串SDS 无论是Redis中的key还是value&#xff0c;其基础数据类型都是字符串。如&#xff0c;Hash型value的field与value的类型&#xff0c;List型&#xff0c;Set型&#xff0c;ZSet型value的元素的类型等都是字符串。redis没有使用传统C中的字符串而是自定义了…

生物医学信号处理--随机信号的数字特征

前言 概率密度函数完整地表现了随机变量和随机过程的统计性质。但是信号经处理后再求其概率密度函数往往较难&#xff0c;而且往往也并不需要完整地了解随机变量或过程的全部统计性质只要了解其某些特定方面即可。这时就可以引用几个数值来表示该变量或过程在这几方面的特征。…

LabVIEW数据库管理系统

LabVIEW数据库管理系统&#xff08;DBMS&#xff09;是一种集成了数据库技术与数据采集、控制系统的解决方案。通过LabVIEW的强大图形化编程环境&#xff0c;结合数据库的高效数据存储与管理能力&#xff0c;开发人员可以实现高效的数据交互、存储、查询、更新和报告生成。LabV…