[linux网络编程]UDP协议和TCP协议的使用

news2024/12/23 15:09:11

目录

看以下内容前,你要先了解main函数带参数有什么用、

了解socket的相关函数接口

 如果不了解socket的相关函数接口请先看我这篇文章

main函数带参数有什么用

UDP

udp_server

1.生成socket文件描述符

2.填充sockaddr_in信息

3.bind

4.发(收)消息

5.关闭socket文件描述符

udp_client

1.生成socket文件描述符

2.填充sockaddr_in信息

3.客户端不用手动bind

4.发(收)消息 (client会在首次发送数据的时候会自动进行bind)

5.关闭socket文件描述符

 完整代码

Makefile

udp_server.hpp

udp_server.cc

udp_client.cc

TCP

tcp_server

1.生成socket文件描述符

2.填充sockaddr_in信息

3.bind

4.监听与获取连接

5.发(收)消息

6.关闭socket文件描述符

tcp_client

1.生成socket文件描述符

2.填充sockaddr_in信息

3.客户端不用手动bind

4.连接服务器(server)

5.发(收)消息

6.关闭socket文件描述符

完整代码

Makefile

tcp_server.hpp

tcp_server.cc

tcp_client.cc

代码运行结果视频展示


看以下内容前,你要先了解main函数带参数有什么用、

了解socket的相关函数接口

 如果不了解socket的相关函数接口请先看我这篇文章

[网络编程]socket嵌套字的一些常用接口-CSDN博客

main函数带参数有什么用

我就给一下例子知道怎么用就行

例如:

在服务端

意思就是启动服务器,端口号为8888,这里在服务端我们没有设置ip, 只要端口号正确,任意IP都能和我们的服务器连接

那么

argc == 2

argv[0] == ./server

argv[1] == 8888

在客户端

 

 意思是启动客户端,连接ip地址为127.0.0.1,端口号为8888的服务器

 那么

argc == 3

argv[0] == ./client

argv[1] == 127.0.0.1

argv[2] == 8888

UDP

udp_server

1.生成socket文件描述符

        //创建socket
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd < 0)
        {
            std::cerr << "creater socket fail!!" << std::endl;
            return false;
        }

socket函数参数说明:

  • AF_INET:仍然指定使用IPv4地址族。
  • SOCK_DGRAM:指定创建的是一个UDP socket。
  • 0:同样,这里让系统自动选择默认的协议。

2.填充sockaddr_in信息

//填充sockaddr_in
//定义sockaddr_in结构体变量---1
struct sockaddr_in saddr;
//初始化server结构体---2
memset(&saddr, 0, sizeof(saddr));
//设置地址族---3
saddr.sin_family = AF_INET;//协议家族
//设置端口号---4
saddr.sin_port = htons(_port);//主机字节序转网络字节序
//inet_pton(AF_INET, _ip.c_str(), &(saddr.sin_addr));//点分十进制转网络字节序
//设置IP地址---5
saddr.sin_addr.s_addr = INADDR_ANY;

 解释:

  1. 这行代码定义了一个sockaddr_in类型的变量saddrsockaddr_in是用于IPv4的套接字地址结构。
  2. 使用memset函数将saddr结构体的所有字节都设置为0。这是为了确保结构体中的所有未明确设置的字段都从一个已知的、安全的初始值开始。
  3. saddr结构体的sin_family字段设置为AF_INET,表示这是一个IPv4地址。
  4. sin_port字段用于存储端口号。htons函数用于将主机字节序的端口号转换为网络字节序。_port是一个之前定义的变量,它包含了要设置的端口号。
  5. 这里将sin_addr字段的s_addr成员设置为INADDR_ANYINADDR_ANY是一个特殊的常量,用于表示套接字应该绑定到所有可用的网络接口,通常用于服务器程序,以便它们可以接受来自任何IP地址的连接。

3.bind

//bind
int n = ::bind(_sockfd, CONV(&saddr), sizeof(saddr));
if(n < 0)
{
    std::cerr << "bind fail!" << std::endl;
}
  • ::bind:这是bind函数的全局作用域解析运算符形式。这通常用于避免与局部作用域或类作用域中的bind函数或变量名冲突。
  • _sockfd:这是一个之前已经创建并初始化的套接字文件描述符。
  • CONV(&saddr):这里CONV可能是一个宏函数,用于将sockaddr_in* 转换为sockaddr*,适合bind函数期望的地址结构体的指针类型。
  • sizeof(saddr):这指定了saddr的大小,告诉bind函数要绑定多少字节的地址信息。

4.发(收)消息

