c++tcp服务端客户端

服务端代码

#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<iostream>
#include<string>
#include<vector>

using namespace std;


int main() {

    // https://zhuanlan.zhihu.com/p/360042334

    // 1. 创建一个监听socket
    // PF_LOCAL 本地地址; PF_INET: ipv4; PF_INET6: ipv6 -- 同样也有AF_LOCAL ...
    // SOCK_STREAM 字节流,对应tcp; SOCK_DGRAM 数据包,对应udp; SOCK_RAW 原始套接字
    // protocol 协议, 指定通信协议的,但现在基本废弃。因为协议已经通过前面两个参数指定完成
    int socket_fd = socket(PF_INET, SOCK_STREAM, 0);
    if (socket_fd == -1) {
        cout << "create socket failed!" << endl;
        return -1;
    }
    cout << "create socket success!" << endl;


    // 2. 初始化服务器地址
    struct sockaddr_in local_addr{};
    // ipv4
    local_addr.sin_family = AF_INET;
    // 指定端口, 注意必须是 htonl(9999), 不能仅指定为 9999
    local_addr.sin_port = htonl(9999);
    // 通配地址 INADDR_ANY 表示通配地址,
    // 如果你只想该服务器程序仅本机上可以访问,那么你 bind 函数中的地址就可以使用127.0.0.1;
    // 如果你的服务只想被局域网内部机器访问,bind 函数的地址可以使用192.168.1.104;
    // 如果 希望这个服务可以被公网访问,你就可以使用地址0.0.0.0或 INADDR_ANY
    local_addr.sin_addr.s_addr = htonl (INADDR_ANY);
    // 如果 using namespace std; 并且使用 bind(而不是 ::bind) 会报错 Invalid operands to binary expression ('int' and '__bind<int &, sockaddr *, unsigned long>')
    // 因为使用的是 std::bind, 可以改为 ::bind, 避免这个问题 https://blog.csdn.net/YoungHong1992/article/details/122567619
    if (-1 == ::bind(socket_fd, (struct sockaddr *) &local_addr, sizeof(local_addr))) {
        cout << "bind port failed!" << endl;
        return -1;
    }
    cout << "bind port success!" << endl;

    // 3. 启动监听
    // 第一个参数 socketdf 为套接字描述符,
    // 第二个参数 backlog,在 Linux 中表示已完成 (ESTABLISHED) 且未 accept 的队列大小,(全连接队列的大小)这个参数的大小决定了可以接收的并发数目。
    // 这个参数越大,并发数目理论上也会越大。但是参数过大也会占用过多的系统资源
    // SOMAXCONN 表示监听队列数默认为128
    if (listen(socket_fd, SOMAXCONN) == -1) {
        cout << "listen failed!" << endl;
        return -1;
    }
    cout << "listen success!" << endl;

    //记录所有客户端连接的容器
    std::vector<int> client_fds;
    while (true) {
        struct sockaddr_in client_addr{};
        socklen_t client_addr_len = sizeof(client_addr);
        //接受客户端的连接
        int client_fd = accept(socket_fd, (struct sockaddr *) &client_addr, &client_addr_len);
        if (client_fd != -1) {
            // 接收客户端数据缓存
            char receive_buf[1024] = {0};
            // 从客户端接收数据
            long ret = recv(client_fd, receive_buf, 1024, 0);
            if (ret != -1) {
                cout << "receive client data: " << receive_buf << endl;
                // 原样返回, 加一个
                char echo_data[] = "echo:";
                strcat(echo_data, receive_buf);
                ret = send(client_fd, echo_data, strlen(echo_data), 0);
                if (ret != strlen(echo_data)) {
                    cout << "send data error!" << endl;
                } else {
                    cout << "send data success!" << endl;
                }
            } else {
                cout << "receive data failed!" << endl;
            }
            // 关闭客户端连接
            close(client_fd);
        } else {
            cout << "accept client error: " << client_fd << endl;
            break;
        }
    }

    // 关闭服务端监听fd
    close(socket_fd);
    return 0;
}

客户端代码

#include <gtest/gtest.h>
#include <iostream>
#include <string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>

#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT    9999
#define SEND_DATA      "hello world!"
#define RECEIVE_BUF_SIZE  32

TEST(TcpTest, Client) {
    //1. 创建一个socket
    int client_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_fd == -1) {
        std::cout << "create client fd error. " << std::endl;
        return;
    }
    std::cout << "create client fd success!" << std::endl;

    //2. 连接服务器
    struct sockaddr_in server_addr{};
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
    server_addr.sin_port = htons(SERVER_PORT);
    // 第一个参数 sock_fd 是连接套接字
    // 第二个、第三个参数 server_addr 和 addr_len 分别代表指向套接字地址结构的指针和该结构的大小。
    // 套接字地址结构必须含有服务器的 IP 地址和端口号
    if (connect(client_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) == -1) {
        std::cout << "connect socket error" << std::endl;
        return;
    }
    std::cout << "connect socket success!" << std::endl;

    //3. 向服务器端发送数据
    long ret = send(client_fd, SEND_DATA, strlen(SEND_DATA), 0);
    if (ret != strlen(SEND_DATA)) {
        std::cout << "send data error." << std::endl;
        return;
    }
    std::cout << "send data successfully, data :" << SEND_DATA << std::endl;


    //4. 客户端收数据
    char receive_buf[RECEIVE_BUF_SIZE] = {0};
    ret = recv(client_fd, receive_buf, RECEIVE_BUF_SIZE, 0);
    if (ret > 0) {
        std::cout << "receive data successfully, data: " << receive_buf << std::endl;
    } else {
        std::cout << "receive data error, data: " << receive_buf << std::endl;

    }
    //5. 关闭socket
    close(client_fd);
}

问题

服务端启动后,端口联不上

服务端启动后, telnet 0 9999 连不上: telnet: Unable to connect to remote host 。

检查发现, 服务端代码33行写成了 local_addr.sin_port = 9999; ,改为 local_addr.sin_port = htons(9999); 就可以了。

实际上使用 = 9999 启动的是别的端口, 可以使用 netstat -nlpt | grep 检查 (mac上可以用 lsof -n -P | grep xxx) 检查。

发现是 TCP *:3879 (LISTEN)

相关链接

  1. linux C/C++ TCP网络通信实战
  2. Socket bind() error: invalid operands to binary expression