C++中的Socket编程

24

有没有人能够为我提供一些使用C++中套接字进行客户端和服务器连接的示例样例。

我已经阅读了一些教程,现在想实现它。

如何开始?


7
如果你真的完成了教程,那么你应该已经实现了一个(简单的)客户端-服务器应用程序。 - Anon.
1
@Anon:我是C++的新手,之前用C#做过这个项目。现在想用C++来完成它。 - Swapnil Gupta
我写了一些可能会有帮助的东西,链接在这里:https://dev59.com/NHE85IYBdhLWcg3wUBoZ#2920787 - default
5个回答

35

1
我编辑了旧链接。现在在这里:http://beej.us/guide/bgnet/html/single/bgnet.html - Alexander Kleinhans
1
我再次编辑了链接。现在链接为:https://beej.us/guide/bgnet/html/#client-server-background - arr_sea

12

C++标准中没有套接字API。POSIX C API具有较高的可移植性(GNU libc文档提供了UDP和TCP客户端和服务器的示例,当我需要快速搭建服务器时通常会参考这些示例),或者您可以使用Boost.ASIO库以获得更加C ++化的体验...


6

我使用C/C++编写了一个简单的客户端/服务器示例,没有任何额外的功能。您可以参考这个示例构建自己的用例。原始代码附在此处,并且也在github上开源!

客户端

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv) {
    struct sockaddr_in server_addr;     // set server addr and port
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    server_addr.sin_port = htons(8000);  // server default port

    int sock_client;
    char send_buf[65536];
    memset(send_buf, '\0', sizeof(send_buf));
    char *send_content = "I am client";
    strcpy(send_buf, send_content);

    if ((sock_client = socket(AF_INET,SOCK_STREAM, 0)) < 0) {
        return 0;
    }

    //connect server, return 0 with success, return -1 with error
    if (connect(sock_client, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
    {
        perror("connect");
        return 0;
    }

    char server_ip[INET_ADDRSTRLEN]="";
    inet_ntop(AF_INET, &server_addr.sin_addr, server_ip, INET_ADDRSTRLEN);
    printf("connected server(%s:%d). \n", server_ip, ntohs(server_addr.sin_port));

    //send a message to server
    send(sock_client, send_buf, strlen(send_buf), 0);
    close(sock_client);

    return 0;
}

服务器

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> // for close
#include <string.h>


int main(int argc, char *argv[]) {

    int server_sockfd;      // server socket fd 
    struct sockaddr_in server_addr;     // server info struct
    server_addr.sin_family=AF_INET;     // TCP/IP
    server_addr.sin_addr.s_addr=INADDR_ANY;     // server addr--permit all connection
    server_addr.sin_port=htons(8000);       // server port

    /*创建服务器端套接字--IPv4协议,面向连接通信,TCP协议*/
    /* create socket fd with IPv4 and TCP protocal*/
    if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0) {  
                    perror("socket error");
                    return 1;
    }

    /*将套接字绑定到服务器的网络地址上*/
    /* bind socket with server addr */
    if(bind(server_sockfd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr))<0) {
                    perror("bind error");
                    return 1;
    }


    /*监听连接请求--监听队列长度为20*/
    /* listen connection request with a queue length of 20 */
    if(listen(server_sockfd,20)<0) {
                    perror("listen error");
                    return 1;
    }
    printf("listen success.\n");

    char recv_buf[65536];
    memset(recv_buf, '\0', sizeof(recv_buf));

    while (1) {
        struct sockaddr_in client_addr;
        socklen_t length = sizeof(client_addr);
        //进程阻塞在accept上,成功返回非负描述字,出错返回-1
        // block on accept until positive fd or error
        int conn = accept(server_sockfd, (struct sockaddr*)&client_addr,&length);
        if(conn<0) {
            perror("connect");
            return -1;
        }

        printf("new client accepted.\n");

        char client_ip[INET_ADDRSTRLEN] = "";
        inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);

        while(recv(conn, recv_buf, sizeof(recv_buf), 0) > 0 ){
            printf("recv %s from client(%s:%d). \n", recv_buf, client_ip, ntohs(client_addr.sin_port));
            memset(recv_buf, '\0', strlen(recv_buf));
            break;
        }
    }

    printf("closed. \n");
    close(server_sockfd);
    return 0;
}

4

一个优秀的C++网络库是ACE。 唯一的问题是,我没有找到任何好的在线教程。 不过,这本书相当不错。


这基本上是一本书的推荐。这是否符合Stack Overflow的标准? - Trevor Lee Oakley
1
我提供了一个关于如何学习该库的建议,因为网上没有太多的教程。我回答了这个问题。但是,我可以将其更加通用化,说网上没有太多的教程,但有一些书籍可供学习该库。这样会更好吗? - zooropa
1
我并不是在批评你。我只是发表了一些评论,因为我在这个网站上看到了很多混乱的标准。我看到很多帖子因为提出建议而被打击,然后我又看到了无数的建议在这个网站上。我对建议没有任何问题,但似乎很多人在这个网站上有问题。 - Trevor Lee Oakley
也许提供一个客户端和服务器交换简单问候消息的代码片段会更合适。 - Ted Shaneyfelt

2

