• C++socket网络编程实战http服务器(支持php)(上)


    @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=yes
    browseable=yes
    guest ok = yes
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    设置项目属性,链接器、输入,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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    第一个参数,是用什么协议,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 _WIN32
    	WSADATA ws;
    	WSAStartup(MAKEWORD(2, 2), &ws);
    #endif
    	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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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 _WIN32
    	WSADATA ws;
    	WSAStartup(MAKEWORD(2, 2), &ws);
    #endif
    	for(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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    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 _WIN32
    	WSADATA ws;
    	WSAStartup(MAKEWORD(2, 2), &ws);
    #endif
    	for(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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    第二章 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
    #endif
    
    int main(int argc, char* argv[])
    {
    //在linux当中socket不需要初始化
    #ifdef _WIN32
    	WSADATA ws;
    	WSAStartup(MAKEWORD(2, 2), &ws);
    #endif
    	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);
    	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函数返回一个客户端的socket
    	int 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;
    		//赋一个\0
    		buf[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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    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;
    			//赋一个\0
    			buf[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 _WIN32
    	WSADATA ws;
    	WSAStartup(MAKEWORD(2, 2), &ws);
    #endif
    	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);
    	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函数返回一个客户端的socket
    		int 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101

    在这里插入图片描述

    编译后报错,提示没有thread,并且你没有用c++11来进行编译,make只是一个简单的自动推导,它不会把这些参数加进去。
    重写一个makefile:

    tcpserver:main.cpp
    	g++ main.cpp -o tcpserver -std=c++11
    
    • 1
    • 2

    在这里插入图片描述
    编译报错,它是没找到pthread_create的定义,但是我们其实没调用这个函数,因为c++11它封装的这个thread里面调用了系统的pthread库,因为它针对windows和linux调用了不同的库,所以我们这里要把pthread库给它引用进来,这个库系统都是默认自带的。
    我们再改下makefile:

    tcpserver:main.cpp
    	g++ main.cpp -o tcpserver -std=c++11 -lpthread
    
    • 1
    • 2

    在这里插入图片描述

    这样就可以同时处理多路数据,我们就做了并发处理。

    接下来我们在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;
    			//赋一个\0
    			buf[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 _WIN32
    	WSADATA ws;
    	WSAStartup(MAKEWORD(2, 2), &ws);
    #endif
    	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);
    	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冲突,这里我们用全局的bind
    	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函数返回一个客户端的socket
    		int 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    第三章 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();
    	//绑定端口和listen
    	bool 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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    //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 _WIN32
    	static 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冲突,这里我们用全局的bind
    	if(::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函数返回一个客户端的socket
    	int 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()
    {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    //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;
    			//赋一个\0
    			buf[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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    在windows下编译运行测试成功!

    在linux下编译报错:
    在这里插入图片描述

    错误提示Recv、Send函数和Close函数没有定义,那是因为我们的makefile里面没有包含XTcp.cpp这个文件,我们把makefile修改一下:

    tcpserver:main.cpp XTcp.cpp XTcp.h
    	g++ main.cpp XTcp.cpp -o tcpserver -std=c++11 -lpthread
    
    • 1
    • 2

    在这里插入图片描述

    创建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)
    #endif
    
    class XSOCKET_EXPORTS XTcp
    {
    public:
    	int CreateSocket();
    	//绑定端口和listen
    	bool 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];
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    记得关闭预编译头。

    //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 _WIN32
    	static 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冲突,这里我们用全局的bind
    	if(::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函数返回一个客户端的socket
    	int 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()
    {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

    我们参考开源项目,调整一下代码的结构,创建几个目录: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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    我们先写一个简单的,保证输出测试成功。
    打开项目的配置属性,常规,设置输出目录为..\..\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;
    			//赋一个\0
    			buf[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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    创建XTcp动态so链接库项目(Linux)并测试

    在xsocket项目目录中,新建并编写makefile文件:

    libxsocket.so:XTcp.cpp XTcp.h
    	g++ $+ -o $@ -fpic -shared -std=c++11
    
    • 1
    • 2

    编译出错,主要是在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
    #endif
    
    class XSOCKET_EXPORTS XTcp
    {
    public:
    	int CreateSocket();
    	//绑定端口和listen
    	bool 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    //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 _WIN32
    	static 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冲突,这里我们用全局的bind
    	if(::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函数返回一个客户端的socket
    	int 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()
    {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

    测试

    在tcpserver项目目录中,新建并编写makefile文件:

    tcpserver:server.cpp
    	g++ $+ -o $@
    
    • 1
    • 2

    编译报错:
    在这里插入图片描述

    我们要把头文件路径给它指定一下:

    tcpserver:server.cpp
    	g++ $+ -o $@ -I../xsocket/ -std=c++11 -lpthread -lxsocket -L../xsocket
    
    • 1
    • 2

    在这里插入图片描述

    提示说它找不到这个xsocket这个库,在linux你必须要指定环境变量,所以我们一个方案就是可以写一个脚本run:

    export LD_LIBRARY_PATH = ../xsocket
    ./tcpserver
    
    • 1
    • 2

    chmod +x run
    ./run

    还有一个省事的方案,你把动态链接库每次改变的时候,你把它拷贝到系统目录下面:

    libxsocket.so:XTcp.cpp XTcp.h
    	g++ $+ -o $@ -fpic -shared -std=c++11
    	cp *.so /usr/lib/
    
    • 1
    • 2
    • 3

    假设我们想直接输入tcpserver运行呢?
    那我们要把tcpserver也放在系统的环境变量下面去。

    tcpserver:server.cpp
    	g++ $+ -o $@ -I../xsocket/ -std=c++11 -lpthread -lxsocket -L../xsocket
    	cp $@ /usr/bin/
    
    • 1
    • 2
    • 3

    第四章 TCP客户端

    新建一个控制台应用程序的空项目,在这个项目当中我们引用之前做好的XTcp这个库,右击解决方案,添加现有项目,把我们的xsocket项目加进来。
    再做一下启动项和依赖项的设置。
    再把输出目录,工作目录,附加包含目录,附加库目录,附加依赖项。

    我们这个客户端很简单,就演示一下建立连接、发送数据、接收数据。

    //tcpclient.cpp
    //
    #include "XTcp.h"
    
    int main()
    {
    	XTcp client;
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    看上图,我们来看一下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
    #endif
    
    class XSOCKET_EXPORTS XTcp
    {
    public:
    	int CreateSocket();
    	//绑定端口和listen
    	bool 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    //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 _WIN32
    	static 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冲突,这里我们用全局的bind
    	if(::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函数返回一个客户端的socket
    	int 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()
    {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    //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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    tcpclient:tcpclient.cpp
    	g++ $+ -o $@ -I../xsocket/ -std=c++11 -lpthread -lxsocket -L../xsocket
    	cp $@ /usr/bin/
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    第五章 TCP阻塞超时和高并发处理

    超时处理

  • 相关阅读:
    A_A01_002 MDK5(STM32)软件安装
    Python操作Excel表格
    openGauss学习笔记-123 openGauss 数据库管理-设置账本数据库-账本数据库概述
    【Nginx24】Nginx学习:压缩模块Gzip
    使用乐鑫 Web IDE 助力物联网开发
    java计算机毕业设计铝塑门窗的研制和生产管理源码+系统+mysql数据库+lw文档
    LeetCode //C - 201. Bitwise AND of Numbers Range
    element tree懒加载默认展开指定节点
    windows下dapr的代码调试--非docker部署
    Spring Cloud 架构设计之linux yum redis
  • 原文地址:https://blog.csdn.net/zhaopeng01zp/article/details/126673512