第一次使用 cmake,做一个兼容 windows/linux 的小程序,使用 socket 模拟简单的 web 处理
为了兼容编译,遇到不少坑,终于完成了。代码现在放出来给大家参考。
https://e.coding.net/greenery2/demowebapp/demowebapp.git
使用效果截图:
bash
┌─[yhen1@devops]─[~/.vs/CDemoWebApp/out/build/linux-debug]
└──╼ $./CDemoWebApp 8888
Demo web app. port: 8888
socket init: 3
=====================================================
waiting accept [8888]...
^CExit...
closesocket: 3 ...
bash
┌─[yhen1@devops]─[~]
└──╼ $curl -vv 127.0.0.1:8888
* About to connect() to 127.0.0.1 port 8888 (#0)
* Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 8888 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 127.0.0.1:8888
> Accept: */*
>
< HTTP/1.1 200 OK
* no chunk, no close, no size. Assume close to signal end
<
* Closing connection 0
OK
CMakeLists.txt
cpp
# CMakeList.txt: CDemoWebApp 的 CMake 项目,在此处包括源代码并定义
# 项目特定的逻辑。
#
cmake_minimum_required (VERSION 3.8)
# Enable Hot Reload for MSVC compilers if supported.
if (POLICY CMP0141)
cmake_policy(SET CMP0141 NEW)
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
endif()
project ("CDemoWebApp")
# 将源代码添加到此项目的可执行文件。
add_executable (CDemoWebApp "CDemoWebApp.cpp" "CDemoWebApp.h")
if (CMAKE_VERSION VERSION_GREATER 3.12)
set_property(TARGET CDemoWebApp PROPERTY CXX_STANDARD 20)
endif()
# TODO: 如有需要,请添加测试并安装目标。
if(WIN32)
target_link_libraries(CDemoWebApp wsock32 ws2_32)
endif()
CDemoWebApp.cpp
cpp
// CDemoWebApp.cpp: 定义应用程序的入口点。
//
#include "CDemoWebApp.h"
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#ifdef WIN32
#include <winsock2.h>
#include <windows.h>
#define socklen_t int
#else
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define closesocket close
#define SOCKET int
#endif
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#define QUEUE 20 //连接请求队列
const char* DEFAULT_HTTP_RESPONE = "HTTP/1.1 200 OK\r\n\r\nOK";
using namespace std;
SOCKET sockfd, connfd; // 定义服务端套接字和客户端套接字
bool appExitFlag = false;
void SigCatch(int sigNum) //信号捕捉函数(捕获Ctrl+C)
{
printf("Exit...\n");
if (sockfd != -1)
{
appExitFlag = true;
closesocket(sockfd);
printf("closesocket: %d ...\n", sockfd);
sockfd = -1;
}
exit(0);
}
int main(int argc, char** argv)
{
char buffer[1024 * 8];
u_short port = 80;
if (argc >= 2) {
port = atoi(argv[1]);
if (port < 80) port = 80;
}
printf("Demo web app. port: %d\n", port);
signal(SIGINT, SigCatch); //注册信号捕获函数
//printf("AF_INET: %d\n", AF_INET); //IPv4协议
//printf("SOCK_STREAM: %d\n", SOCK_STREAM); //字节流套接字
#ifdef WIN32
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif
sockfd = socket(AF_INET, SOCK_STREAM, 0); //若成功则返回一个sockfd (套接字描述符)
printf("socket init: %d\n", sockfd);
struct sockaddr_in server_sockaddr; //一般是储存地址和端口,用于信息的显示及存储作用
//下面设置sockaddr_in 结构体中相关参数
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(port); //将一个无符号短整型数值转换为网络字节序,即大端模式
//printf("INADDR_ANY: %d\n", INADDR_ANY);
//INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或"所有地址"、"任意地址"。
//一般来说,在各个系统中均定义成为0值。
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); //将主机无符号长整型数转换成网络字节顺序。
if (bind(sockfd, (struct sockaddr*)&server_sockaddr, sizeof(server_sockaddr)) == -1)
{
perror("bind error!\n");
exit(1);
}
if (listen(sockfd, QUEUE) == -1)
{
perror("listen error!\n");
exit(1);
}
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
while (1) {
printf("=====================================================\n");
printf("waiting accept [%d]...\n", port);
//成功返回非负描述字,出错返回-1
int conn = accept(sockfd, (struct sockaddr*)&client_addr, &length);
//如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与所返回客户的TCP连接
//accept之后就会用新的套接字conn
if (conn < 0)
{
// 如果正在退出系统,那么就不用打印错误
if (appExitFlag) break;
perror("connect error!\n");
exit(1);
}
printf("accept ok: %d...\n", conn);
memset(buffer, 0, sizeof(buffer));
int len = recv(conn, buffer, sizeof(buffer), 0); //从TCP连接的另一端接收数据。
/*该函数的第一个参数指定接收端套接字描述符;
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数一般置0*/
printf("%s", buffer);//如果有收到数据则输出数据
// 返回默认 http respone
int ret = send(conn, DEFAULT_HTTP_RESPONE, strlen(DEFAULT_HTTP_RESPONE), 0);//向TCP连接的另一端发送数据。
printf("respone: [%d]\n%s\n", ret, DEFAULT_HTTP_RESPONE);
closesocket(conn); //因为accept函数连接成功后还会生成一个新的套接字描述符,结束后也需要关闭
printf("close connect: %d\n", conn);
}
closesocket(sockfd); //关闭socket套接字描述符
return 0;
}
注意事项
头文件不一样
windows的和linux的不一样。我用宏判断分别导入不同的头文件
cpp
#ifdef WIN32
#include <winsock2.h>
#include <windows.h>
#else
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#endif
注意,windows下面要先 include <winsock2.h> 再 include <windows.h>,否则会出现很多重复定义的错误
部分函数、类型不一样
windows的和linux的不一样。我简单统一了一下
cpp
#ifdef WIN32
#define socklen_t int
#else
#define closesocket close
#define SOCKET int
#endif
windows 下面socket要加lib
我写在 CMakeLists.txt,通过宏判断,进行lib导入
target_link_libraries(CDemoWebApp wsock32 ws2_32)
windows 下面socket要初始化
cpp
#ifdef WIN32
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif