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

news2024/9/20 5:37:47

@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阻塞超时和高并发处理

超时处理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/22247.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spark 离线开发框架设计与实现

一、背景 随着 Spark 以及其社区的不断发展&#xff0c;Spark 本身技术也在不断成熟&#xff0c;Spark 在技术架构和性能上的优势越来越明显&#xff0c;目前大多数公司在大数据处理中都倾向使用 Spark。Spark 支持多种语言的开发&#xff0c;如 Scala、Java、Sql、Python 等。…

亚马逊、OZON、速卖通等跨境电商平台卖家怎样快速提高产品权重?

亚马逊跨境电商是世界顶级的电子商务平台之一。基本上&#xff0c;当80%的客户购买产品时&#xff0c;亚马逊跨境电子商务将成为首选的在线购物平台。亚马逊是一个拥有自己独特优化算法的服务平台&#xff0c;对服务平台上数亿产品进行有序排序。当客户进行产品检索时&#xff…

【附源码】计算机毕业设计JAVA学生宿舍信息管理系统

【附源码】计算机毕业设计JAVA学生宿舍信息管理系统 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA…

经典排序算法JAVA实现

1、选择排序 首先在未排序数列中找到最小元素&#xff0c;然后将其与数列的首部元素进行交换&#xff0c;然后&#xff0c;在剩余未排序元素中继续找出最小元素&#xff0c;将其与已排序数列的末尾位置元素交换。以此类推&#xff0c;直至所有元素均排序完毕.复杂度为n2&#…

《Java并发编程之美》读书笔记——第一部分(并发编程基础知识)

文章目录第一章 并发编程线程基础1.什么是线程2.线程的创建与运行3.线程的通知与等待wait()wait(long timeout)wait(long timeout, int nanos)notify()与notifyAll()虚假唤醒4.等待线程执行终止的join方法5.让线程睡眠的sleep方法6.让CPU交出执行权的yield方法7.线程中断8.理解…

[附源码]java毕业设计物理中考复习在线考试系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

消息队列卡夫卡+EFLFK集群部署

pache公司的软件包官方下载地址&#xff1a;archive.apache.org/dist/ 注&#xff1a;kafka从3.0版本之后&#xff0c;不再依赖zookeeper。 一 Zookeeper概述 官方下载地址&#xff1a;archive.apache.org/dist/zookee… 1.Zookeeper定义 Zookeeper是一个开源的分布式的&a…

国内网络编译,Ambari 2.7.6 全部模块源码编译笔记

本次编译 ambari 2.7.6 没有使用科学上网的工具&#xff0c;使用的普通网络&#xff0c;可以编译成功&#xff0c;过程比 ambari 2.7.5 编译时要顺畅。 该版本相对 2.7.5 版本以来&#xff0c;共有 26 个 contributors 提交了 114 个 commits 以及修改了 557 个文件。详情见&a…

ovirt-engine通过UI Plugin自定义页面

官方API&#xff1a;点击打开 1 新增一个菜单项 1.1 创建引导html 首先你的这个页面是作为一个功能插件存在的&#xff0c;所以先给他起个名字&#xff0c;我这里的页面主要是用作用户创建&#xff0c;所以我的这个插件的名字就叫user。 接着就创建这个插件的 引导html &…

多级式多传感器信息融合中的状态估计(Matlab代码实现)

&#x1f352;&#x1f352;&#x1f352;欢迎关注&#x1f308;&#x1f308;&#x1f308; &#x1f4dd;个人主页&#xff1a;我爱Matlab &#x1f44d;点赞➕评论➕收藏 养成习惯&#xff08;一键三连&#xff09;&#x1f33b;&#x1f33b;&#x1f33b; &#x1f34c;希…

新能源车提车、上牌流程

漫长等待四个多月&#xff0c;终于2022年10月27日&#xff0c;笔者圆梦&#xff0c;喜提人生第一辆车。从选车、提车、上牌全程一人&#xff0c;用文记录下经历&#xff0c;以供参考。 一、提车流程 1.1 提车时间 若分到车&#xff0c;4S店销售会提前联系确定时间。 提示&…

【自然语言处理】【实体匹配】CollaborER:使用多特征协作的自监督实体匹配框架

CollaborER&#xff1a;使用多特征协作的自监督实体匹配框架《CollaborER: A Self-supervised Entity Resolution Framework Using Multi-features Collaboration》论文地址&#xff1a;https://arxiv.org/pdf/2108.08090.pdf 相关博客&#xff1a; 【自然语言处理】【实体匹配…

最大似然估计(机器学习)

目录 最大似然估计算法 最大似然估计例子 最大似然估计算法存在的问题 最大似然估计算法 EM算法是一种最大似然估计(Max imum Likel ihood Est imation)算法&#xff0c;传统的最大似然估计算法是根据已知的观察数据来评估模型参数 最大似然估计的一般步骤如下&#xff1a; …

HTML小游戏10 —— 休闲类游戏《解救海盗船长》(附完整源码)

&#x1f482; 网站推荐:【神级源码资源网】【摸鱼小游戏】&#x1f91f; 风趣幽默的前端学习课程&#xff1a;&#x1f449;28个案例趣学前端&#x1f485; 想寻找共同学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】&#x1f4ac; 免费且实用的计算机相关知…

kingbase之集群部署之集群规划和安装

一、kingbase简介 KingbaseES是一款面向大规模并发交易处理的企业级关系型数据库。该产品支持严格的ACID特性、结合多核架构的极致性能、行业最高的安全标准&#xff0c;以及完备的高可用方案&#xff0c;并提供可覆盖迁移、开发及运维管理全使用周期的智能便捷工具。在早先的博…

zsh: command not found: adb问题分析

问题描述 Mac上使用 adb 调试Android设备时&#xff0c;出现了 zsh: command not found: adb 的报错提示。 出现上述错误代表 adb 无法在挡枪 的shell 中使用&#xff0c;而当前的 shell 为 zsh 。 zsh 介绍 zsh 也是一种 shell &#xff0c;Unix 衍生系统的默认 的shell 都…

如何使用组件切换器做话题导航

highlight: atelier-cave-dark 使用组件切换器实现一个标签导航 效果展示 前置准备 背景素材 话题图标素材 具体步骤 制作背景 制作话题导航 制作话题导航结果列表 设置组件切换器关联内容 创建切换组件行为触发器 创建点击行为触发器 步骤分解 制作背景 将背景素材添加到 …

Vue使用axios进行get请求拼接参数的两种方式

前言 本文主要介绍如何在Vue使用axios进行get请求拼接参数的两种方式 我们就以github上的一个开源接口举例&#xff1a; https://api.github.com/search/users?qxxx 这是github给开发人员提供的一个接口&#xff0c;是get请求。我们可以直接通过浏览器访问 很明显&#xff…

[附源码]java毕业设计网易云音乐推荐系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

FL Studio21傻瓜式编曲音乐编辑器FL水果软件

在我看来软件只是工具.不管哪个都可以做任何风格的音乐,区别只是软件操作相对而言fl studio更容易上手,在国内也很受欢迎,弱项应该是混音上如果你做电音的话 还是FL更好一些 ,因为他就是为舞曲而生的!flstudio内配置音源、插件、录音软件、混音效果!而FLStudio则更偏向于电子音…