TCPIP实验3、4(非阻塞多线程的Socket编程)

实验目的:

学习SOCKET编程基础

实验要求:

  1. 程序使用非阻塞方式进行通信;
  2. 使得服务器可以同时和两个客户机相互发送信息,客户端所显示的IP地址和端口是客户自己的。服务器端所显示的IP地址和端口也是客户的。(效果如下图)
    在这里插入图片描述
    实验设备:vs2013 (如果你是使用6.0版本的,详情请看我的博客“TCPIP实验1、2”中的开发提示。如果开发过程中有什么问题,也建议将我这部分的专栏都浏览一遍,或许能找到我也同样遇到过的问题的解决方案。)

基于非阻塞模式的多线程服务器应用程序的编程,需要打开至少一个作为服务端的vs2013工程,至少两个作为客户端 的vs2013工程。

实验整体运行效果:
在这里插入图片描述

流程图:

在这里插入图片描述

服务器端代码:

// 大作业服务器端.cpp : 定义控制台应用程序的入口点。

#include "stdafx.h"
#include <WINSOCK2.H>   
#include <iostream>
#include <string>
#pragma comment(lib,"WS2_32.lib")   
#define BUF_SIZE    64				// 缓冲区大小

sockaddr_in addrClient;				// 客户端地址

DWORD  WINAPI  AnswerThread(LPVOID  lparam)
{
	char   buf[BUF_SIZE];			// 用于接受客户端数据的缓冲区   
	int     retVal;					// 调用各种Socket函数的返回值   

	SOCKET  sClient = (SOCKET)(LPVOID)lparam;

	// 循环接收客户端的数据,直接客户端发送quit命令后退出。  
	while (true)
	{
		ZeroMemory(buf, BUF_SIZE);						// 清空接收数据的缓冲区
		retVal = recv(sClient, buf, BUFSIZ, 0);				// 接收来自客户端的数据,因为是非阻塞模式,所以即使没有数据也会继续
		if (SOCKET_ERROR == retVal)
		{
			int err = WSAGetLastError();				// 获取错误编码
			if (err == WSAEWOULDBLOCK)			// 接收数据缓冲区暂无数据
			{
				Sleep(100);
				continue;
			}
			else if (err == WSAETIMEDOUT || err == WSAENETDOWN)
			{
				printf("recv failed !\n");
				closesocket(sClient);
				WSACleanup();
				return -1;
			}
		}
		if (retVal == 0){
			printf("用户以下线");
		}
		// 获取当前系统时间
		SYSTEMTIME st;
		GetLocalTime(&st);
		char sDateTime[30];
		sprintf_s(sDateTime, "%4d-%2d-%2d %2d:%2d:%2d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
		// 打印输出的信息
		printf("时间:%s,客户端IP和端口号: [%s:%d] 客户端名称:%s\n", sDateTime, inet_ntoa(addrClient.sin_addr), addrClient.sin_port, buf);
		// 如果客户端发送quit字符串,则服务器退出
		if (strcmp(buf, "quit") == 0)
		{
			retVal = send(sClient, "quit", strlen("quit"), 0);
			break;
		}
		else		// 否则向客户端发送回显字符串
		{
			char    msg[BUF_SIZE];
			sprintf_s(msg, " %s", buf);
			//___________________________________________________
			// 向服务器发送数据   
			printf("Please input a string to send: ");
			// 接收输入的数据
			std::string str;
			std::getline(std::cin, str);
			// 将用户输入的数据复制到buf中
			ZeroMemory(buf, BUF_SIZE);
			strcpy_s(buf, str.c_str());
			//---------------------------------------------------
			while (true)
			{
				// 向服务器发送数据
				retVal = send(sClient, buf, strlen(buf), 0);
				if (SOCKET_ERROR == retVal)
				{
					int err = WSAGetLastError();
					if (err == WSAEWOULDBLOCK)			// 无法立即完成非阻塞套接字上的操作
					{
						Sleep(500);
						continue;
					}
					else
					{
						printf("send failed !\n");
						closesocket(sClient);
						WSACleanup();
						return -1;
					}
				}
				break;
			}

		}
	}
	// 关闭套接字
	closesocket(sClient);
}

int _tmain(int argc, _TCHAR* argv[])
{
	WSADATA wsd;					// WSADATA变量,用于初始化Windows Socket   
	SOCKET  sServer;					// 服务器套接字,用于监听客户端请求
	SOCKET  sClient;					// 客户端套接字,用于实现与客户端的通信   
	int     retVal;							// 调用各种Socket函数的返回值   

	// 初始化套接字动态库   
	if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
	{
		printf("WSAStartup failed !\n");
		return 1;
	}
	// 创建用于监听的套接字   
	sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
	if (INVALID_SOCKET == sServer)
	{
		printf("socket failed !\n");
		WSACleanup();
		return -1;
	}
	// 设置套接字为非阻塞模式
	int iMode = 1;
	retVal = ioctlsocket(sServer, FIONBIO, (u_long FAR*) &iMode);
	if (retVal == SOCKET_ERROR)
	{
		printf("ioctlsocket failed !\n");
		WSACleanup();
		return -1;
	}
	// 设置服务器套接字地址   
	SOCKADDR_IN addrServ;
	addrServ.sin_family = AF_INET;
	addrServ.sin_port = htons(9990);		// 监听端口为9990
	addrServ.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	//addrServ.sin_addr.S_un.S_addr = inet_addr("192.168.43.62");
	// 绑定套接字sServer到本地地址,端口9990  
	retVal = bind(sServer, (const struct sockaddr*)&addrServ, sizeof(SOCKADDR_IN));
	if (SOCKET_ERROR == retVal)
	{
		printf("bind failed !\n");
		closesocket(sServer);
		WSACleanup();
		return -1;
	}
	// 监听套接字   
	retVal = listen(sServer, SOMAXCONN);
	if (SOCKET_ERROR == retVal)
	{
		printf("listen failed !\n");
		closesocket(sServer);
		WSACleanup();
		return -1;
	}
	// 接受客户请求   
	printf("服务端启动,可以接受多个客户端同时连接...\n");
	int addrClientlen = sizeof(addrClient);
	// 循环等待
	while (true)
	{
		sClient = accept(sServer, (sockaddr FAR*)&addrClient, &addrClientlen);
		if (INVALID_SOCKET == sClient)
		{
			int err = WSAGetLastError();
			if (err == WSAEWOULDBLOCK)				// 无法立即完成非阻塞套接字上的操作
			{
				Sleep(100);
				continue;
			}
			//else
			//{
			//	//printf("accept failed !\n");   
			//	//closesocket(sServer);   
			//	//WSACleanup();   
			//	//return -1;   
			//	Sleep(100);
			//	continue;
			//}
		}
		// 创建专用通信线程
		DWORD dwThreadId;
		CreateThread(NULL, NULL, AnswerThread, (LPVOID)sClient, 0, &dwThreadId);
	}
	// 释放套接字   
	closesocket(sServer);
	WSACleanup();
	// 暂停,按任意键退出
	system("pause");
	return 0;
}

客户端代码:

// 大作业-客户端2.cpp : 定义控制台应用程序的入口点。

#include "stdafx.h"
#include <Winsock2.H>   
#include <string>
#include <iostream>

#pragma comment(lib,"WS2_32.lib")   
#define BUF_SIZE    64          // 缓冲区大小  

int _tmain(int argc, _TCHAR* argv[])
{
	WSADATA     wsd;					// 用于初始化Windows Socket   
	SOCKET      sHost;					// 与服务器进行通信的套接字   
	SOCKADDR_IN servAddr;			// 服务器地址   
	char        buf[BUF_SIZE];			// 用于接受数据缓冲区   
	int         retVal;							// 调用各种Socket函数的返回值   
	// 初始化Windows Socket
	if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
	{
		printf("WSAStartup failed !\n");
		return 1;
	}
	// 创建套接字   
	sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
	if (INVALID_SOCKET == sHost)
	{
		printf("socket failed !\n");
		WSACleanup();
		return -1;
	}
	// 设置套接字为非阻塞模式
	int iMode = 1;
	retVal = ioctlsocket(sHost, FIONBIO, (u_long FAR*) &iMode);
	if (retVal == SOCKET_ERROR)
	{
		printf("ioctlsocket failed !\n");
		WSACleanup();
		return -1;
	}
	// 设置服务器地址   
	servAddr.sin_family = AF_INET;
	//servAddr.sin_addr.S_un.S_addr =  htonl(INADDR_ANY);		// 用户需要根据实际情况修改
	servAddr.sin_addr.S_un.S_addr = inet_addr("192.168.43.62");
	servAddr.sin_port = htons(9990);		// 在实际应用中,建议将服务器的IP地址和端口号保存在配置文件中
	int sServerAddlen = sizeof(servAddr);		// 计算地址的长度       
	// 循环等待
	while (true)
	{
		// 连接服务器   
		Sleep(200);
		retVal = connect(sHost, (LPSOCKADDR)&servAddr, sizeof(servAddr));
		Sleep(200);
		if (SOCKET_ERROR == retVal)
		{
			int err = WSAGetLastError();
			if (err == WSAEWOULDBLOCK || err == WSAEINVAL)  // 无法立即完成非阻塞套接字上的操作
			{
				//Sleep(500);
				continue;
			}
			else if (err == WSAEISCONN)												// 已建立连接
			{
				break;
			}
			else
			{
				continue;

				//printf("connect failed !\n");   
				//closesocket(sHost);   
				//WSACleanup();   
				//return -1;   
			}
		}
	}
	// 循环向服务器发送字符串,并显示反馈信息。
	// 发送quit将使服务器程序退出,同时客户端程序自身也将退出
	while (true)
	{
		// 向服务器发送数据   
		printf("Please input a string to send: ");
		// 接收输入的数据
		std::string str;
		std::getline(std::cin, str);
		// 将用户输入的数据复制到buf中
		ZeroMemory(buf, BUF_SIZE);
		strcpy_s(buf, str.c_str());
		// 循环等待
		while (true)
		{
			// 向服务器发送数据
			retVal = send(sHost, buf, strlen(buf), 0);
			if (SOCKET_ERROR == retVal)
			{
				int err = WSAGetLastError();
				if (err == WSAEWOULDBLOCK)			// 无法立即完成非阻塞套接字上的操作
				{
					Sleep(500);
					continue;
				}
				else
				{
					printf("send failed !\n");
					closesocket(sHost);
					WSACleanup();
					return -1;
				}
			}
			break;
		}

		while (true)
		{
			ZeroMemory(buf, BUF_SIZE);						// 清空接收数据的缓冲区
			retVal = recv(sHost, buf, sizeof(buf)+1, 0);  // 接收服务器回传的数据   
			if (SOCKET_ERROR == retVal)
			{
				int err = WSAGetLastError();				// 获取错误编码
				if (err == WSAEWOULDBLOCK)			// 接收数据缓冲区暂无数据
				{
					Sleep(100);
					//printf("waiting back msg !\n");
					continue;
				}
				else if (err == WSAETIMEDOUT || err == WSAENETDOWN)
				{
					printf("recv failed !\n");
					closesocket(sHost);
					WSACleanup();
					return -1;
				}
				break;
			}
			break;
		}
		//ZeroMemory(buf,BUF_SIZE);						// 清空接收数据的缓冲区
		//retVal = recv(sHost,buf,sizeof(buf)+1,0);   // 接收服务器回传的数据   
		// 获取当前系统时间
		SYSTEMTIME st;
		GetLocalTime(&st);
		char sDateTime[30];
		sprintf_s(sDateTime, "%4d-%2d-%2d %2d:%2d:%2d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
		// 打印输出的信息
		printf("时间:%s,服务端IP和端口号: [%s:%d] 服务端名称:%s\n", sDateTime, inet_ntoa(servAddr.sin_addr), servAddr.sin_port, buf);
		// 如果收到quit,则退出
		if (strcmp(buf, "quit") == 0)
		{
			printf("quit!\n");
			break;
		}
	}
	// 释放资源   
	closesocket(sHost);
	WSACleanup();
	// 暂停,按任意键继续
	system("pause");
	return 0;
}




上面的实验存在一个问题,实验默认客户端是不会掉线的,但是当客户端掉线的时候,服务端会输出很多没必要的提示信息,我也在找解决办法,稍安勿躁

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值