计算机网络系统学习精华总结(三):传输层——4(套接字编程实战,全双工通信)

       全双工通信。本文在前文单双共通信模式下(连接:计算机网络系统学习精华总结(三):传输层——3(套接字编程实战,半双工通信)),采用简单的多线程编程实现,代码几乎每句都包含注释。

       服务端代码:

       

#include 
#include
#include //WSAADATA 关键字
#include
#pragma comment (lib, "ws2_32.lib")
#pragma warning(disable : 4996)

/*全双工通信,互不干扰*/

//---------------通信用-----------------------
const int bufLen = 1024;
char reciveBuf[bufLen];    //存放接收数据,字符数组
char sendBuf[bufLen];      //存放发送数据
//---------------通信用-----------------------
using namespace std;

WSAEVENT bExit = WSACreateEvent();//设置一个多线程停止事件


//接收线程函数
DWORD WINAPI recvMessage(LPVOID parsock)
{
	SOCKET recvsock = (SOCKET) parsock;        //对端客户机传来的套接字
	int err;                                   //错误代码
	while (true)
	{
		memset(reciveBuf,0,sizeof(reciveBuf));//初始化接收缓存
		if (recv(recvsock, reciveBuf, bufLen, 0) != SOCKET_ERROR)
		{//接收成功
			cout << "收到内容:" << reciveBuf << endl;
			if (strcmp(reciveBuf, "bye") == 0)
			{//客户端结束通信,输入bye
				SetEvent(bExit);//触发事件,结束线程,继续执行主进程
				break;
			}
		}
		else
		{//出错了
			err = WSAGetLastError();
			if (err == WSAECONNRESET)
			{//连接有问题了
				cout << "The peer is closed" << endl;
				continue;//继续下一次
			}
			SetEvent(bExit);//f否则就是事件触发直接退出
			break;
		}
	}
	closesocket(recvsock);//关闭套接字
	WSACleanup();//释放资源
	return 0;
}


//发送线程函数
DWORD WINAPI sendMessage(LPVOID parsock)
{
	SOCKET sendsock = (SOCKET)parsock;        //对端客户机传来的套接字
	while (true)
	{
		cout << "请输入要发送的信息:" << endl;
		cin.getline(sendBuf, sizeof(sendBuf));
		send(sendsock, sendBuf, sizeof(sendBuf),0);//发送消息
		if (strcmp(sendBuf, "bye") == 0)
		{
			SetEvent(bExit);//事件触发
			break;
		}
	}
	closesocket(sendsock);	
	WSACleanup();//释放资源
	return 0;
}

