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

news2025/1/8 4:53:39

计算机网络 —— 网络编程(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执行后那个新的套接字(饭店里面的服务员才是真正提供服务)

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

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

相关文章

Vue 项目自动化部署:Coding + Jenkins + Nginx 实践分享

前言 本文详细记录如何使用 Coding (以 Jenkinsfile 为核心) 和 Nginx 部署 Vue 项目&#xff0c;包含完整流程、配置细节及注意事项&#xff0c;为开发者提供一个高效的实践参考。 准备工作 这里借用一个优秀的开源项目做演示&#xff1a;芋道源码/yudao-ui-admin-vue2。 以…

Mysql运维利器之备份恢复-xtrabackup 安装

1、插件下载 xtrabackup 下载地址 找到自己mysql版本对应得 插件版本下载 2、执行安装命令 yum localinstall percona-xtrabackup-80-8.0.26-18.1.el7.x86_64.rpm 安装完毕&#xff01;查看版本信息 xtrabackup --version 安装完毕&#xff01;&#xff01;&#xff01;

【JAVA】神经网络的基本结构和前向传播算法

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c; 忍不住分享一下给大家。点击跳转到网站 学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把…

ip属地的信息准确吗?ip归属地不准确怎么办

在数字化时代&#xff0c;IP属地信息成为了我们日常生活中不可或缺的一部分。在各大社交媒体平台上&#xff0c;IP属地信息都扮演着重要的角色。然而&#xff0c;随着技术的不断进步和网络的复杂性增加&#xff0c;IP属地信息的准确性问题也日益凸显。那么&#xff0c;IP属地信…

Flask----前后端不分离-登录

文章目录 扩展模块flask-wtf 的简单使用定义用户数据模型flask-login完成用户登录 扩展模块 flask-sqlalchmy&#xff0c;连接数据库flask-login&#xff0c;处理用户的登录&#xff0c;认证flask-session&#xff0c;会话保持&#xff0c;默认对用户数据加密&#xff0c;存储…

[读书日志]从零开始学习Chisel 第一篇:书籍介绍,Scala与Chisel概述,Scala安装运行(敏捷硬件开发语言Chisel与数字系统设计)

简介&#xff1a;从20世纪90年代开始&#xff0c;利用硬件描述语言和综合技术设计实现复杂数字系统的方法已经在集成电路设计领域得到普及。随着集成电路集成度的不断提高&#xff0c;传统硬件描述语言和设计方法的开发效率低下的问题越来越明显。近年来逐渐崭露头角的敏捷化设…

计算机网络掩码、最小地址、最大地址计算、IP地址个数

一、必备知识 1.无分类地址IPV4地址网络前缀主机号 2.每个IPV4地址由32位二进制数组成 3. /15这个地址表示网络前缀有15位&#xff0c;那么主机号32-1517位。 4.IP地址的个数&#xff1a;2**n (n表示主机号的位数) 5.可用&#xff08;可分配&#xff09;IP地址个数&#x…

【阅读笔记】基于FPGA的红外图像二阶牛顿插值算法的实现

图像缩放技术在图像显示、传输、分析等多个领域中扮演着重要角色。随着数字图像处理技术的发展&#xff0c;对图像缩放质量的要求也越来越高。二阶牛顿插值因其在处理图像时能够较好地保持边缘特征和减少细节模糊&#xff0c;成为了图像缩放中的一个研究热点。 一、 二阶牛顿插…

计算机毕业设计Python电商品推荐系统 商品比价系统 电商比价系统 商品可视化 商品爬虫 机器学习 深度学习 京东爬虫 国美爬虫 淘宝爬虫 大数据

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

BBP飞控板中的坐标系变换

一般飞控板中至少存在以下坐标系&#xff1a; 陀螺Gyro坐标系加速度计Acc坐标系磁强计Mag坐标系飞控板坐标系 在BBP飞控板采用的IMU为同时包含了陀螺&#xff08;Gyro&#xff09;及加速度计&#xff08;Acc&#xff09;的6轴传感器&#xff0c;故Gyro及Acc为同一坐标系。同时…

企业网络综合组网

1 概述 2 网络需求分析 2.1企业需求分析 公司规模 员工规模&#xff1a;200人&#xff0c;其中包括技术研发人员&#xff0c;市场营销人员&#xff0c;运营管理人员&#xff0c;客户服务人员等。部门数量&#xff1a;19个部门&#xff0c;包括财务部&#xff0c;人力资源部…

【沉默的羔羊心理学】汉尼拔的“移情”游戏:操纵与理解的艺术,精神分析学视角下的角色互动

终极解读《沉默的羔羊》&#xff1a;弗洛伊德精神分析学视角下的深层剖析 关键词 沉默的羔羊弗洛伊德精神分析学角色心理意识与潜意识性别与身份 弗洛伊德精神分析学简介 弗洛伊德的精神分析学是心理学的一个重要分支&#xff0c;主要关注人类行为背后的无意识动机和冲突。…

Qt窗口获取Tftpd32_svc服务下载信息

前言 一个由Qt开发的Windows小工具需要布置Tftp协议服务端来支持设备下载数据&#xff0c;并显示下载列表&#xff08;进度、下载源等&#xff09;。 考虑开发方便&#xff0c;优先使用了Qtftp方案&#xff0c;经测试发现&#xff0c;不够稳定&#xff0c;会有下载超时的情况&a…

合合信息亮相CSIG AI可信论坛,全面拆解AI视觉内容安全的“终极防线”

合合信息亮相CSIG AI可信论坛&#xff0c;全面拆解视觉内容安全的“终极防线”&#xff01; &#x1f42f; AI伪造泛滥&#xff0c;我们还能相信“眼见为实”吗&#xff1f; 近期&#xff0c;由中国图象图形学学会主办的CSIG青年科学家会议 AI可信论坛在杭州成功举办。本次论…

Bash Shell的操作环境

目录 1、路径与指令搜寻顺序 2、bash的进站&#xff08;开机&#xff09;与欢迎信息&#xff1a;/etc/issue&#xff0c;/etc/motd &#xff08;1&#xff09;/etc/issue &#xff08;2&#xff09;/etc/motd 3、bash的环境配置文件 &#xff08;1&#xff09;login与non-…

如何在没有 iCloud 的情况下将联系人从 iPhone 传输到 iPhone

概括 近期iOS 13.5的更新以及苹果公司发布的iPhone SE在众多iOS用户中引起了不小的轰动。此外&#xff0c;不少变化&#xff0c;如暴露通知 API、Face ID 增强功能以​​及其他在 COVID-19 期间与公共卫生相关的新功能&#xff0c;吸引了 iPhone 用户尝试新 iPhone 并更新到最…

网站设计总结后期维护与更新的重要性

当我们谈论网站设计时&#xff0c;往往会聚焦在初始阶段的创意和实现上。然而&#xff0c;一旦网站建成并上线&#xff0c;后期维护与更新的重要性就显得尤为突出。一个网站的成功不仅取决于其初始设计&#xff0c;更在于持续的维护与更新。 首先&#xff0c;后期维护能够确保网…

Android NDK开发实战之环境搭建篇(so库,Gemini ai)

文章流程 音视频安卓开发首先涉及到ffmpeg编译打包动态库&#xff0c;先了解动态库之间的cpu架构差异性。然后再搭建可运行的Android 环境。 So库适配 ⽇常开发我们经常会使⽤到第三库&#xff0c;涉及到底层的语⾳&#xff0c;视频等都需要添加so库。⽽so库的体积⼀般来说 ⾮…

数据仓库中的指标体系模型介绍

数据仓库中的指标体系介绍 文章目录 数据仓库中的指标体系介绍前言什么是指标体系指标体系设计有哪些模型?1. 指标分层模型2. 维度模型3. 指标树模型4. KPI&#xff08;关键绩效指标&#xff09;模型5. 主题域模型6.平衡计分卡&#xff08;BSC&#xff09;模型7.数据指标框架模…

unity学习11:地图相关的一些基础

目录 1 需要从 unity的 Asset Store 下载资源 1.1 下载资源 1.2 然后可以从 package Manager 里选择下载好的包&#xff0c;import到项目里 2 创建地形 2.1 创建地形 2.2 地形 Terrain大小 2.3 各种网格的尺寸大小 2.4 比较这个地形尺寸和创建的其他物体的大小对比 3 …