//1
struct sockaddr_in peer;
//2
memset(&peer, 0, sizeof(peer)); // 初始化
socklen_t len = sizeof(peer);
// 收发消息
while (true)

    // 使用recvfrom收消息
    char buffer[1024];
    //3
    ssize_t rn = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&peer), &len);
    if (rn <= 0)
    {
        std::cerr << "receive message is empty" << std::endl;
        return false;
    }
    //输出从客户端接收到的消息
    buffer[rn] = 0; // 添加'/0'
    std::cout << "client say# " << buffer << std::endl;
    // 处理消息:在消息前加字符串"server say# "
    std::string message = "server say# ";
    message += buffer;
    //4
    // 发消息回client
    ssize_t sn = sendto(_sockfd, message.c_str(), message.size(), 0, CONV(&peer), len);
    
    if (sn <= 0)
    {
        std::cerr << " send message fail!!!" << std::endl;
        return false;
    }
}

1:

这里定义了一个 struct sockaddr_in 类型的变量 peer,用于存储客户端的地址信息。

2:

  • memset(&peer, 0, sizeof(peer));:使用 memset 函数初始化 peer 结构体,将其所有字节设置为0。这是为了避免使用未初始化的内存,并确保 peer 结构体在 recvfrom 调用前处于已知状态。
  • socklen_t len = sizeof(peer);:定义了一个 socklen_t 类型的变量 len,用于存储客户端地址结构的实际大小。这个变量将在 recvfrom 调用中更新,以反映实际接收到的地址信息的大小。

3:

  • char buffer[1024];:定义了一个字符数组 buffer,大小为1024字节,用于存储从客户端接收到的消息。
  • recvfrom 函数用于从 _sockfd 套接字接收消息。这里,sizeof(buffer) - 1 确保 buffer 数组有足够的空间来存储一个额外的空字符 '\0',以标记字符串的结束。CONV(&peer) 假设是一个宏函数,用于将 sockaddr_in 结构体转换为 sockaddr 结构体

4:

  • std::string message = "server say# ";:创建一个字符串 message,并初始化为 "server say# "
  • message += buffer;:将接收到的客户端消息追加到 message 字符串的末尾。
  • sendto 函数用于将处理后的消息发送回客户端。这里,message.c_str() 获取 message 字符串的C字符串表示,message.size() 获取字符串的长度。

5.关闭socket文件描述符

 // 关闭sockfd
close(_sockfd);

udp_client

1.生成socket文件描述符

//创建socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd <  0)
{
    std::cerr << "creater socket fail!!!" << std::endl;
}

2.填充sockaddr_in信息

uint16_t port = std::stoi(argv[2]);
// 填充server的sockaddr_in信息
// 定义sockaddr_in结构体变量---1
struct sockaddr_in server;
// 初始化server结构体---2
memset(&server, 0, sizeof(server));
// 定义socklen_t变量---3
socklen_t len = sizeof(server);
// 设置地址族---4
server.sin_family = AF_INET;
// 设置端口号---5
server.sin_port = htons(port);
// 设置IP地址---6
inet_pton(AF_INET, argv[1], &server.sin_addr);

1:

这里定义了一个 sockaddr_in 类型的变量 server,用于存储服务器的地址信息。

2:

使用 memset 函数将 server 结构体的所有字节设置为0。这是为了确保结构体中的所有字段都被正确地初始化为默认值,避免使用未初始化的内存。

3:

定义了一个 socklen_t 类型的变量 len,并赋值为 server 结构体的大小。这个变量通常用于 bindconnect 等网络函数中,表示地址结构体的长度。

4:

设置 server 结构体的 sin_family 字段为 AF_INET。这表示使用的是IPv4地址族。

5:

首先,从命令行参数 argv[2] 中获取端口号,并将其转换为整数类型 uint16_t。然后,使用 htons 函数将端口号从主机字节序转换为网络字节序,并赋值给 server.sin_port

6:

使用 inet_pton 函数将点分十进制的IP地址字符串(从 argv[1] 中获取)转换为二进制形式,并存储在 server.sin_addr 中。AF_INET 参数表示正在处理的是IPv4地址。

3.客户端不用手动bind

在首次发送数据时,如果没有显式调用bind,操作系统会自动为该socket分配一个本地端口

4.发(收)消息 (client会在首次发送数据的时候会自动进行bind)

while (true)
{
    // 发信息---1
    std::string message;
    std::getline(std::cin, message);
    sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server));

    // 收消息---2
    char buffer[1024];
    ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&server), &len);
    //3
    if (n > 0)
    {
        buffer[n] = 0; // 添加'/0'
        std::cout << buffer << std::endl;
    }
    else
    {
        break;
    }
}

 1:

  • std::string message;:定义一个std::string类型的变量message,用于存储用户输入的消息。
  • std::getline(std::cin, message);:从标准输入(通常是键盘)读取一行文本,并将其存储在message字符串中。
  • sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server));:使用sendto函数将message发送到服务器。message.c_str()返回指向message内部字符数组的指针,message.size()返回消息的长度。CONV是一个宏函数,用于将sockaddr_in结构体转换为sockaddr结构体。sizeof(server)提供服务器地址结构的大小。