虽然标准的C++没有包括标准套接字对象,但是目前在g++中有一个实验性技术规范可用,使用编译器标志-std=gnu++2a。

https://en.cppreference.com/w/cpp/header/experimental/net

这个提议的扩展尚未在C++20中标准化,也不能保证它会被采纳,但它基于Boost ASIO库,你也可以在大多数平台上免费获取该库。

https://www.boost.org/doc/libs/1_76_0/doc/html/boost_asio/tutorial.html

或者你可以自己从头开始创建。

“C++ Web Server from Scratch | Part 1: Creating a Socket Object” 是一个不错的视频教程,它介绍了如何在C++中创建面向对象的UNIX风格套接字。

https://www.youtube.com/watch?v=YwHErWJIh6Y

你可以在Windows上使用winsock进行类似操作,但需要使用winsock.h而不是所有的UNIX风格头文件,并且需要添加一些Windows风格的初始化和清理。Boost为Windows和UNIX风格都提供了支持,所以我建议使用它。

如果你只是想快速进行UNIX风格的网络编程而不使用实验性库,那么这个简单的Socket.h头文件可以帮助你入门:

// Adapted from C code example 
// at https://www.geeksforgeeks.org/socket-programming-cc/
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <stdexcept>


class Socket {
        int sock;
public:
        Socket(int socket) : sock(socket) {
                if (sock<0) throw std::runtime_error("Socket creation error");
        }
        Socket() : Socket(socket(AF_INET, SOCK_STREAM, 0)) {}
        std::string rx() {
                char buffer[1024] = {0};
                int n = read( sock , buffer, sizeof(buffer));
                return std::string(buffer,n);
        }
        void tx(std::string s) {
                send(sock , s.c_str() , s.length() , 0);
        }
        int getSocket() {
                return sock;
        }
};

class Connection: public Socket {
public:
        Connection(int socket) : Socket(socket) {}
        Connection(std::string address,unsigned short port): Socket()
        {
                struct sockaddr_in serv_addr;
                serv_addr.sin_family = AF_INET;
                serv_addr.sin_port = htons(port);
                // Convert IPv4 and IPv6 addresses from text to binary form
                if(inet_pton(
                                AF_INET,
                                address.c_str(),
                                &serv_addr.sin_addr
                ) <= 0) throw std::runtime_error("Invalid address: Address not supported");

                if (connect(
                                getSocket(),
                                (struct sockaddr *)&serv_addr,
                                sizeof(serv_addr)
                ) < 0) throw std::runtime_error("\nConnection Failed \n");
        }
};

class PortListener {
        Socket server; // fd is created in default Socket constructor
        struct sockaddr_in address;
        int opt = 1;
public:
        PortListener(unsigned short port) {

                // Forcefully attaching socket to the port 8080
                if (setsockopt(
                                server.getSocket(),
                                SOL_SOCKET,
                                SO_REUSEADDR | SO_REUSEPORT,
                                &opt,
                                sizeof(opt)
                )) throw std::runtime_error("setsockopt");

                address.sin_family = AF_INET;
                address.sin_addr.s_addr = INADDR_ANY;
                address.sin_port = htons( port );

                // Forcefully attaching socket to the port 8080
                if (bind(
                                server.getSocket(),
                                (struct sockaddr *)&address,
                                sizeof(address)
                ) < 0) throw std::runtime_error("bind failed");


                if (listen(server.getSocket(), 3) < 0) {
                        throw std::runtime_error("listen");
                }
        }
        Connection waitForConnection() {
                int new_socket;
                int addrlen = sizeof(struct sockaddr_in);
                new_socket = accept(
                                server.getSocket(),
                                (struct sockaddr *)&address,
                                (socklen_t*)&addrlen
                );
                if (new_socket<0) throw std::runtime_error("accept");
                return Connection(new_socket);
        }
};

以下是示例服务器代码 server.cpp:

#include "Socket.h"
#include <iostream>

int main(int argc, char const *argv[]) {
        using namespace std;
        try {
                // Normally you'd spawn threads for multiple connections.
                Connection conn = PortListener(8080).waitForConnection();
                cout << conn.rx() << endl;
                conn.tx("Hello from server");
                cout << "Hello message sent" << endl;
        } catch (runtime_error &e) {
                cerr << e.what() << endl;
                return EXIT_FAILURE;
        }
        return 0;
}

这里是客户端示例代码client.cpp:
#include "Socket.h"
#include <iostream>

int main(int argc, char const *argv[]) {
        using namespace std;
        try {
                Connection conn("127.0.0.1",8080);
                conn.tx("Hello from client");
                cout << "Hello message sent" << endl;
                string s = conn.rx();
                cout << s << endl;
        } catch (exception &e) {
                cerr << e.what() << endl;
                return EXIT_FAILURE;
        }
    return 0;
}

使用g++进行编译和运行:

$ g++ server.cpp -o server
$ g++ client.cpp -o client
$ server &
[1] 2468
$ client
Hello message sent
Hello from client
Hello message sent
Hello from server
[1]+  Done                    ./server


这应该适用于任何类UNIX操作系统。(对于Windows,您可以安装WSL添加一个Linux操作系统并在Linux下运行)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接