int main()
{
	//初始化套接字动态库,使用socket2.0版本,
	WSADATA wsaDATA;
	//WSAStartup(MAKEWORD(2, 0), &wsaDATA);
	if (WSAStartup(MAKEWORD(2, 0), &wsaDATA) != 0)
	{//初始化失败
		std::cout << "初始化失败" << std::endl;
		return -1;
	}

	//创建套接字,int socket(int domain,int type,int protocol)
	/*socket函数第一个参数——地址族(协议族、协议域):底层使用哪种通信协议来传递数据。AF_INET使用TCP/IPv4,AF_INET6使用TCP/IPv6,AF_LOCAL或者AF_UNIX指本地通信,当前主机上不同进程间的通信,一般用绝对路径指明
	socket函数第二个参数——类型:socket类型有三种,SOCK_STREAM:即TCP,面向字节流,需要先连接,可靠传输,全双工,面向字节流,有流量控制。
	SOCK_DGRAM:即UDP,面向数据报,无连接,不可靠,无拥塞控制,首部开销小
	SOCK_RAW:即IP,工作在网络层,无连接,不保证数据完整性和有序性,无流量控制。
	socket函数第三个参数——协议:常用协议,为0表示默认匹配相应的,IPPROTO_TCP\IPPTOTO_UDP\IPPROTO_SCTP\IPPROTO_TIPC,
	其中type与protocol不能任意组合

	*/
	SOCKET  serverSocket = socket(AF_INET, SOCK_STREAM, 0);//创建套接字,使用ipv4,TCP协议
	if (serverSocket == INVALID_SOCKET)
	{
		std::cout << "socket创建失败" << std::endl;
		WSACleanup();//释放套接字资源
		return -1;
	}
	//绑定套接字
	struct sockaddr_in  sockAddr;//其结构根据地址创建socket的协议族不同而不同,struct sockaddr_in/sockaddr_in/SOCKADDR_IN
	sockAddr.sin_family = AF_INET;//协议族(地址族)
	sockAddr.sin_port = htons(8888);//本地端口端口,将主机数值搞成网络字节序
	sockAddr.sin_addr.s_addr = inet_addr("192.168.0.103");//inet_addr将字符串形式的IP地址装换成网络字节顺序的整型值,127.0.0.1为本地回环地址
														  //中 s_addr宏定义为S_un.S_addr
														  //sockAddr.sin_addr.S_un.S_addr= inet_addr("127.0.0.1");

	int ret = bind(serverSocket, (struct sockaddr*)&sockAddr, sizeof(SOCKADDR_IN));//LPSOCKADDR等同于struct sockaddr*、SOCKADDR*
																				   //int bind(int sockfd,const struct socketaddr * addr,
	if (ret == SOCKET_ERROR)//
	{
		std::cout << "绑定失败!" << std::endl;
		WSACleanup();//释放套接字资源
		closesocket(serverSocket);//关闭套接字
		return -1;
	}


	//进入监听状态
	listen(serverSocket, 5);//5代表可以排队的最大连接数
	if (listen(serverSocket, 5) < 0)//listen返回0表示监听中,返回-1表示监听失败
	{
		std::cout << "监听失败!" << std::endl;
		WSACleanup();//释放套接字资源
		closesocket(serverSocket);//关闭套接字
		return -1;

	}

	//接收客户端请求
	//SOCKADDR clsocket;
	SOCKADDR  clsocket;
	int len = sizeof(SOCKADDR);
	SOCKET newSock = accept(serverSocket, (SOCKADDR*)&clsocket, &len);
	if (newSock == SOCKET_ERROR)
	{
		std::cout << "服务器——客户端连接失败!" << std::endl;
		return -1;
	}
	std::cout << "服务器——客户端连接完成" << std::endl;

	//连接成功,开启多线程收发
	HANDLE send_thread, recv_thread;//定义发送和接收线程,句柄方式定义
	DWORD nSthread, nRthread;    //定义返回的线程ID号
	/*使用CreateThread()创建线程
	  第一个参数表示内核对象安全属性
	  第二个参数表示线程栈空间大小,0表示默认1MB
	  第三个参数表示执行的线程函数地址
	  第四个参数表示传递给线程函数的参考,本线程传递的是对端的套接字
	  第五个参数表示额外的标志,用来控制线程的创建,0表示创建完之后线程立即执行,CREAT——SUSPENDED表示创建后暂停执行,知道调用ResumeThread()函数
	  第六个参数表示返回的线程号
	*/
	send_thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)sendMessage, (LPVOID)newSock, 0, &nSthread);//发送线程
	recv_thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)recvMessage, (LPVOID)newSock, 0, &nRthread);//接收线程
	/*希望线程完成后才继续执行主进程,直到bExit事件被激活(由SetEvent()触发事件的函数激活)
	  主线程才继续执行
	*/
	WaitForSingleObject(bExit, INFINITE);
	closesocket(newSock);
	closesocket(serverSocket);
	WSACleanup();
	return 0;
}

         客户端代码:

         

#include 
#include
#include 
#pragma comment (lib, "ws2_32.lib")
#pragma warning(disable : 4996)
using namespace std;

/*全双工通信*/

//---------------通信用-----------------------
const int bufLen = 1024;
char reciveBuf[bufLen];//字符数组
char sendBuf[bufLen];//存放发送数据
//---------------通信用-----------------------

WSAEVENT bExit = WSACreateEvent();//设置一个多线程停止事件

								  //接收线程函数