2:

  • char buffer[1024];:定义一个字符数组buffer,大小为1024字节,用于存储从服务器接收到的消息。
  • recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&server), &len);:使用recvfrom函数从服务器接收消息。sizeof(buffer) - 1确保buffer数组有足够的空间来存储一个额外的空字符'\0'。接收到的字节数将存储在n中。

3:

  • 如果recvfrom返回的n(接收到的字节数)大于0,表示成功接收到了消息。
  • buffer[n] = '\0';:在接收到的消息字符串的末尾添加一个空字符'\0',确保它是一个合法的C字符串。
  • std::cout << buffer << std::endl;:输出从服务器接收到的消息。
  • 如果n小于或等于0,表示没有接收到有效的消息(可能是因为连接断开或错误),因此break语句将终止无限循环。

5.关闭socket文件描述符

close(sockfd);

 完整代码

Makefile

.PHOINY:all clean
all:client server

server:udp_server.cc
	g++ -o $@ $^ -std=c++11
	
client:udp_client.cc
	g++ -o $@ $^ -std=c++11 
clean:
	rm -f client server

udp_server.hpp

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <string>
#define CONV(in) ((struct sockaddr *)in)

const int socketfddefault = -1;
class UdpServer
{
public:
    UdpServer(uint16_t port, int sockfd = socketfddefault)
        : _port(port)
    {
    }
    bool Init()
    {
        // 创建socket
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            std::cerr << "creater socket fail!!" << std::endl;
            return false;
        }
        // 填充sockaddr_in
        // 定义sockaddr_in结构体变量---1
        struct sockaddr_in saddr;
        // 初始化server结构体---2
        memset(&saddr, 0, sizeof(saddr));
        // 设置地址族---3
        saddr.sin_family = AF_INET; // 协议家族
        // 设置端口号---4
        saddr.sin_port = htons(_port); // 主机字节序转网络字节序
        // inet_pton(AF_INET, _ip.c_str(), &(saddr.sin_addr));//点分十进制转网络字节序
        // 设置IP地址---5
        saddr.sin_addr.s_addr = INADDR_ANY;

        // bind
        int n = ::bind(_sockfd, CONV(&saddr), sizeof(saddr));
        if (n < 0)
        {
            std::cerr << "bind fail!" << std::endl;
        }
        return true;
    }
    bool Start()
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer)); // 初始化
        socklen_t len = sizeof(peer);
        // 收发消息
        while (true)
        {
            // 使用recvfrom收消息
            char buffer[1024];
            ssize_t rn = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&peer), &len);
            if (rn <= 0)
            {
                std::cerr << "receive message is empty" << std::endl;
                return false;
            }
            //输出从客户端接收到的消息
            buffer[rn] = 0; // 添加'/0'
            std::cout << "client say# " << buffer << std::endl;
            // 处理消息:在消息前加字符串"server say# "
            std::string message = "server say# ";
            message += buffer;

            // 发消息回client
            ssize_t sn = sendto(_sockfd, message.c_str(), message.size(), 0, CONV(&peer), len);
            if (sn <= 0)
            {
                std::cerr << " send message fail!!!" << std::endl;
                return false;
            }
        }
        return true;
    }
    int GetFd()
    {
        return _sockfd;
    }
    uint16_t GetPort()
    {
        return _port;
    }

    ~UdpServer()
    {
        // 关闭sockfd
        close(_sockfd);
    }

private:
    // 不需要,设置为任意ip都可以连接服务器
    // std::string _ip;//点分十进制ip地址
    int _sockfd;
    uint16_t _port; // 16位端口号
};

udp_server.cc

#include "udp_server.hpp"
#include <memory>
void Usage(const std::string& process)
{
    std::cout << "Usage:" << std::endl;;
    std::cout <<  "\t\t"<< process << " port, example: " << process << " 8888" << std::endl;
}
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        return 0;
    }

    std::unique_ptr<UdpServer> server(new UdpServer(std::stoi(argv[1])));
    if(!server->Init())
    {
        std::cout << "server->Init() fail" << std::endl;
        return 1;
    }
    if(!server->Start())
    {
        std::cout << "server->Start() fail" << std::endl;
        return 2;
    }
    return 0;
}

udp_client.cc

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <iostream>
#include <string>
#include <arpa/inet.h>
#define CONV(in) ((struct sockaddr *)in)

void Usage(const std::string &process)
{
    std::cout << "Usage:" << std::endl;
    std::cout << "\t\t" << process << " ip port, example: " << process << " 127.0.0.1 8888" << std::endl;
}
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return 0;
    }
    // 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "creater socket fail!!!" << std::endl;
    }

    std::cout << "client socket: " << sockfd << " precess pid: " << getpid() << std::endl;
    uint16_t port = std::stoi(argv[2]);
    // 填充server的sockaddr_in信息
    // 定义sockaddr_in结构体变量---1
    struct sockaddr_in server;
    // 初始化server结构体---2
    memset(&server, 0, sizeof(server));
    // 定义socklen_t变量
    socklen_t len = sizeof(server);
    // 设置地址族---3
    server.sin_family = AF_INET;
    // 设置端口号---4
    server.sin_port = htons(port);
    // 设置IP地址---5
    inet_pton(AF_INET, argv[1], &server.sin_addr);

    // 让操作系统自动bind:在首次发送数据时,如果没有显式调用bind,操作系统会自动为该socket分配一个本地端口
    while (true)
    {
        // 发信息
        std::string message;
        std::getline(std::cin, message);
        sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server));

        // 收消息
        char buffer[1024];
        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&server), &len);
        if (n > 0)
        {
            buffer[n] = 0; // 添加'/0'
            std::cout << buffer << std::endl;
        }
        else
        {
            break;
        }
    }
    close(sockfd);
}

TCP

tcp_server

1.生成socket文件描述符

std::cout << "process pid:" << getpid() << std::endl;
// 1.创建嵌套字
_listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listenfd < 0)
{
    std::cerr << "creater listenfd fail!!!" << std::endl;
    return false;
}
  • AF_INET:指定使用IPv4地址族。这意味着套接字将使用IPv4地址来与网络上的其他设备进行通信。
  • SOCK_STREAM:指定创建的套接字类型为TCP套接字。TCP是一个面向连接的、可靠的、字节流的协议,适用于需要确保数据完整性和顺序的应用场景。
  • 0:通常设置为0,表示使用默认的协议。对于TCP套接字,这通常是TCP协议本身。

2.填充sockaddr_in信息

//填充sockaddr_in信息
// 定义sockaddr_in结构体变量---1
struct sockaddr_in local;
// 使用memset初始化结构体---2
memset(&local, 0, sizeof(local));
// 设置地址族---3
local.sin_family = AF_INET;
// 设置端口号---4
local.sin_port = htons(_port);
// 设置IP地址---5
local.sin_addr.s_addr = INADDR_ANY;

1:

这行代码定义了一个sockaddr_in类型的变量localsockaddr_in是一个结构体,通常用于IPv4地址的套接字编程。

2:

使用memset函数将local结构体的所有字节设置为0。这样做是为了确保结构体的所有未明确赋值的字段都被初始化为0,防止未初始化的内存导致的问题。

3:

sin_family字段设置为AF_INET,表示这个结构体用于IPv4地址。

4:

设置要绑定的端口号。_port是端口的整数值,它是以主机字节序存储的。htons函数用于将端口号从主机字节序转换为网络字节序(大端字节序),因为网络通信中通常使用网络字节序。

5:

sin_addr.s_addr字段设置为INADDR_ANY。这个特殊的宏表示套接字应该绑定到所有可用的网络接口上,即接受来自任何IP地址的连接。

3.bind

// bind
int ret = ::bind(_listenfd, CONV(&local), sizeof(local));
if (ret < 0)
{
    std::cerr << "bind fail!!!" << std::endl;
    return false;
}
  • :: 是一个作用域解析运算符,它在这里用来确保调用的是全局作用域中的bind函数,而不是某个类或者命名空间中可能存在的同名函数。
  • _listenfd 是之前通过socket函数创建的套接字文件描述符。
  • CONV是一个宏函数,用于将local的地址转换为正确的类型,以便作为bind函数的参数。通常,这个宏函数会转换为(struct sockaddr*),以确保类型匹配bind函数的第二个参数。
  • sizeof(local) 是local结构体的大小,它告诉bind函数要处理多少字节的地址信息。 

4.监听与获取连接

// 进入倾听状态
//1
ret = listen(_listenfd, 2); // 最多两client连接server
if (ret < 0)
{
    std::cerr << "listen fail!!!" << std::endl;
    return false;
}

// 获取连接
//2
struct sockaddr_in peer;
//3
memset(&peer, 0, sizeof(peer));
//4
socklen_t len = sizeof(peer);
//5
_sockfd = accept(_listenfd, CONV(&peer), &len);
if (_sockfd < 0)
{
    std::cerr << "accept link fail!!!" << std::endl;
    return false;
}
std::cout << "accept link success , socket fd:" << _sockfd << std::endl;

 1:

_listenfd这个套接字设置为监听状态,准备接受客户端的连接请求。参数2表示这个套接字队列中可以挂起的最大未完成连接数。换句话说,它限制了同时等待服务器处理的客户端连接数。

2:

定义了一个sockaddr_in类型的变量peer,用于存储客户端的地址信息。
3:

使用memset函数将peer结构体的所有字节设置为0,确保结构体的所有未明确赋值的字段都被初始化为0。

4:

定义了一个socklen_t类型的变量len,并初始化为peer结构体的大小。这个变量稍后会传递给accept函数,以便accept知道要填充多少字节的客户端地址信息。

