@TOC
第一章 Socket快速入门篇
1、TCP/IP模型
用Wireshark抓包工具来看一下上图TCP/IP模型这种4层协议里面究竟有什么内容。
在windows和Linux系统之间配置共享
首先保证我们的putty已经连接上了linux服务器,然后我们要安装samba这么一个目录共享工具:
apt-get install samba
where samba
vim /etc/samba/smb.conf
移动该配置项最后,添加内容:
[code]#这就表示你这个对外部显示的共享目录的名字path=/code#我们在根目录下面创建的code目录writeable=yesbrowseable=yesguest ok = yes
pkill smbd //如果当前smbda已经启动了,先把它杀掉
smbd //重新启动
ps -ef //这只是当前控制端的进程列表
ps -ef | grep smbd
竖线表示一个管道,就是把前面命令的输出传给后面,后面再做处理、过滤输出
mkdir /code
chmod 777 /code //这是偷懒的办法,更保险的是我们看一下目录当前的权限
ls -l | grep code //这会把每一行的输出进行过滤,匹配之后才输出
第一个root和第二个root表示这个目录文件所属的用户和所属的组,最左边是权限部分:
d表示它是一个目录,没有的话可能是文件,字母l表示链接;
d后面有9个字母,每3个字母表示一组权限,第一组3个字母rwx
表示拥有者用户(owner)的权限,就是这个文件是由谁来创建的,像上图这里显示的是root用户创建的;
中间3个字母r-x
表示同组用户(group)的权限;
r-x
最后3个字母表示其他人(other)的权限。
我们的目录共享是以一个默认用户进来的,所以说它应该是其他用户,所以我们看到,这个code只能被其他用户读和执行,但是不可以写,而我们是要在windows上编辑linux上的文件,那它就必须要具有写的权限。
我们有两个方案,一个方案就是给这个目录设置3组权限都为rwx:
chmod 777 code
还有一个方案就是给这个目录指定用户可以访问:
我们设定这个用户和这个组可以访问这个目录。
我们先把这个共享目录code删除,再看看从windows上访问该目录的效果:
我们按键盘上的窗口键+r打开运行窗口,输入\\192.168.3.69
打开linux服务器共享的目录:
这就说明我们访问该目录没有权限。
这样就可以在windows上编辑文件、创建目录了,还可以通过svn或git提交,都可以在windows上执行了。
系统socket库介绍
2、windows上加载socket库、并创建socket
//main.cpp//#include int main(int argc, char* argv[]){WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);return 0;}
设置项目属性,链接器、输入,ws2_32.lib;
编译成功!
我们调用socket函数:
//main.cpp//#include #include int main(int argc, char* argv[]){WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);for(int i = 0; i < 1000; i++){int sock = socket(AF_INET, SOCK_STREAM, 0);if(sock == -1){printf("create socket failed! sock = %d\n", sock);return -1;}printf("sock = [%d]\n", sock);closesocket(sock);}getchar();return 0;}
第一个参数,是用什么协议,AF_INET,就是TCP/IP协议;
第二个参数,传输层用udp还是tcp,这里我们用tcp的;
返回值,socket句柄的值。
3、移植到Linux并设置单进程创建socket的最大数量
//main.cpp//#ifdef _WIN32#include #endif#include int main(int argc, char* argv[]){//在linux当中socket不需要初始化#ifdef _WIN32WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);#endiffor(int i = 0; i < 1000; i++){int sock = socket(AF_INET, SOCK_STREAM, 0);if(sock == -1){printf("create socket failed! sock = %d\n", sock);return -1;}printf("sock = [%d]\n", sock);closesocket(sock);}getchar();return 0;}
make main
编译如果报错,根据信息可以判断是由于没有添加响应头文件导致的,那么socket相关的头文件是什么呢,我们通过man手册来查找。
man socket
//main.cpp//#ifdef _WIN32#include #else#include #include #endif#include int main(int argc, char* argv[]){//在linux当中socket不需要初始化#ifdef _WIN32WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);#endiffor(int i = 0; i < 2000; i++){int sock = socket(AF_INET, SOCK_STREAM, 0);if(sock == -1){printf("create socket failed! sock = %d\n", sock);return -1;}printf("sock = [%d]\n", sock);//closesocket(sock);}getchar();return 0;}
make main
默认会执行编译命令:g++ main.cpp -o main
可以看到在linux下,当它创建到1023的时候,再创建socket就失败了,而且我们可以看到在linux下socket是从3开始的,0、1、2是默认已经用掉的,它们分别是标准输入、标准输出、标准错误;
在linux当中,它对单个进程是文件句柄的最大数限制的,我们怎么看这个文件句柄的最大数呢?
ulimit -n
所以说,我们可以把这个值改掉,也可以把这个限制关闭掉就行了:
ulimit -n 3000
那么怎么保存这个值使之一直生效呢,我们可以把它放在启动项;
或者写一个启动脚本,在这个启动脚本当中把这个指令加进去;或者通过代码来实现。
另外,由于在windows和linux下,关闭socket的函数是不一样的:
所以我们修改代码:
//main.cpp//#ifdef _WIN32#include #else#include #include #include #define closesocket close#endif#include int main(int argc, char* argv[]){//在linux当中socket不需要初始化#ifdef _WIN32WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);#endiffor(int i = 0; i < 2000; i++){int sock = socket(AF_INET, SOCK_STREAM, 0);if(sock == -1){printf("create socket failed! sock = %d\n", sock);return -1;}printf("sock = [%d]\n", sock);//closesocket(sock);}getchar();return 0;}
第二章 TCP服务器
TPC协议
- TCP协议
网络字节序
创建tcp服务端
- int accept(int sockfd, void *addr, int *addrlen);
//main.cpp//#include #include #include #ifdef _WIN32#include #else#include #include #include #include #define closesocket close#endifint main(int argc, char* argv[]){//在linux当中socket不需要初始化#ifdef _WIN32WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);#endifint sock = socket(AF_INET, SOCK_STREAM, 0);if(sock == -1){printf("create socket failed! sock = %d\n", sock);return -1;}printf("sock = [%d]\n", sock);unsigned short port = 8080;if(argc > 1)port = atoi(argv[1]);sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(port);//本地字节序转成网络字节序、主机字节序转成网络字节序saddr.sin_addr.s_addr = htonl(0);//任意的IP地址发过来的数据我们都接收if(bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0){printf("bind port %d failed! \n", port);return -2;}printf("bind port %d success!\n", port);listen(sock, 10);sockaddr_in cliAddr;socklen_t socklen = sizeof(cliAddr);//这个sock是专门用来建立连接的//accept函数返回一个客户端的socketint client = accept(sock, (sockaddr*)&cliAddr, &socklen);printf("accept client %d\n", client);char* ip = inet_ntoa(cliAddr.sin_addr);unsigned short cliPort = ntohs(cliAddr.sin_port);//网络字节序转成本地字节序printf("client ip is %s, port is %d\n", ip, cliPort);char buf[1024] = {0};for(;;){int recvLen = recv(client, buf, sizeof(buf) - 1, 0);if(recvLen <= 0)break;//赋一个\0buf[recvLen] = '\0';if(strstr(buf, "quit") != nullptr){char re[] = "quit success!\n";send(client, re, strlen(re) + 1, 0);break;}int sendLen = send(client, "ok\n", 4, 0);printf("recv %s\n", buf);}closesocket(client);//closesocket(sock);getchar();return 0;}
TCP的3次握手是由系统内部已经做完了,我们accept函数只是读取了它3次握手后的信息,所以说是在我们绑定bind、进行lisent之后,对方客户端用连接请求的时候,它就已经完成了3次握手。
//我们现在增加一个功能,每来一个客户端,我们就把它加到一个新的线程当中,这样保证我能同时并发处理多个客户端的数据,
//我们采用C++11的线程
//main.cpp//#include #include #include #ifdef _WIN32#include #else#include #include #include #include #define closesocket close#endif#include using namespace std;class TcpThread{public:void Main(){char buf[1024] = {0};for(;;){int recvLen = recv(client, buf, sizeof(buf) - 1, 0);if(recvLen <= 0)break;//赋一个\0buf[recvLen] = '\0';if(strstr(buf, "quit") != nullptr){char re[] = "quit success!\n";send(client, re, strlen(re) + 1, 0);break;}int sendLen = send(client, "ok\n", 4, 0);printf("recv %s\n", buf);}closesocket(client);}int client = 0;};int main(int argc, char* argv[]){//在linux当中socket不需要初始化#ifdef _WIN32WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);#endifint sock = socket(AF_INET, SOCK_STREAM, 0);if(sock == -1){printf("create socket failed! sock = %d\n", sock);return -1;}printf("sock = [%d]\n", sock);unsigned short port = 8080;if(argc > 1)port = atoi(argv[1]);sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(port);//本地字节序转成网络字节序、主机字节序转成网络字节序saddr.sin_addr.s_addr = htonl(0);//任意的IP地址发过来的数据我们都接收if(bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0){printf("bind port %d failed! \n", port);return -2;}printf("bind port %d success!\n", port);listen(sock, 10);for(;;){sockaddr_in cliAddr;socklen_t socklen = sizeof(cliAddr);//这个sock是专门用来建立连接的//accept函数返回一个客户端的socketint client = accept(sock, (sockaddr*)&cliAddr, &socklen);if(client <= 0)break;printf("accept client %d\n", client);char* ip = inet_ntoa(cliAddr.sin_addr);unsigned short cliPort = ntohs(cliAddr.sin_port);//网络字节序转成本地字节序printf("client ip is %s, port is %d\n", ip, cliPort);TcpThread *th = new TcpThread();th->client = client;thread subTh(&TcpThread::Main, th);//释放我的主线程拥有的子线程的资源sth.detach();}closesocket(client);getchar();return 0;}
编译后报错,提示没有thread,并且你没有用c++11来进行编译,make只是一个简单的自动推导,它不会把这些参数加进去。
重写一个makefile:
tcpserver:main.cppg++ main.cpp -o tcpserver -std=c++11
编译报错,它是没找到pthread_create的定义,但是我们其实没调用这个函数,因为c++11它封装的这个thread里面调用了系统的pthread库,因为它针对windows和linux调用了不同的库,所以我们这里要把pthread库给它引用进来,这个库系统都是默认自带的。
我们再改下makefile:
tcpserver:main.cppg++ main.cpp -o tcpserver -std=c++11 -lpthread
这样就可以同时处理多路数据,我们就做了并发处理。
接下来我们在windows下编译该代码,正常情况下肯定会有一些错误,毕竟和linux有所区别。
//main.cpp//#include #include #include #ifdef _WIN32#include #define socklen_t int#else#include #include #include #include #define closesocket close#endif#include using namespace std;class TcpThread{public:void Main(){char buf[1024] = {0};for(;;){int recvLen = recv(client, buf, sizeof(buf) - 1, 0);if(recvLen <= 0)break;//赋一个\0buf[recvLen] = '\0';if(strstr(buf, "quit") != nullptr){char re[] = "quit success!\n";send(client, re, strlen(re) + 1, 0);break;}int sendLen = send(client, "ok\n", 4, 0);printf("recv %s\n", buf);}closesocket(client);}int client = 0;};int main(int argc, char* argv[]){//在linux当中socket不需要初始化#ifdef _WIN32WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);#endifint sock = socket(AF_INET, SOCK_STREAM, 0);if(sock == -1){printf("create socket failed! sock = %d\n", sock);return -1;}printf("sock = [%d]\n", sock);unsigned short port = 8080;if(argc > 1)port = atoi(argv[1]);sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(port);//本地字节序转成网络字节序、主机字节序转成网络字节序saddr.sin_addr.s_addr = htonl(0);//任意的IP地址发过来的数据我们都接收//为了不和c++11的bind冲突,这里我们用全局的bindif(::bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0){printf("bind port %d failed! \n", port);return -2;}printf("bind port %d success!\n", port);listen(sock, 10);for(;;){sockaddr_in cliAddr;socklen_t socklen = sizeof(cliAddr);//这个sock是专门用来建立连接的//accept函数返回一个客户端的socketint client = accept(sock, (sockaddr*)&cliAddr, &socklen);if(client <= 0)break;printf("accept client %d\n", client);char* ip = inet_ntoa(cliAddr.sin_addr);unsigned short cliPort = ntohs(cliAddr.sin_port);//网络字节序转成本地字节序printf("client ip is %s, port is %d\n", ip, cliPort);TcpThread *th = new TcpThread();th->client = client;thread subTh(&TcpThread::Main, th);//释放我的主线程拥有的子线程的资源sth.detach();}closesocket(client);getchar();return 0;}
第三章 TCP类封装
添加一个类,XTcp,勾选虚析构函数,确定。
大家记得一点,你这个头文件的引用尽量要放在.cpp当中,为什么要放在.cpp当中,而不放在.h当中呢?
你.h如果是被第三方引用,对于调用者来说他只要知道.h就行了,他不需要知道.cpp,你甚至可以把.cpp变成动态链接库,但是.h你是需要给他的,如果你.h当中引用了一些头文件比如windows.h,那就有可能给调用者造成冲突影响,就像std命名空间里面有很多库会与很多库产生冲突,包括windows.h也是一样,它涉及到引用次序,也会对很多函数产生影响;
所以说我们最保险的做法,像windows.h一定不要放在.h当中,放在.cpp当中这样就不会对别人产生影响;产生影响只局限于你本.cpp内部,你可以内部就把它调整过来,当出现一种交错影响的时候,那代码是相当的难调试的。
//XTcp.h//#pragma once#include class XTcp{public:int CreateSocket();//绑定端口和listenbool Bind(unsigned short port);XTcp Accept();void Close();int Recv(char *buf, int bufsize);int Send(const char *buf, int sendsize);XTcp();virtual ~XTcp();int sock = 0;unsigned short port = 0;std::string ip;};
//XTcp.cpp//#include "XTcp.h"#include #include #include #ifdef _WIN32#include #define socklen_t int#else#include #include #include #include #define closesocket close#endif#include using namespace std;XTcp::XTcp(){#ifdef _WIN32static bool first = true;if(first){first = false;WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);}#endif}int XTcp::CreateSocket(){if(sock <= 0){sock = socket(AF_INET, SOCK_STREAM, 0);if(sock == -1){printf("create socket failed! sock = %d\n", sock);}}return sock;}bool XTcp::Bind(unsigned short port){if(sock <= 0){CreateSocket();}sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(port);//本地字节序转成网络字节序、主机字节序转成网络字节序saddr.sin_addr.s_addr = htonl(0);//任意的IP地址发过来的数据我们都接收//为了不和c++11的bind冲突,这里我们用全局的bindif(::bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0){printf("bind port %d failed! \n", port);return false;}printf("bind port %d success!\n", port);listen(sock, 10);return true;}XTcp XTcp::Accept(){XTcp tcp;sockaddr_in cliAddr;socklen_t socklen = sizeof(cliAddr);//这个sock是专门用来建立连接的//accept函数返回一个客户端的socketint client = accept(sock, (sockaddr*)&cliAddr, &socklen);if(client <= 0)return tcp;printf("accept client %d\n", client);tcp.sock = client;tcp.ip = inet_ntoa(cliAddr.sin_addr);tcp.port = ntohs(cliAddr.sin_port);//网络字节序转成本地字节序printf("client ip is %s, port is %d\n", tcp.ip.c_str(), tcp.port);return tcp;}int XTcp::Recv(char *buf, int bufsize){return recv(sock, buf, bufsize, 0);}int XTcp::Send(const char *buf, int size){int sendedSize = 0;while(sendedSize != size){int len = send(sock, buf + sendedSize, sendsize - sendedSize, 0);if(len <= 0)break;sendedSize += len;}return sendedSize;}void XTcp::Close(){if(sock <= 0) return;closesocket(sock);}XTcp::~XTcp(){}
//main.cpp//#include #include "XTcp.h"#include #include class TcpThread{public:void Main(){char buf[1024] = {0};for(;;){int recvLen = client.Recv(buf, sizeof(buf) - 1);if(recvLen <= 0)break;//赋一个\0buf[recvLen] = '\0';if(strstr(buf, "quit") != nullptr){char re[] = "quit success!\n";client.Send(re, strlen(re) + 1);break;}int sendLen = client.Send("ok\n", 4);printf("recv %s\n", buf);}client.Close();}XTcp client;};int main(int argc, char* argv[]){unsigned short port = 8080;if(argc > 1){port = atoi(argv[1]);}XTcp server;server.CreateSocket();server.Bind(port);for(;;){XTcp client = server.Accept();TcpThread *th = new TcpThread();th->client = client;std::thread subTh(&TcpThread::Main, th);//释放我的主线程拥有的子线程的资源sth.detach();}server.Close();getchar();return 0;}
在windows下编译运行测试成功!
在linux下编译报错:
错误提示Recv、Send函数和Close函数没有定义,那是因为我们的makefile里面没有包含XTcp.cpp这个文件,我们把makefile修改一下:
tcpserver:main.cpp XTcp.cpp XTcp.hg++ main.cpp XTcp.cpp -o tcpserver -std=c++11 -lpthread
创建XTcp动态dll链接库项目并测试
我们把前面做的XTcp这个类放到一个动态链接库当中,分别在windows和linux当中都来实现这个动态链接库。
我们新建一个Win32 控制台应用程序,XSocket,应用程序类型选择DLL,附加选项选择导出符号,导出符号的好处项目当中有一些宏已经帮你定义好了,省得你定义了。
把前面做的XTcp.h和XTcp.cpp拷贝到当前项目目录中,并添加到当前项目中。
//XTcp.h//#pragma once#include #ifdef XSOCKET_EXPORTS#define XSOCKET_API __declspec(dllexport)#else#define XSOCKET_API __declspec(dllimport)#endifclass XSOCKET_EXPORTS XTcp{public:int CreateSocket();//绑定端口和listenbool Bind(unsigned short port);XTcp Accept();void Close();int Recv(char *buf, int bufsize);int Send(const char *buf, int sendsize);XTcp();virtual ~XTcp();int sock = 0;unsigned short port = 0;char ip[16];};
记得关闭预编译头。
//XTcp.cpp//#include "XTcp.h"#include #include #include #ifdef _WIN32#include #define socklen_t int#else#include #include #include #include #define closesocket close#endif#include using namespace std;XTcp::XTcp(){#ifdef _WIN32static bool first = true;if(first){first = false;WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);}#endif}int XTcp::CreateSocket(){if(sock <= 0){sock = socket(AF_INET, SOCK_STREAM, 0);if(sock == -1){printf("create socket failed! sock = %d\n", sock);}}return sock;}bool XTcp::Bind(unsigned short port){if(sock <= 0){CreateSocket();}sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(port);//本地字节序转成网络字节序、主机字节序转成网络字节序saddr.sin_addr.s_addr = htonl(0);//任意的IP地址发过来的数据我们都接收//为了不和c++11的bind冲突,这里我们用全局的bindif(::bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0){printf("bind port %d failed! \n", port);return false;}printf("bind port %d success!\n", port);listen(sock, 10);return true;}XTcp XTcp::Accept(){XTcp tcp;sockaddr_in cliAddr;socklen_t socklen = sizeof(cliAddr);//这个sock是专门用来建立连接的//accept函数返回一个客户端的socketint client = accept(sock, (sockaddr*)&cliAddr, &socklen);if(client <= 0)return tcp;printf("accept client %d\n", client);tcp.sock = client;char *ip = inet_ntoa(cliAddr.sin_addr);strcpy(tcp.ip, ip);tcp.port = ntohs(cliAddr.sin_port);//网络字节序转成本地字节序printf("client ip is %s, port is %d\n", tcp.ip, tcp.port);return tcp;}int XTcp::Recv(char *buf, int bufsize){return recv(sock, buf, bufsize, 0);}int XTcp::Send(const char *buf, int size){int sendedSize = 0;while(sendedSize != size){int len = send(sock, buf + sendedSize, sendsize - sendedSize, 0);if(len <= 0)break;sendedSize += len;}return sendedSize;}void XTcp::Close(){if(sock <= 0) return;closesocket(sock);}XTcp::~XTcp(){}
我们参考开源项目,调整一下代码的结构,创建几个目录:bin、src、lib、doc。
- 打开项目属性、配置属性、常规,把项目的输出目录修改为
..\..\bin\
- 链接器、高级,我们修改导入库,把生成的lib文件放到
..\..\lib\$(TargetName).lib
- 配置属性、调试,修改工作目录为
..\..\bin\
,调试中的这个工作目录是指你这个执行程序在哪个路径下执行,因为你的dll在bin目录中,所以你这个调试路径不设置为bin目录的话,你就无法通过测试程序调试这个dll。
测试
右击解决方案,添加新项目,tcpserver,应用程序类型选择控制台应用程序,空项目。
打开解决方案属性,设置启动项目为tcpserver,项目依赖项为tcpserver依赖于xsocket,也就是当tcpserver要运行的时候,必须保证xsocket已经编译过了。
给tcpserver项目添加server.cpp,我们还要引用头文件XTcp.h和对应的链接库:
- 打开tcpserver属性,C/C++,设置附加包含目录为
..\xsocket
- 链接器,常规,设置附加库目录为
..\..\lib
- 链接器,输入,给附加依赖项添加
xsocket.lib
//server.cpp//#include "XTcp.h"int main(int argc, char *argv[]){unsigned short port = 8080;if(argc > 1){port = atoi(argv[1]);}XTcp server;server.Bind(port);XTcp client = server.Accept();server.Close();getchar();return 0;}
我们先写一个简单的,保证输出测试成功。
打开项目的配置属性,常规,设置输出目录为..\..\bin\
配置属性,调试,设置工作目录为..\..\bin\
测试成功后,我们再把之前代码中的线程那部分代码拷贝过来:
//server.cpp//#include #include "XTcp.h"#include #include class TcpThread{public:void Main(){char buf[1024] = {0};for(;;){int recvLen = client.Recv(buf, sizeof(buf) - 1);if(recvLen <= 0)break;//赋一个\0buf[recvLen] = '\0';if(strstr(buf, "quit") != nullptr){char re[] = "quit success!\n";client.Send(re, strlen(re) + 1);break;}int sendLen = client.Send("ok\n", 4);printf("recv %s\n", buf);}client.Close();}XTcp client;};int main(int argc, char* argv[]){unsigned short port = 8080;if(argc > 1){port = atoi(argv[1]);}XTcp server;server.CreateSocket();server.Bind(port);for(;;){XTcp client = server.Accept();TcpThread *th = new TcpThread();th->client = client;std::thread subTh(&TcpThread::Main, th);//释放我的主线程拥有的子线程的资源sth.detach();}server.Close();getchar();return 0;}
创建XTcp动态so链接库项目(Linux)并测试
在xsocket项目目录中,新建并编写makefile文件:
libxsocket.so:XTcp.cpp XTcp.hg++ $+ -o $@ -fpic -shared -std=c++11
编译出错,主要是在linux当中不认识windows中的几个宏,我们修改动态链接库的代码:
//XTcp.h//#ifndef _XTCP_H_#define _XTCP_H_#include #ifdef _WIN32#ifdef XSOCKET_EXPORTS#define XSOCKET_API __declspec(dllexport)#else#define XSOCKET_API __declspec(dllimport)#endif#else#define XSOCKET_API#endifclass XSOCKET_EXPORTS XTcp{public:int CreateSocket();//绑定端口和listenbool Bind(unsigned short port);XTcp Accept();void Close();int Recv(char *buf, int bufsize);int Send(const char *buf, int sendsize);XTcp();virtual ~XTcp();int sock = 0;unsigned short port = 0;char ip[16];};#endif
//XTcp.cpp//#include "XTcp.h"#include #include #include #ifdef _WIN32#include #define socklen_t int#else#include #include #include #include #define closesocket close#endif#include using namespace std;XTcp::XTcp(){#ifdef _WIN32static bool first = true;if(first){first = false;WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);}#endif}int XTcp::CreateSocket(){if(sock <= 0){sock = socket(AF_INET, SOCK_STREAM, 0);if(sock == -1){printf("create socket failed! sock = %d\n", sock);}}return sock;}bool XTcp::Bind(unsigned short port){if(sock <= 0){CreateSocket();}sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(port);//本地字节序转成网络字节序、主机字节序转成网络字节序saddr.sin_addr.s_addr = htonl(0);//任意的IP地址发过来的数据我们都接收//为了不和c++11的bind冲突,这里我们用全局的bindif(::bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0){printf("bind port %d failed! \n", port);return false;}printf("bind port %d success!\n", port);listen(sock, 10);return true;}XTcp XTcp::Accept(){XTcp tcp;sockaddr_in cliAddr;socklen_t socklen = sizeof(cliAddr);//这个sock是专门用来建立连接的//accept函数返回一个客户端的socketint client = accept(sock, (sockaddr*)&cliAddr, &socklen);if(client <= 0)return tcp;printf("accept client %d\n", client);tcp.sock = client;char *ip = inet_ntoa(cliAddr.sin_addr);strcpy(tcp.ip, ip);tcp.port = ntohs(cliAddr.sin_port);//网络字节序转成本地字节序printf("client ip is %s, port is %d\n", tcp.ip, tcp.port);return tcp;}int XTcp::Recv(char *buf, int bufsize){return recv(sock, buf, bufsize, 0);}int XTcp::Send(const char *buf, int size){int sendedSize = 0;while(sendedSize != size){int len = send(sock, buf + sendedSize, sendsize - sendedSize, 0);if(len <= 0)break;sendedSize += len;}return sendedSize;}void XTcp::Close(){if(sock <= 0) return;closesocket(sock);}XTcp::~XTcp(){}
测试
在tcpserver项目目录中,新建并编写makefile文件:
tcpserver:server.cppg++ $+ -o $@
编译报错:
我们要把头文件路径给它指定一下:
tcpserver:server.cppg++ $+ -o $@ -I../xsocket/ -std=c++11 -lpthread -lxsocket -L../xsocket
提示说它找不到这个xsocket这个库,在linux你必须要指定环境变量,所以我们一个方案就是可以写一个脚本run:
export LD_LIBRARY_PATH = ../xsocket./tcpserver
chmod +x run
./run
还有一个省事的方案,你把动态链接库每次改变的时候,你把它拷贝到系统目录下面:
libxsocket.so:XTcp.cpp XTcp.hg++ $+ -o $@ -fpic -shared -std=c++11cp *.so /usr/lib/
假设我们想直接输入tcpserver运行呢?
那我们要把tcpserver也放在系统的环境变量下面去。
tcpserver:server.cppg++ $+ -o $@ -I../xsocket/ -std=c++11 -lpthread -lxsocket -L../xsocketcp $@ /usr/bin/
第四章 TCP客户端
新建一个控制台应用程序的空项目,在这个项目当中我们引用之前做好的XTcp这个库,右击解决方案,添加现有项目,把我们的xsocket项目加进来。
再做一下启动项和依赖项的设置。
再把输出目录,工作目录,附加包含目录,附加库目录,附加依赖项。
我们这个客户端很简单,就演示一下建立连接、发送数据、接收数据。
//tcpclient.cpp//#include "XTcp.h"int main(){XTcp client;return 0;}
看上图,我们来看一下TCP的三次握手的过程。
服务端这里创建了一个Socket,绑定了一个端口,通过listen开始监听,listen之后就是被动打开,也就是说它是由对方发送信息,它被动的反应,而不是主动去做。
服务端做好了之后,客户端是怎么进行三次握手的呢?
客户端它也要创建一个Socket,通过这个Socket调用Connect,Connect也是一个阻塞的函数,它是主动打开,它发一个SYN一个J过去,服务端收到这个包之后,服务端也会发一个SYN一个K过去,然后它会回一个ack的J+1回去,这个过程你在TCP协议头当中都会包含这几个内容;
当客户端收到服务端发过来的这个回应之后,Connect就会再发送一个ack K+1过去,就是告诉服务器我也收到你的信息了。
三次握手的过程在Connect函数内部其实就已经完成了,Accept只是读三次握手已经完成后的数据。
//XTcp.h//#ifndef _XTCP_H_#define _XTCP_H_#include #ifdef _WIN32#ifdef XSOCKET_EXPORTS#define XSOCKET_API __declspec(dllexport)#else#define XSOCKET_API __declspec(dllimport)#endif#else#define XSOCKET_API#endifclass XSOCKET_EXPORTS XTcp{public:int CreateSocket();//绑定端口和listenbool Bind(unsigned short port);XTcp Accept();void Close();int Recv(char *buf, int bufsize);int Send(const char *buf, int sendsize);bool Connect(const char *ip, unsigned short port);XTcp();virtual ~XTcp();int sock = 0;unsigned short port = 0;char ip[16];};#endif
//XTcp.cpp//#include "XTcp.h"#include #include #include #ifdef _WIN32#include #define socklen_t int#else#include #include #include #include #define closesocket close#endif#include using namespace std;XTcp::XTcp(){#ifdef _WIN32static bool first = true;if(first){first = false;WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);}#endif}int XTcp::CreateSocket(){if(sock <= 0){sock = socket(AF_INET, SOCK_STREAM, 0);if(sock == -1){printf("create socket failed! sock = %d\n", sock);}}return sock;}bool XTcp::Bind(unsigned short port){if(sock <= 0){CreateSocket();}sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(port);//本地字节序转成网络字节序、主机字节序转成网络字节序saddr.sin_addr.s_addr = htonl(0);//任意的IP地址发过来的数据我们都接收//为了不和c++11的bind冲突,这里我们用全局的bindif(::bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0){printf("bind port %d failed! \n", port);return false;}printf("bind port %d success!\n", port);listen(sock, 10);return true;}XTcp XTcp::Accept(){XTcp tcp;sockaddr_in cliAddr;socklen_t socklen = sizeof(cliAddr);//这个sock是专门用来建立连接的//accept函数返回一个客户端的socketint client = accept(sock, (sockaddr*)&cliAddr, &socklen);if(client <= 0)return tcp;printf("accept client %d\n", client);tcp.sock = client;char *ip = inet_ntoa(cliAddr.sin_addr);strcpy(tcp.ip, ip);tcp.port = ntohs(cliAddr.sin_port);//网络字节序转成本地字节序printf("client ip is %s, port is %d\n", tcp.ip, tcp.port);return tcp;}int XTcp::Recv(char *buf, int bufsize){return recv(sock, buf, bufsize, 0);}int XTcp::Send(const char *buf, int size){int sendedSize = 0;while(sendedSize != size){int len = send(sock, buf + sendedSize, sendsize - sendedSize, 0);if(len <= 0)break;sendedSize += len;}return sendedSize;}void XTcp::Close(){if(sock <= 0) return;closesocket(sock);}bool XTcp::Connect(const char *ip, unsigned short port){if(sock <= 0)CreateSocket();sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(port);saddr.sin_addr.s_addr = inet_addr(ip);if(connect(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0){printf("connect %s:%d failed!:%s\n", ip, port, strerror(errno));return false;}printf("connect %s:%d success!\n", ip, port);return true;}XTcp::~XTcp(){}
//tcpclient.cpp//#include "XTcp.h"int main(){XTcp client;client.Connect("192.168.0.108", 8080);client.Send("client", 6);char buf[1024] = {0};client.Recv(buf, sizeof(buf));printf("client recv : %s\n", buf);return 0;}
tcpclient:tcpclient.cppg++ $+ -o $@ -I../xsocket/ -std=c++11 -lpthread -lxsocket -L../xsocketcp $@ /usr/bin/
第五章 TCP阻塞超时和高并发处理
超时处理
来源地址:https://blog.csdn.net/zhaopeng01zp/article/details/126673512