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

  • Post author:
  • Post category: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

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 <windows.h>

int main(int argc, char* argv[])
{
	WSADATA ws;
	WSAStartup(MAKEWORD(2, 2), &ws);
	
	return 0;
}

设置项目属性,链接器、输入,ws2_32.lib;

编译成功!

我们调用socket函数:

//main.cpp
//
#include <windows.h>
#include <stdio.h>

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 <windows.h>
#endif
#include <stdio.h>

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;
}

make main

编译如果报错,根据信息可以判断是由于没有添加响应头文件导致的,那么socket相关的头文件是什么呢,我们通过man手册来查找。

man socket

在这里插入图片描述

//main.cpp
//
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#endif
#include <stdio.h>

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;
}

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 <windows.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#define closesocket close
#endif
#include <stdio.h>

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;
}



第二章 TCP服务器



TPC协议

  • TCP协议

    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述



网络字节序

在这里插入图片描述

在这里插入图片描述



创建tcp服务端

  • int accept(int sockfd, void *addr, int *addrlen);

    在这里插入图片描述
//main.cpp
//
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#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;
}

TCP的3次握手是由系统内部已经做完了,我们accept函数只是读取了它3次握手后的信息,所以说是在我们绑定bind、进行lisent之后,对方客户端用连接请求的时候,它就已经完成了3次握手。

//我们现在增加一个功能,每来一个客户端,我们就把它加到一个新的线程当中,这样保证我能同时并发处理多个客户端的数据,

//我们采用C++11的线程

//main.cpp
//
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#define closesocket close
#endif

#include <thread>
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;
}

在这里插入图片描述

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

重写一个makefile:

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

在这里插入图片描述

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

我们再改下makefile:

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

在这里插入图片描述

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

接下来我们在windows下编译该代码,正常情况下肯定会有一些错误,毕竟和linux有所区别。

//main.cpp
//
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#define socklen_t int
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#define closesocket close
#endif

#include <thread>
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;
}



第三章 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 <string>

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;
};
//XTcp.cpp
//
#include "XTcp.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#define socklen_t int
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#define closesocket close
#endif

#include <thread>
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()
{

}
//main.cpp
//
#include <stdlib.h>
#include "XTcp.h"
#include <thread>
#include <string.h>

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;
}

在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

在这里插入图片描述



创建XTcp动态dll链接库项目并测试

我们把前面做的XTcp这个类放到一个动态链接库当中,分别在windows和linux当中都来实现这个动态链接库。

我们新建一个Win32 控制台应用程序,XSocket,应用程序类型选择DLL,附加选项选择导出符号,导出符号的好处项目当中有一些宏已经帮你定义好了,省得你定义了。

把前面做的XTcp.h和XTcp.cpp拷贝到当前项目目录中,并添加到当前项目中。

//XTcp.h
//
#pragma once
#include <string>

#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];
};

记得关闭预编译头。

//XTcp.cpp
//
#include "XTcp.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#define socklen_t int
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#define closesocket close
#endif

#include <thread>
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()
{

}

我们参考开源项目,调整一下代码的结构,创建几个目录: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 <stdlib.h>
#include "XTcp.h"
#include <thread>
#include <string.h>

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;
}



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

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

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

编译出错,主要是在linux当中不认识windows中的几个宏,我们修改动态链接库的代码:

//XTcp.h
//
#ifndef _XTCP_H_
#define _XTCP_H_
#include <string>

#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
//XTcp.cpp
//
#include "XTcp.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#define socklen_t int
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#define closesocket close
#endif

#include <thread>
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()
{

}



测试

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

tcpserver:server.cpp
	g++ $+ -o $@

编译报错:

在这里插入图片描述

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

tcpserver:server.cpp
	g++ $+ -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.h
	g++ $+ -o $@ -fpic -shared -std=c++11
	cp *.so /usr/lib/

假设我们想直接输入tcpserver运行呢?

那我们要把tcpserver也放在系统的环境变量下面去。

tcpserver:server.cpp
	g++ $+ -o $@ -I../xsocket/ -std=c++11 -lpthread -lxsocket -L../xsocket
	cp $@ /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 <string>

#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
//XTcp.cpp
//
#include "XTcp.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#define socklen_t int
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#define closesocket close
#endif

#include <thread>
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()
{

}
//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.cpp
	g++ $+ -o $@ -I../xsocket/ -std=c++11 -lpthread -lxsocket -L../xsocket
	cp $@ /usr/bin/

在这里插入图片描述



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



超时处理



版权声明:本文为zhaopeng01zp原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。