5:

  • 这行代码调用accept函数,尝试从_listenfd这个监听套接字上接受一个客户端的连接请求。如果成功,accept会返回一个新的套接字文件描述符(_sockfd),这个新的套接字用于与客户端通信。同时,客户端的地址信息会被填充到peer结构体中,len会被更新为实际填充的字节数。
  • CONV是一个宏函数,用于将local的地址转换为正确的类型,以便作为bind函数的参数。通常,这个宏函数会转换为(struct sockaddr*),以确保类型匹配bind函数的第二个参数。

5.发(收)消息

// 收发消息
while (true)
{
    // 收消息---1
    char buffer[1024];
    ssize_t n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
    if (n > 0)
    {
        buffer[n] = 0; // 添加'/0'
        std::cout << "client syd# " << buffer << std::endl;
        // 处理信息
        // 发消息
        std::string message = "server syd# ";
        message += buffer;
        //2
        n = send(_sockfd, message.c_str(), message.size(), 0);
        if (n == 0)
        {
            std::cerr << "send message is empty!!!" << std::endl;
        }
        else if (n < 0)
        {
            std::cerr << "send message fail!!!" << std::endl;
        }
        else
        {
            std::cerr << "send message success, message len is: " << message.size() << std::endl;
        }
    }
}

1:

使用recv函数从与客户端通信的套接字_sockfd接收消息。接收的最多字节数是sizeof(buffer) - 1,确保留有一个字节的位置给字符串结束符'\0'

2:

使用send函数将处理后的消息发送回客户端。发送的内容是message的C字符串形式(通过message.c_str()获取),长度为message.size()。 

6.关闭socket文件描述符

close(_listenfd);
close(_sockfd);

tcp_client

1.生成socket文件描述符

// 创建socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0)
{
    std::cerr << "creater socket fail!!!" << std::endl;
}
  • AF_INET:这是地址族(address family)参数,表示使用IPv4地址。
  • SOCK_STREAM:这是套接字类型参数,表示创建的是一个面向连接的套接字,通常用于TCP协议。
  • 0:这是协议参数,通常设置为0,表示使用默认的协议。

2.填充sockaddr_in信息

// 填充sockaddr_in信息
//1
struct sockaddr_in client;
//2
memset(&client, 0, sizeof(client));
//3
client.sin_family = AF_INET;
//4
client.sin_port = htons((uint16_t)std::stoi(argv[2]));
//5
inet_pton(AF_INET, argv[1], &(client.sin_addr));

1:

定义一个sockaddr_in类型的变量client。这个结构体通常用于IPv4地址和端口的表示。

2:

使用memset函数将client结构体的所有字节设置为0。这是一个常见的做法,用于初始化结构体,确保没有未初始化的内存。

3:

设置sin_family字段为AF_INET,表示这个地址是IPv4地址。
4:

设置sin_port字段为网络字节序的端口号。htons函数用于将主机字节序的端口号转换为网络字节序。这里假设argv[2]是一个字符串形式的端口号,使用std::stoi函数将其转换为整数。

5:

inet_pton函数用于将点分十进制的IPv4地址字符串(如"192.168.1.1")转换为二进制形式,并存储在sin_addr字段中。这里假设argv[1]是一个字符串形式的IPv4地址。AF_INET表示我们正在处理IPv4地址。

3.客户端不用手动bind

客户端通常不需要调用bind,因为操作系统会在创建socket时自动为其分配一个未被使用的本地端口。当客户端调用connect函数去连接服务器时,操作系统会自动完成socket的绑定操作,并将socket与远程服务器的地址和端口关联起来。

4.连接服务器(server)


// 连接server
int n = connect(sockfd, CONV(&client), sizeof(client));
if (n < 0)
{
    std::cerr << "connect server fail!!!" << std::endl;
    return 1;
}
std::cerr << "connect server success!!!" << std::endl;

  • connect 函数是客户端用来建立与服务器的连接的。
  • sockfd 是一个套接字描述符,代表客户端的套接字。它通常通过 socket 函数创建。
  • CONV(&client) 这部分代码中的 CONV 是一个宏函数,用于将 sockaddr_in 类型的 client 转换为 connect 函数所需的 sockaddr 类型。
  • sizeof(client) 提供了 client 结构体的大小,这告诉 connect 函数要发送多少字节的地址信息。
  • n 存储 connect 函数的返回值。如果连接成功,返回值通常是0;如果连接失败,返回值是-1,并且全局变量 errno 会被设置为指示错误原因的值。

5.发(收)消息

while (true)
{
    // 发消息
    std::string message;
    getline(std::cin, message);
    //1
    ssize_t n = send(sockfd, message.c_str(), message.size(), 0);
    if (n == 0)
    {
        std::cout << "send message is empty!!!" << std::endl;
    }
    else if (n < 0)
    {
        std::cout << "send message fail!!!" << std::endl;
        return 2;
    }
    char buffer[1024];
    // 收消息
    //2
    n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (n == 0)
    {
        std::cout << "receive message is empty!!!" << std::endl;
    }
    else if (n < 0)
    {
        std::cout << "receive message fail!!!" << std::endl;
        return 2;
    }
    else
    {
        buffer[n] = 0; // 添加'/0'
        std::cout << buffer << std::endl;
        std::cerr << "receive message success, message len is: " << n << std::endl;
    }
}

1:

send函数用于发送数据到已连接的套接字。这里,它发送message字符串的内容。

  • sockfd是已建立的套接字描述符。
  • message.c_str()返回字符串内容的C风格字符数组。
  • message.size()返回字符串的长度(以字节为单位)。
  • 0send函数的标志参数,通常用于控制发送行为。在这里,它设置为0,表示使用默认行为。

send函数的返回值n表示实际发送的字节数。如果n小于message.size(),则可能发生了部分发送,这在非阻塞套接字或网络拥塞时可能发生。

2:

recv函数用于从已连接的套接字接收数据。

  • sockfd是已建立的套接字描述符。
  • buffer是接收数据的存储位置。
  • sizeof(buffer) - 1指定接收缓冲区的大小,减去一个字节用于存储字符串结束符。
  • 0recv函数的标志参数,用于控制接收行为。

recv函数的返回值n表示实际接收到的字节数。

6.关闭socket文件描述符

close(sockfd);

完整代码

Makefile

.PHONY:all clean
all:server client

server:tcp_server.cc
	g++ -o $@ $^ -std=c++11
client:tcp_clinet.cc
	g++ -o $@ $^ -std=c++11

clean:
	rm -f server client

tcp_server.hpp

#pragma once
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string>
#include <memory>
#include <cstring>
#include <iostream>
#define CONV(in) ((struct sockaddr *)in)
const int defaultsockfd = -1;
class TcpServer
{
public:
    TcpServer(uint16_t port, int listenfd = defaultsockfd, int sockfd = defaultsockfd)
        : _port(port), _listenfd(listenfd), _sockfd(sockfd)
    {
    }
    bool Init()
    {
        std::cout << "process pid:" << getpid() << std::endl;
        // 1.创建嵌套字
        _listenfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenfd < 0)
        {
            std::cerr << "creater listenfd fail!!!" << std::endl;
            return false;
        }
        std::cout << "creater listen socket success, listen fd:" << _listenfd << std::endl;
        // 2.填充sockaddr_in信息
        // 定义sockaddr_in结构体变量
        struct sockaddr_in local;
        // 使用memset初始化结构体
        memset(&local, 0, sizeof(local));
        // 设置地址族
        local.sin_family = AF_INET;
        // 设置端口号
        local.sin_port = htons(_port);
        // 设置IP地址
        local.sin_addr.s_addr = INADDR_ANY;

        // 3.bind
        int ret = ::bind(_listenfd, CONV(&local), sizeof(local));
        if (ret < 0)
        {
            std::cerr << "bind fail!!!" << std::endl;
            return false;
        }

        // 4.进入倾听状态
        ret = listen(_listenfd, 2); // 最多两client连接server
        if (ret < 0)
        {
            std::cerr << "listen fail!!!" << std::endl;
            return false;
        }

        // 5.获取连接
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len = sizeof(peer);
        _sockfd = accept(_listenfd, CONV(&peer), &len);
        if (_sockfd < 0)
        {
            std::cerr << "accept link fail!!!" << std::endl;
            return false;
        }
        std::cout << "accept link success , socket fd:" << _sockfd << std::endl;
        return true;
    }
    void Start()
    {
        // 收发消息
        while (true)
        {
            // 收消息
            char buffer[1024];
            ssize_t n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
            if (n > 0)
            {
                buffer[n] = 0; // 添加'/0'
                std::cout << "client syd# " << buffer << std::endl;
                // 处理信息
                // 发消息
                std::string message = "server syd# ";
                message += buffer;
                n = send(_sockfd, message.c_str(), message.size(), 0);
                if (n == 0)
                {
                    std::cerr << "send message is empty!!!" << std::endl;
                }
                else if (n < 0)
                {
                    std::cerr << "send message fail!!!" << std::endl;
                }
                else
                {
                    std::cerr << "send message success, message len is: " << message.size() << std::endl;
                }
            }
        }
    }
    ~TcpServer()
    {
        // 关闭socket
        close(_listenfd);
        close(_sockfd);
    }

private:
    int _listenfd;
    int _sockfd;
    uint16_t _port;
};

tcp_server.cc

#include "tcp_server.hpp"

void ServerUsage(const std::string& process)
{
    std::cout << "ServerUsage\n\t\t" << process << " port, example: " << process << " 8888" << std::endl;
}

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        ServerUsage(argv[0]);
        return 0;
    }
    std::unique_ptr<TcpServer> server(new TcpServer(std::stoi(argv[1])));
    if(!server->Init())
    {
        std::cerr << "Init fail" << std::endl;
    }
    server->Start();

    return 0;

}

tcp_client.cc

