增加宏定义开发兼容Windows和Linux的C语言TCP编程代码
1、设计思路通过宏定义屏蔽了Windows和Linux在TCP编程上的细节不同逻辑代码按照Windows的TCP编程即可宏定义可以在Linux下自动翻译成可以运行的版本。代码是C的但是注意到有关网络处理的代码都是C语言的所以理论上代码也可以写成C语言版本。测试发送了一个13字节长度的数据 “Hello world!”在Windows10和Ubuntu22.04.5上运行测试通过。如果发送任意长度的数据可以自己设计握手协议。例如先传递一个4字节的int类型长度信息然后再接收这个长度的数据。如果接收文件可以先接收文件路径字符串再接收文件长度、和文件具体内容。2、代码示例// localsend.cpp#includeiostream#includevector#includestdexcept#includecstring#ifdef_WIN32#includewinsock2.h#includews2tcpip.h#includewindows.h#pragmacomment(lib,ws2_32.lib)#else#includesys/socket.h#includearpa/inet.h#includeunistd.h#includeerrno.h#defineSOCKETint#defineINVALID_SOCKET-1#defineSOCKET_ERROR-1#defineclosesocketclose#defineaccept(sock,addr,addrlen)accept(sock,addr,(socklen_t*)(addrlen))#defineWSAStartup(a,b)0#defineWSACleanup()#defineWSAGetLastError()errnotypedefstruct{}WSADATA;#endif#defineBUFFER_SIZE4096#defineSERVER_PORT9295charTEST_TEXT[]Hello world!;voidsend_data(SOCKET sock,constchar*data,size_t length){size_t total_sent0;while(total_sentlength){ssize_t sentsend(sock,datatotal_sent,length-total_sent,0);if(sent0){throwstd::runtime_error(Failed to send data);}total_sentstatic_castsize_t(sent);}}voidrecv_data(SOCKET sock,char*data,size_t length){size_t total_received0;while(total_receivedlength){ssize_t receivedrecv(sock,datatotal_received,length-total_received,0);if(received0){throwstd::runtime_error(Failed to receive data);}if(received0){throwstd::runtime_error(Connection closed by peer while receiving data);}total_receivedstatic_castsize_t(received);}}voidserver_mode(){WSADATA wsa_data;SOCKET server_sock,client_sock;structsockaddr_inserver_addr,client_addr;intclient_lensizeof(client_addr);// Initialize Winsockif(WSAStartup(MAKEWORD(2,2),wsa_data)!0){std::cerrFailed to initialize Winsockstd::endl;return;}// Create socketserver_socksocket(AF_INET,SOCK_STREAM,0);if(server_sockINVALID_SOCKET){std::cerrFailed to create socketstd::endl;WSACleanup();return;}// Set up server addressmemset(server_addr,0,sizeof(server_addr));server_addr.sin_familyAF_INET;server_addr.sin_addr.s_addrINADDR_ANY;server_addr.sin_porthtons(SERVER_PORT);// Bind socketif(bind(server_sock,(structsockaddr*)server_addr,sizeof(server_addr))SOCKET_ERROR){std::cerrFailed to bind socketstd::endl;closesocket(server_sock);WSACleanup();return;}// Listen for connectionsif(listen(server_sock,1)SOCKET_ERROR){std::cerrFailed to listen on socketstd::endl;closesocket(server_sock);WSACleanup();return;}std::coutServer listening on port SERVER_PORTstd::endl;while(true){// Accept connectionclient_sockaccept(server_sock,(structsockaddr*)client_addr,client_len);if(client_sockINVALID_SOCKET){std::cerrAccept failedstd::endl;continue;}std::coutClient connected from inet_ntoa(client_addr.sin_addr)std::endl;// Handle clientcharbuffer[1024]{0};recv_data(client_sock,buffer,sizeof(TEST_TEXT));std::coutReceived text: bufferstd::endl;closesocket(client_sock);}closesocket(server_sock);WSACleanup();}voidclient_mode(conststd::stringserver_ip){WSADATA wsa_data;SOCKET client_sock;structsockaddr_inserver_addr;intresult;// Initialize Winsockif(WSAStartup(MAKEWORD(2,2),wsa_data)!0){std::cerrFailed to initialize Winsockstd::endl;return;}// Create socketclient_socksocket(AF_INET,SOCK_STREAM,0);if(client_sockINVALID_SOCKET){std::cerrSocket creation failedstd::endl;WSACleanup();return;}// Set up server address structurememset(server_addr,0,sizeof(server_addr));server_addr.sin_familyAF_INET;server_addr.sin_porthtons(SERVER_PORT);// Convert IP address string to binary form// Use inet_addr instead of inet_pton for better Windows compatibilityserver_addr.sin_addr.s_addrinet_addr(server_ip.c_str());if(server_addr.sin_addr.s_addrINADDR_NONE){std::cerrInvalid IP address: server_ipstd::endl;closesocket(client_sock);WSACleanup();return;}// Connect to serverstd::coutConnecting to server at server_ip:SERVER_PORTstd::endl;resultconnect(client_sock,(structsockaddr*)server_addr,sizeof(server_addr));if(resultSOCKET_ERROR){std::cerrConnection failedstd::endl;closesocket(client_sock);WSACleanup();return;}std::coutConnected to server successfullystd::endl;// Send messagestd::coutSending text: TEST_TEXTstd::endl;send_data(client_sock,TEST_TEXT,sizeof(TEST_TEXT));closesocket(client_sock);WSACleanup();}intmain(intargc,char*argv[]){if(argc1){// Server modestd::coutStarting server mode...std::endl;server_mode();}elseif(argc2){// Client modestd::string server_ipargv[1];std::coutStarting client mode...std::endl;std::coutConnecting to server at server_ipstd::endl;client_mode(server_ip);}else{std::coutUsage:std::endl;std::cout localsend.exe - Start server modestd::endl;std::cout localsend.exe IP - Send demo message to serverstd::endl;}return0;}3、运行方法软件可以运行在服务器端和客户端两种模式下先启动服务器再启动客户端客户端可以向服务器发送一段文本数据服务器接收后将接收的文本打印出来。Windows编译g localsend.cpp-olocalsend.exe-lws2_32Linux编译g localsend.cpp-olocalsend.exe启动服务器localsend.exe启动客户端localsend.exe IP如果在同一台电脑上进行测试可以将IP修改为127.0.0.1实现自己发给自己的效果。也就是localsend.exe127.0.0.1注意需要先启动服务器再启动客户端。4、实现原理// 1. 类型替换Windows的SOCKET是指针/句柄类型Linux下是int#defineSOCKETint// 2. 常量替换Windows下的无效socket和错误返回值#defineINVALID_SOCKET-1#defineSOCKET_ERROR-1// 3. 函数替换Windows用closesocketLinux用close#defineclosesocketclose// 4. 类型转化Windows的accept函数第3个参数类型是int*而在Linux中这个参数是socklen_t*在aarch64等构架下socklen_t是unsigned int所以需要将int*类型强行转换为socklen_t*#defineaccept(sock,addr,addrlen)accept(sock,addr,(socklen_t*)(addrlen))// 5. WSA机制替换Linux不需要WSAStartup和WSACleanup用宏将其“吃掉”直接返回成功判断值“0”#defineWSAStartup(a,b)0#defineWSACleanup()// 6. 结构体替换因为WSAStartup被吃掉了所以需要给WSADATA一个空定义防止编译报错typedefstruct{}WSADATA;// 7. 错误码替换本程序中未用到Windows用WSAGetLastError()Linux直接用全局变量errno#defineWSAGetLastError()errno5、应用案例本代码应用在了一个我写的局域网收发文件的工具软件localsend中本文将其中兼容Windows/Linux的TCP编程部分代码提炼总结了一下分享给大家交流学习。如果对完整项目感兴趣欢迎Star原项目https://github.com/znsoooo/localsend