当前位置 : 主页 > 编程语言 > c语言 >

一个CPP Socket C/S程序

来源:互联网 收集:自由互联 发布时间:2023-08-25
虽然学了很多回了,但总是记不住。这里直接给个可以使用的 CPP TCP socket 程序吧。使用cmake管理,但是我是个半吊子水平。在学习,例如CMake Tutorial 程序结构 PROG_HOME├── build# 可以不

虽然学了很多回了,但总是记不住。 这里直接给个可以使用的 CPP TCP socket 程序吧。 使用cmake管理,但是我是个半吊子水平。在学习,例如CMake Tutorial

程序结构

PROG_HOME
├── build			# 可以不自己创建,直接运行run.sh会给生成的。
├── CMakeLists.txt		# 
├── docs			# 好吧,我还没习惯写文档
├── include			# 实际上我并没用到include,我也不知道怎么去使用,cmake还在学习中。
├── libs			# 库文件
│   ├── CMakeLists.txt
│   └── socket			# 自己写的socket库
│       ├── CMakeLists.txt
│       ├── socket.cpp
│       └── socket.h
├── run.sh			# 这个脚本用于简化编译运行步骤
└── src
    ├── client.cpp
    └── server.cpp

CMakeLists.txt文件

在这里有3个CMakeLists.txt。至于怎么管理的搞不太清楚的,需要先学习,我也是半吊子。

项目的CMakeLists.txt源码

cmake_minimum_required(VERSION 3.5)
project(socket_test)

set(CMAKE_CXX_STANDARD 11)

# 虽然没有用到,还是给加进来了,主要是我也没有刻意区分了
include_directories(include)

# 添加库文件
add_subdirectory(libs)

# C/S可执行程序
add_executable(client ./src/client.cpp)
add_executable(server ./src/server.cpp)

# 链接socket库
target_link_libraries(client PUBLIC socket)
target_link_libraries(server PUBLIC socket)

libs下的CMakeLists.txt源码

# 只需要加上需要的库(实际上也只有这一个)
add_subdirectory(socket)

socket下的CMakeLists.txt源码

# 添加库
add_library(socket STATIC socket.cpp)
# 添加引用文件,以便于让client和server引入(但是总感觉这样用怪怪的)
target_include_directories(socket PUBLIC ".")

socket库

socket流程,在CPP和C中没有太大区别。大致流程如下,至于分析三次握手协议,不在这里说了。

  1. server:socket() => bind() => listen() => accept() => read/write => close()
  2. client: socket() [=> bind()] => connect() => read/write => close()

以下是一个阻塞型的TCP socket程序。 要改为非阻塞型或者UDP,就需要另外学习其他的了。

socket.h

/******************************************************
 * File Name:    socket.h
 * Author:       basilguo@163.com
 * Created Time: 2023-08-13 09:15:29
 * Description:  socket库头文件,定义数据结构
 ******************************************************/

#ifndef SOCKET_H
#define SOCKET_H

#include <string>

using std::string;

// 套两层,实际上在这里套不套都可以,反正是自己写的,只是为了代码隔离。
// 但是貌似无所谓了,套吧
namespace basil
{
    namespace socket
    {
        class Socket
        {
            public:
                Socket();
                Socket(int sockfd);
                ~Socket();

		// 把socket程序的基础函数都拿过来了
                bool bind(const string &ip, const int port);
                bool listen(const int backlog);
                bool connect(const string &ip, const int port);
                int accept();

                int send(const char *buf, int len);
                int recv(char *buf, int len);

                void close();

            private:
                string m_ip;
                int m_port;
                int m_sockfd;
        };
    }
}

#endif // SOCKET_H

socket.cpp

/*****************************************************
 * File Name:    socket.cpp
 * Author:       basilguo@163.com
 * Created Time: 2023-08-13 09:18:11
 * Description:  实现。应该使用日志文件,但是我没写,简单点就直接打印吧。
 ******************************************************/

#include <iostream>
#include <string>

#include <cstring>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include "socket.h"


using namespace std;

using namespace basil::socket;

/* 创建socket */
Socket::Socket() : m_ip(""), m_port(0), m_sockfd(-1)
{
    m_sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (m_sockfd < 0)
    {
        perror("socket()");
        return;
    }
    printf("socket() create successfully!\n");
}

/* 创建socket,用于server的接收 */
Socket::Socket(int sockfd) : m_ip (""), m_port(0), m_sockfd(sockfd)
{}

/* 销毁socket,就是关闭 */
Socket::~Socket()
{
    close();
}

/* 绑定 */
bool Socket::bind(const string &ip, const int port)
{
    struct sockaddr_in sockaddr;

    if (ip.empty())
    {
        sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    }
    else
    {
        sockaddr.sin_addr.s_addr = inet_addr(ip.c_str());
    }

    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(port);

    if (::bind(m_sockfd, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) < 0)
    {
        perror("bind()");
        return false;
    }

    m_ip = ip;
    m_port = port;

    printf("bind() successfully\n");

    return true;
}

/* 监听 */
bool Socket::listen(const int backlog)
{
    if (::listen(m_sockfd, backlog) < 0)
    {
        perror("listen()");
        return false;
    }

    printf("listen() at [%s:%d] successfully\n", m_ip.c_str(), m_port);

    return true;
}