#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string>
#include <memory>
#include <cstring>
#include <iostream>
#define CONV(in) ((struct sockaddr *)in)
void ClinetUsage(const std::string &process)
{
    std::cout << "ServerUsage\n\t\t" << process << " ip port, example: " << process << " 127.0.0.1 8888" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        ClinetUsage(argv[0]);
        return 0;
    }
    // 1.创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    if (sockfd < 0)
    {
        std::cerr << "creater socket fail!!!" << std::endl;
    }

    std::cout << "client socket: " << sockfd << " precess pid: " << getpid() << std::endl;

    // 2.填充sockaddr_in信息
    struct sockaddr_in client;
    memset(&client, 0, sizeof(client));
    client.sin_family = AF_INET;
    client.sin_port = htons((uint16_t)std::stoi(argv[2]));
    inet_pton(AF_INET, argv[1], &(client.sin_addr));

    // 3.连接server
    int n = connect(sockfd, CONV(&client), sizeof(client));
    if (n < 0)
    {
        std::cerr << "connect server fail!!!" << std::endl;
        return 1;
    }
    std::cerr << "connect server success!!!" << std::endl;
    // 4.让操作系统会自动完成socket的绑定操作
    // 5.收发消息
    while (true)
    {
        // 发消息
        std::string message;
        getline(std::cin, message);
        ssize_t n = send(sockfd, message.c_str(), message.size(), 0);
        if (n == 0)
        {
            std::cout << "send message is empty!!!" << std::endl;
        }
        else if (n < 0)
        {
            std::cout << "send message fail!!!" << std::endl;
            return 2;
        }
        char buffer[1024];
        // 收消息
        n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (n == 0)
        {
            std::cout << "receive message is empty!!!" << std::endl;
        }
        else if (n < 0)
        {
            std::cout << "receive message fail!!!" << std::endl;
            return 2;
        }
        else
        {
            buffer[n] = 0; // 添加'/0'
            std::cout << buffer << std::endl;
            std::cerr << "receive message success, message len is: " << n << std::endl;
        }
    }
    close(sockfd);
}

代码运行结果视频展示

以udp为例子(tcp也是同样的操作即可)

环境:Linux

软件:XShell

代码编写软件:Visual Studio Code(即vscode)连接远端服务器编写代码

注意:如果想使用XShell你需要买一个云端服务器

视频链接:udp代码运行展示-CSDN直播

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

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

相关文章

Golang基础7-并发编程

并发编程 https://www.cnblogs.com/Survivalist/p/11527949.html 进程和线程、协程的区别_线程协程进程的区别-CSDN博客 Golang中的并发编程是一个重点&#xff0c;我们要了解Golang中的并发Goroutine因此需要先理解进程、线程、之后再理解协程。 进程&#xff1a;操作系统进…

论文发表英语类期刊《校园英语》如何投稿?

论文发表英语类期刊《校园英语》如何投稿&#xff1f; 《校园英语》本刊设有栏目&#xff1a;本期关注、教研探索、实践前沿、经验交流、翻译探究、语言文化等。 《校园英语》杂志是经国家新闻出版总署批准&#xff0c;创刊于2000年&#xff0c;由河北出版传媒集团主管&#…

AI文章写作网站

最强AI文章写作网站——心语流光&#xff08; Super Ai Writer &#xff09; 特点 多轮问答写作&#xff0c;自动携带历史记录进行问答可以自定义携带历史记录的轮数&#xff0c;为0则携带全部历史记录&#xff0c;有效避免token浪费&#xff08;类似coze平台&#xff09;AI生…

制作自己的YOLOv8数据集

制作自己的YOLO8数据集 前言 该数据集的格式参照于coco数据集结构✨ 步骤一&#xff1a;收集图像数据 从互联网上下载公开的数据集&#xff0c;也可以使用摄像头或其他设备自行采集图像&#xff0c;确保你的图像数据覆盖了你感兴趣的目标和场景 步骤二&#xff1a;安装Labe…

OCNet

object context pooling (OCP) 作者未提供代码

【黑马点评Redis——002商户查询缓存】

1. 商户查询缓存 2. 知识储备和课程内容 2.1 什么是缓存 缓存是数据交换的缓冲区&#xff0c;是存贮数据的临时地方&#xff0c;一般读写性能较高。 浏览器缓存应用层缓存数据库缓存CPU缓存磁盘缓存 缓存的作用&#xff1a; 降低后端负载提高读写效率&#xff0c;降低响应…

计网笔记:第1章 计算机网络概论

计网笔记&#xff1a;第1章 计算机网络概论 第1章 计算机网络概论1.1 计算机网络发展与分类1.2 OSI和TCP/IP参考模型OSI与TCP/IP参考模型图 1.3 数据封装与解封过程借助OSI模型理解数据传输过程(封装)借助OSI模型理解数据传输过程(解封) 1.4 本章例题 第1章 计算机网络概论 1.…

