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

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

我们之前了解过了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是差不多的,所以我们这边按照套路写一下就行了:

cpp 复制代码
#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;
};
cpp 复制代码
#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");
    }
}
cpp 复制代码
#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() 来处理这些请求。

函数原型

c 复制代码
#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() 函数:

c 复制代码
#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() 调用来建立与客户端的实际连接。这使得服务器可以同时处理多个客户端的连接请求,而不会因为等待某个客户端而导致其他客户端被忽视。

我们补全代码:

cpp 复制代码
#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服务器端编程中,它从已完成连接队列中提取下一个连接请求,并创建一个新的套接字来与客户端通信。这个新的套接字专用于与特定客户端之间的数据交换,而原始的监听套接字则继续等待其他连接请求。

函数原型

c 复制代码
#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() 之前将套接字设置为非阻塞模式。
  2. 多线程或多进程处理 :为了同时处理多个客户端连接,通常需要在每次接受到新连接后启动一个新的线程或子进程来处理该连接,这样主程序可以继续调用 accept() 来接收更多的连接。
  3. 关闭连接 :当与客户端的通信完成后,记得关闭对应的客户端套接字 (new_socket)。监听套接字 (server_fd) 则保持打开状态,继续接收新的连接请求。
cpp 复制代码
#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() 是一个阻塞调用,意味着它会一直等待直到连接建立成功或失败。

函数原型

c 复制代码
#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:在非阻塞模式下,连接正在尝试建立但尚未完成。
c 复制代码
#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() 或其他方法来检查连接是否已经建立成功。
  2. 错误处理 :确保对 connect() 的返回值进行适当的错误处理。特别是要注意处理那些可能导致连接失败的情况,如服务器不可达或端口未开放等。
  3. 超时机制 :为了防止程序长时间卡在 connect() 上,可以考虑实现超时机制。这可以通过设置套接字选项(如 SO_RCVTIMEOSO_SNDTIMEO)或者使用 select()poll() 来实现。
  4. 资源管理:无论连接是否成功,都应该确保在适当的时候关闭套接字以释放系统资源。

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

如果一切顺利就可以看到这样的结果:

send 和 recv

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

send() 函数

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

函数原型
c 复制代码
#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() 函数,也提供了额外的控制选项。

函数原型
c 复制代码
#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 以指示具体的错误原因。

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

cpp 复制代码
#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;

};
cpp 复制代码
#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来测试一下:

cpp 复制代码
#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执行后那个新的套接字(饭店里面的服务员才是真正提供服务)

相关推荐
tjjingpan2 小时前
HCIA-Access V2.5_8_1_EPON原理_PON基本概念
网络
hc_bmxxf2 小时前
【无标题】
网络
阿乾之铭5 小时前
计算机网络(第七版)知识点
网络·计算机网络
m0_748252605 小时前
在Linux系统上使用nmcli命令配置各种网络(有线、无线、vlan、vxlan、路由、网桥等)
linux·服务器·网络
XuanRanDev5 小时前
【计算机网络】深入了解交换机:概念、命令及基本操作
网络·计算机网络
raysync8885 小时前
如何保留企业传输系统操作习惯来实现企业无缝FTP替代升级
运维·服务器·网络
严文文-Chris5 小时前
【一个HTTP请求和一个HTTP会话的区别】
网络·网络协议·http
zhgjx_chen6 小时前
ENSP综合实验(中小型网络)
服务器·网络·华为
爱思考的发菜_汽车网络信息安全6 小时前
汽车网络信息安全-通信安全方案(基于OSI模型)
网络·汽车
LLLuckyGirl~9 小时前
计算机网络的定义与发展历程
网络·计算机网络