/* client主动连接 */
bool Socket::connect(const string &ip, const int port)
{
    struct sockaddr_in sockaddr;

    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(port);
    sockaddr.sin_addr.s_addr = inet_addr(ip.c_str());

    if (::connect(m_sockfd, (struct sockaddr *)&sockaddr, sizeof(struct sockaddr)) < 0)
    {
        perror("connect()");
        return false;
    }

    printf("connect() successfully\n");

    m_ip = ip;
    m_port = port;

    return true;
}

/* server等待连接 */
int Socket::accept()
{
    int connfd = ::accept(m_sockfd, nullptr, nullptr);

    if (connfd < 0)
    {
        perror("accept()");
        return -1;
    }

    printf("accept() successfully\n");

    return connfd;
}

/* 发送 */
int Socket::send(const char *buf, int len)
{
    return ::send(m_sockfd, buf, len, 0);
}

/* 接收 */
int Socket::recv(char *buf, int len)
{
    return ::recv(m_sockfd, buf, len, 0);
}

/* 关闭socket */
void Socket::close()
{
    if (m_sockfd > 0)
    {
        ::close(m_sockfd);
        m_sockfd = 0;
    }
}

程序源码

客户端

/******************************************************
 * File Name:    src/client.cpp
 * Author:       basilguo@163.com
 * Created Time: 2023-08-13 02:28:10
 * Description:
 ******************************************************/

#include <string>

#include <cstring>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include "socket.h"

using namespace basil::socket;
using std::string;

int main(int argc, char *argv[])
{
    Socket client;
    bool connected = client.connect("127.0.0.1", 8008);

    if (!connected)
    {
        fprintf(stderr, "connected failed\n");
        return -1;
    }
    string data;
    char buf[1024] = {0};

    while (1)
    {
        printf("[C->S]: ");
        scanf("%s", buf);
        data = buf;
        client.send(data.c_str(), data.size());

        if (strncmp(buf, "quit", 4) == 0)
        {
            printf("byebye~\n");
            break;
        }

        client.recv(buf, 1024);
        printf("[S->C]: %s\n", buf);
   }

    return 0;
}

服务端

/******************************************************
 * File Name:    server.cpp
 * Author:       basilguo@163.com
 * Created Time: 2022-10-09 07:34:59
 * Description:  使用CPP风格的socket,不要再使用C风格的socket。
 ******************************************************/

#include <cstring>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include "socket.h"

using namespace basil::socket;
using std::string;

int main()
{
    Socket server;

    string ip = "127.0.0.1";
    int port = 8008;
    server.bind(ip, port);

    server.listen(1024);

    while (true)
    {
        int connfd = server.accept();
        if (connfd < 0)
            break;
        Socket client(connfd);

        while(true)
        {
            char buf[1024] = {0};
            size_t len = client.recv(buf, sizeof(buf));
            printf("[C->S]: %s\n", buf);

            if (strncmp(buf, "quit", 4) == 0)
            {
                bzero(buf, 1024);
                len = strlen("byebye~");
                snprintf(buf, len, "byebye~");
                client.send(buf, len);
                printf("[client: %d] byebye~\n", connfd);
                break;
            }

            client.send(buf, len);
        }
        client.close();
    }

    server.close();
}

运行测试

#!/bin/bash

mkdir -p build

rm -rf build/*
cd build
cmake ..
make

if [ 0 -ne $# ]
then
    ./$@
fi
  1. 起服务端
$ ./run.sh server
-- The C compiler identification is GNU 11.4.0
-- The CXX compiler identification is GNU 11.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/basil/code/cpp/server_dev/sockets/build
[ 16%] Building CXX object libs/socket/CMakeFiles/socket.dir/socket.cpp.o
[ 33%] Linking CXX static library libsocket.a
[ 33%] Built target socket
[ 50%] Building CXX object CMakeFiles/client.dir/src/client.cpp.o
[ 66%] Linking CXX executable client
[ 66%] Built target client
[ 83%] Building CXX object CMakeFiles/server.dir/src/server.cpp.o
[100%] Linking CXX executable server
[100%] Built target server
socket() create successfully!
bind() successfully
listen() at [127.0.0.1:8008] successfully

  1. 起客户端
$ ./run.sh client
-- The C compiler identification is GNU 11.4.0
-- The CXX compiler identification is GNU 11.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/basil/code/cpp/server_dev/sockets/build
[ 16%] Building CXX object libs/socket/CMakeFiles/socket.dir/socket.cpp.o
[ 33%] Linking CXX static library libsocket.a
[ 33%] Built target socket
[ 50%] Building CXX object CMakeFiles/client.dir/src/client.cpp.o
[ 66%] Linking CXX executable client
[ 66%] Built target client
[ 83%] Building CXX object CMakeFiles/server.dir/src/server.cpp.o
[100%] Linking CXX executable server
[100%] Built target server
socket() create successfully!
connect() successfully
[C->S]: 

至于测试,就是在client输入,server输出以及返回。当client输入为quit时,就会退出client端,server端也会断开连接。如果不想自己写client,或者只是简单测试,其实可以使用nc 127.0.0.1 8008命令来测试,不过需要安装下ncat。

不过这里只能输入一些字符串,而不能输入int等其它类型(更多时候可能是二进制的)得再改改。

参考

  1. 高级篇---网络编程基础
上一篇:Programming abstractions in C阅读笔记:p107-p110
下一篇:没有了
网友评论