DWORD WINAPI recvMessage(LPVOID parsock)
{
	SOCKET recvsock = (SOCKET)parsock;        //对端客户机传来的套接字
	int err;                                   //错误代码
	while (true)
	{
		memset(reciveBuf, 0, sizeof(reciveBuf));//初始化接收缓存
		if (recv(recvsock, reciveBuf, bufLen, 0) != SOCKET_ERROR)
		{//接收成功
			cout << "收到内容:" << reciveBuf << endl;
			if (strcmp(reciveBuf, "bye") == 0)
			{//客户端结束通信,输入bye
				SetEvent(bExit);//触发事件,结束线程,继续执行主进程
				break;
			}
		}
		else
		{//出错了
			err = WSAGetLastError();
			if (err == WSAECONNRESET)
			{//连接有问题了
				cout << "The peer is closed" << endl;
				continue;//继续下一次
			}
			SetEvent(bExit);//f否则就是事件触发直接退出
			break;
		}
	}
	closesocket(recvsock);//关闭套接字
	WSACleanup();//释放资源
	return 0;
}


//发送线程函数
DWORD WINAPI sendMessage(LPVOID parsock)
{
	SOCKET sendsock = (SOCKET)parsock;        //对端客户机传来的套接字
	while (true)
	{
		cout << "请输入要发送的信息:" << endl;
		cin.getline(sendBuf, sizeof(sendBuf));
		send(sendsock, sendBuf, sizeof(sendBuf), 0);//发送消息
		if (strcmp(sendBuf, "bye") == 0)
		{
			SetEvent(bExit);//事件触发
			break;
		}
	}
	closesocket(sendsock);
	WSACleanup();//释放资源
	return 0;
}

int main()
{
	//初始化套接字动态库
	WSADATA data;
	//WSAStartup(MAKEWORD(2, 0), &data);
	if (WSAStartup(MAKEWORD(2, 0), &data) != 0)//为0则初始化成功
	{
		std::cout << "初始化动态套接字失败!" << std::endl;
		return -1;
	}

	SOCKET client = socket(AF_INET, SOCK_STREAM, 0);//创建套接字
	if (client == INVALID_SOCKET)
	{
		std::cout << "套接字创建失败!" << std::endl;
		WSACleanup();//释放套接字资源
		return -1;
	}

	//配置目的地址信息,ip地址,端口,协议类型
	sockaddr_in addr;
	addr.sin_addr.S_un.S_addr = inet_addr("192.168.0.103");
	addr.sin_family = AF_INET;
	addr.sin_port = htons(8888);

	std::cout << "连接中......" << std::endl;
	int ret = connect(client, (SOCKADDR*)&addr, sizeof(addr));
	if (ret == -1)//返回0表示连接成功,-1连接失败
	{
		std::cout << "连接失败!" << std::endl;
		return -1;
	}
	std::cout << "连接成功!" << std::endl;

	//连接成功,开启多线程收发
	HANDLE send_thread, recv_thread;//定义发送和接收线程,句柄方式定义
	DWORD nSthread, nRthread;    //定义返回的线程ID号
								 /*使用CreateThread()创建线程
								 第一个参数表示内核对象安全属性
								 第二个参数表示线程栈空间大小,0表示默认1MB
								 第三个参数表示执行的线程函数地址
								 第四个参数表示传递给线程函数的参数,本线程传递的是对端的套接字
								 第五个参数表示额外的标志,用来控制线程的创建,0表示创建完之后线程立即执行,CREAT——SUSPENDED表示创建后暂停执行,知道调用ResumeThread()函数
								 第六个参数表示返回的线程号
								 */
	send_thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)sendMessage, (LPVOID)client, 0, &nSthread);//发送线程
	recv_thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)recvMessage, (LPVOID)client, 0, &nRthread);//接收线程
																											/*希望线程完成后才继续执行主进程,直到bExit事件被激活(由SetEvent()触发事件的函数激活)
																											主线程才继续执行
																											*/
	WaitForSingleObject(bExit, INFINITE);
	closesocket(client);
	WSACleanup();
	return 0;
}

 

 

你可能感兴趣的:(计算机网络,c++,socket,多线程,网络)