喜讯!和鲸科技入选 “算力中关村”—— 2024 算力技术创新与应用服务案例集

2024 年 4 月 27 日上午&#xff0c;以"技术重塑生态 算力驱动未来"为主题的"算力中关村"技术成果对接交流专场活动在北京成功举办。作为 2024 中关村论坛的重要组成部分&#xff0c;该专场活动旨在为各界人士打造一个专业化、高效化的技术成果展示与对接平…

SpringMVC 源码剖析

SpringMVC 源码剖析 0 从源码角度分析SpringMVC执行流程 // 前端控制器&#xff0c;SpringMVC最核心的类 public class DispatcherServlet extends FrameworkServlet {// 前端控制器最核心的方法&#xff0c;这个方法是负责处理请求的&#xff0c;一次请求&#xff0c;调用一次…

Redis入门到通关之数据结构解析-IntSet

文章目录 概述IntSet升级简易源码总结 欢迎来到 请回答1024 的博客 &#x1f34e;&#x1f34e;&#x1f34e;欢迎来到 请回答1024的博客 关于博主&#xff1a; 我是 请回答1024&#xff0c;一个追求数学与计算的边界、时间与空间的平衡&#xff0c;0与1的延伸的后端开发者。 …

详细谈电脑ip、域名、内网、外网、localhost、127.0.0.1、网关等通讯基础知识(易懂)

1. ip地址与域名的定义以及其关系 ip地址的定义&#xff1a; IP地址&#xff08;Internet Protocol Address&#xff09;是指互联网协议地址&#xff0c;又译为网际协议地址。 IP地址是IP协议提供的一种统一的地址格式&#xff0c;它为互联网上的每一个网络和每一台主机分配一…

【ARM 裸机】BSP 工程管理

回顾一下上一节&#xff1a;【ARM 裸机】NXP 官方 SDK 使用&#xff0c;我们发现工程文件夹里面各种文件非常凌乱&#xff1b; 那么为了模块化整理代码&#xff0c;使得同一个属性的文件存放在同一个目录里面&#xff0c;所以学习 BSP 工程管理非常有必要。 1、准备工作 新建…

短视频账号矩阵系统===4年技术源头打磨

短视频矩阵系统技术源头打磨需要从多个方面入手&#xff0c;以下是一些建议&#xff1a; 1. 基础技术研发&#xff1a;不断投入资金和人力进行基础技术研发&#xff0c;包括但不限于视频处理、人工智能、大数据等技术&#xff0c;以提高短视频矩阵系统的性能和稳定性。 2. 优化…

多进程编程:原理、技术与应用

title: 多进程编程&#xff1a;原理、技术与应用 date: 2024/4/26 12:14:47 updated: 2024/4/26 12:14:47 categories: 后端开发 tags: 多进程并发编程网络服务分布式系统任务处理进程池线程对比 第一章&#xff1a;进程与线程 进程与线程的概念及区别&#xff1a; 进程&am…

四信智能化感知与控制方案,助推灌区续建配套与现代化改造建设

“十四五”明确提到推进大中型灌区节水改造和精细化管理&#xff0c;建设节水灌溉骨干工程&#xff0c;同步推进水价综合改革。 灌区是保障国家粮食安全的重要基础性设施&#xff0c;是实施乡村振兴战略的水利支撑。灌区续建配套与现代化改造是实施乡村振兴战略一项重要任务。为…

el-tab面板添加折叠按钮方法

折叠后 <template><div class"page-type-left-wrap"><div class"page-type-left-wrap-info nav-link" :class"{ leftCollapse }"><el-tabs v-model"activeName" class"page-tabs" tab-change"han…

Git系列:Refs与Reflog

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

【经典算法】LeetCode104二叉树的最大深度(Java/C/Python3实现含注释说明,Easy)

目录 题目描述思路及实现方式一&#xff1a;递归思路代码实现Java版本C语言版本Python3版本Go语言版本 复杂度分析 方式二&#xff1a;广度优先搜索(BFS)思路代码实现Java版本C语言版本Python3版本 复杂度分析 总结相似题目 标签(题目类型)&#xff1a;树、深度优先搜索(DFS)、…

B站无限评论暴力截留协议及教程

B站无限评论暴力截留协议及教程 B站无限评论暴力截留协议及教程&#xff0c;需要抓CK &#xff0c;教程里面有讲如何抓取 网盘自动获取 链接&#xff1a;https://pan.baidu.com/s/1lpzKPim76qettahxvxtjaQ?pwd0b8x 提取码&#xff1a;0b8x

PHP 错误 Unparenthesized `a ? b : c ? d : e` is not supported

最近在一个新的服务器上测试一些老代码的时候得到了类似上面的错误&#xff1a; [Thu Apr 25 07:37:34.139768 2024] [php:error] [pid 691410] [client 192.168.1.229:57183] PHP Fatal error: Unparenthesized a ? b : c ? d : e is not supported. Use either (a ? b : …