虽然学了很多回了,但总是记不住。 这里直接给个可以使用的 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中没有太大区别。大致流程如下,至于分析三次握手协议,不在这里说了。
- server:socket() => bind() => listen() => accept() => read/write => close()
- client: socket() [=> bind()] => connect() => read/write => close()
以下是一个阻塞型的TCP socket程序。 要改为非阻塞型或者UDP,就需要另外学习其他的了。
socket.h
/******************************************************
* File Name: socket.h
* Author: [email protected]
* 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: [email protected]
* 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: [email protected]
* 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: [email protected]
* 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
- 起服务端
$ ./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
- 起客户端
$ ./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等其它类型(更多时候可能是二进制的)得再改改。