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

发布于:2024-04-28 ⋅ 阅读:(23) ⋅ 点赞:(0)

目录

看以下内容前,你要先了解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直播


网站公告

今日签到

点亮在社区的每一天
去签到