Socket基础四:基于流式套接字的网络程序(并发服务器设计)

 作者:刘磊 2020.4.27

   参考书目:《Windows网络编程》刘琰等著

  • 并发性

并发性是TCP/IP程序的基础,服务器软件必须在程序中有专门的支持并发的硬件或专门的机制实现并发处理。

 

  • 多线程编程要点  

1、程序、进程和线程、

1)程序

程序是计算机指令的集合,它以文件形式存储在磁盘上。

2)进程

进程是程序在其自身的地址空间中的一次执行活动。

进程由两部分组成:

*内核对象。内核对象是系统用来存放关于进程统计信息的区域。

*地址空间。地址空间包含所有可执行模块或DLL模块的代码和数据,还包含动态内存分配的空间,如线程堆栈和堆分配空间。

进程是线程的容器,进程若要完成某项操作,必须拥有一个在它环境中运行的线程,这些线程都并发的执行进程空间中的代码。

3)线程

系统从进程的地址空间中分配内存供线程的堆栈使用。线程可以访问进程内核对象的所有句柄、进程中的所有内存和在相同进程中的其他线程的堆栈,这使得单个进程中的多个线程能够互相通信。

线程由两个部分组成:

*内核对象。存放线程统计信息的区域。

*线程堆栈。线程堆栈维护线程在执行代码时所需要的所有参数和局部变量。

线程并发并不是指同一时间内CPU同时运行多个线程。

 

2、多线程编程

1)创建线程函数CreateThread()

在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄。

2)线程挂起函数SuspendThread()

挂起。

3)挂起恢复函数ResumeThread()

4)线程结束函数ExitThread()

正常结束。

5)线程终止函数TerminateThread()

强行终止某一线程的执行。

6)线程消息传递函数PostThreadMessage()

将一个消息放入到指定线程的消息队列中,并且不等到消息被该线程处理时便返回。

 

3、MFC对多线程编程的支持

MFC中有两类线程,工作线程和用户界面线程,主要区别是工作线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。

工作线程主要用于执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。

用户界面线程一般用于处理独立于其他线程执行之外的用户输入。

 

4、线程间通信

一般来说,应用程序中的一个从线程总是为主线程执行特定的任务。

1)使用全局变量通信

定义一个全局变量,用volatile修饰符,它告诉编译器无需对该变量作任何优化,并且该值可被外部改变。

2)使用自定义消息通信

通过消息机制完成。

 

5、线程的同步

使隶属于同一进程的各线程协调一致工作称为线程的同步。MFC提供了多种同步对象,常用的同步对象如下:

1)临界区

当多个线程访问一个独占性共享资源时,使用临界区对象,任一时刻只有一个线程可以拥有临界区对象,其他希望进入临界区的线程将被挂起等待,直到拥有临界区的线程放弃临界区时为止,这样就保证了不会在同一时刻出现多个线程访问共享资源。

2)事件

CEvent类提供了对事件(CEvent)的支持,事件是允许一个线程在某种情况发生时候唤醒另外一个线程的同步对象。例如:A线程负责监听通信端口,B线程负责更新用户数据,通过CEvent类,A可以通知B何时更新用户数据。

每一个CEvent对象可以有两种状态:置信状态和非置信状态。

CEvent类对象有两种类型:人工事件和自动事件。

3)互斥

互斥对象和临界区对象比较类似,其差别在于,互斥对象不仅可以在同一进程内的各个线程间使用,还可以在进程间使用,而临界区对象只能在同一进程的各个线程间使用。临界区比互斥更节省系统资源。

4)信号量

信号量指被CSemaphore类对象所控制的资源可以同时接受访问的最大线程数。

 

  • 工作线程函数

    UINT tcp_server_fun_echo(LPVOID pParam)
    {
    	int iResult = 0;
    	char recvline[MAXLINE];
    	int err;
    
    	//将输入参数转换为连接套接字
    	SOCKET s = *((SOCKET *)pParam);
    	do {
    		memset(recvline, 0, MAXLINE);
    		//接收数据
    		iResult = recv(s, recvline, MAXLINE, 0);
    		if (iResult > 0)
    		{
    			printf("服务器接收到数据: %s\n",recvline);
    			//回射发送已收到的数据
    			iResult = send(s, recvline, MAXLINE, 0);
    			if (iResult == SOCKET_ERROR)
    			{
    				printf("send 函数调用错误,错误号:%d\n", WSAGetLastError());
    				err = closesocket(s);
    				if(err == SOCKET_ERROR)
    					printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());
    				iResult = -1;
    			}
    			else
    				printf("服务器发送数据: %s\n", recvline);
    		}
    		else
    		{
    			if (iResult == 0)
    				printf("对方连接关闭,退出\n");
    			else
    			{
    				printf("recv 函数调用错误,错误号:%d\n", WSAGetLastError());
    				iResult = -1;
    			}
    			err = closesocket(s);
    			if (err == SOCKET_ERROR)
    				printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());
    			break;
    		}
    	} while (iResult > 0);
    
    		return iResult;
    }

     

  • 代码

  1. mysocket.h
#pragma once

#include <time.h>
//#include <winsock2.h>
#include <stdio.h>
#include <AFXWIN.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;

#define MAXLINE 4096	//接收缓冲区长度
#define LISTENQ 1024	//监听队列长度
#define SERVER_PORT 13	//时间同步服务器端口号

int start_up(void);		//初始化Windows Sockets DLL;协商版本号
int clean_up(void);		//windows sockets资源释放函数
int set_address(char *hname, char * sname, struct sockaddr_in * sap, char * protocol);		//地址转换函数
int quit(SOCKET s);		//退出处理函数

SOCKET tcp_server(char *hname, char *sname);		//服务器初始化函数(字符类型)
SOCKET tcp_server(ULONG uIP, USHORT uPort);			//服务器初始化函数(无符号长整型)
SOCKET tcp_client(char *hname, char *sname);		//客户端初始化函数(字符类型)
SOCKET tcp_client(ULONG uIP, USHORT uPort);			//客户端初始化函数(无符号长整型)

int tcp_server_fun_echo(SOCKET s);		//回射函数
int tcp_client_fun_echo(FILE *fp,SOCKET s);		//回射函数
UINT tcp_server_fun_echo(LPVOID pParam);		//工作线程完成了在特定连接上接收客户端数据,并将数据发回的功能,直到客户端关闭连接或网络操作发生错误

   2.mysocket.cpp

#include "mysocket.h"

//初始化Windows Sockets DLL;协商版本号
int start_up(void)
{
	//初始化Windows Sockets DLL;协商版本号
	WORD wVersionRequested;
	WSADATA wsaData;
	int iResult;

	//使用MAKEWORD(lowbyte, highbyte) 宏,在windef.h中声明
	wVersionRequested = MAKEWORD(2, 2);
	iResult = WSAStartup(wVersionRequested, &wsaData);
	if (iResult != 0)
	{
		printf("WSAStartup函数调用错误,错误号:%d\n", WSAGetLastError());
		return -1;
	}
	else
	{
		printf("初始化成功!\n");
	}

	return 0;
}

//windows sockets资源释放函数
int clean_up(void)
{
	int iResult;

	iResult = WSACleanup();
	if (iResult == SOCKET_ERROR)
	{
		printf(" WSACleanup函数调用错误,错误号:%d\n", WSAGetLastError());
		return 1;
	}
	else
		printf("WSACleanup 函数调用错误,错误号:%d\n", WSAGetLastError());
	
	return 0;
}

//地址转换函数
int set_address(char *hname, char * sname, struct sockaddr_in * sap, char * protocol)
{
	struct servent *sp;
	struct hostent *hp;
	char *endptr;
	unsigned short port;
	unsigned long ulAddr = INADDR_NONE;

	//将地址结构socketaddr_in初始化为0,并设置地址为AF_INET
	memset( sap, 0, sizeof( *sp));
	sap->sin_family = AF_INET;
	if (hname != NULL)
	{
		//如果hname不为空,假定给出的hname为点分十进制表示的数字地址,转换地址为sockaddr_in类型
		ulAddr = inet_addr(hname); 
		if (ulAddr == INADDR_NONE || ulAddr == INADDR_ANY)
		{
			//调用错误,表明给出的是主机名,调用gethostbyname获得主机地址
			hp = gethostbyname(hname); 
			if (hp = NULL)
			{
				printf("未知的主机名,错误号:%d\n", WSAGetLastError());
				return -1;
			}
			
			sap->sin_addr = *(struct in_addr *)hp->h_addr; 
		}
		else
			sap->sin_addr.S_un.S_addr = ulAddr;
	}
	else
		sap->sin_addr.s_addr = htonl(INADDR_ANY);
	port = (unsigned short)strtol(sname, &endptr, 0);
	if (*endptr == '\0')
	{
		sap->sin_port = htons(port);
	}
	else
	{
		sp = getservbyname(sname, protocol);
		if (sp == NULL)
		{
			printf("未知的服务,错误号;%d\n",WSAGetLastError());
			return -1;
		}
		sap->sin_port = sp->s_port;
	}
	return 0;
}

//退出处理函数
int quit(SOCKET s)
{
	int iResult = 0;
	iResult = closesocket(s);
	if (iResult == SOCKET_ERROR)
	{
		printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());
		return -1;
	}
	iResult = clean_up();
	return iResult;
}

//服务器初始化函数(字符类型)
SOCKET tcp_server(char *hname, char *sname)
{
	sockaddr_in local;
	SOCKET ListenSocket;
	const int on = 1;
	int iResult = 0;

	//为服务器的本地地址local设置用户输入的IP和端口号
	if (set_address(hname, sname, &local, "tcp") != 0)
		return -1;

	//创建套接字
	ListenSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (ListenSocket == INVALID_SOCKET)
	{
		printf("socket函数调用错误,错误号:%d\n",WSAGetLastError());
		clean_up();
		return -1;
	}

	//绑定服务器地址
	iResult = bind(ListenSocket, (struct sockaddr *) & local, sizeof(local));
	if (iResult == SOCKET_ERROR)
	{
		printf("bind 函数调用错误,错误号:%d\n", WSAGetLastError());
		quit(ListenSocket);
		return -1;
	}

	//设置服务器位监听状态,监听队列长度为LISTENQ
	iResult = listen(ListenSocket, SOMAXCONN);
	if (iResult == SOCKET_ERROR)
	{
		printf("listen 函数调用错误,错误号:%d\n", WSAGetLastError());
		quit(ListenSocket);
		return -1;
	}
	return ListenSocket;
}

//服务器初始化函数(无符号长整型)
SOCKET tcp_server(ULONG uIP, USHORT uPort)
{
	sockaddr_in local;
	SOCKET ListenSocket;
	const int on = 1;
	int iResult = 0;

	//为服务器的本地地址local设置用户输入的IP和端口号
	memset(&local, 0, sizeof(local));
	local.sin_family = AF_INET;
	local.sin_addr.S_un.S_addr = htonl(uIP);
	local.sin_port = htons(uPort);

	//创建套接字
	ListenSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (ListenSocket == INVALID_SOCKET)
	{
		printf("socket函数调用错误,错误号:%d\n", WSAGetLastError());
		clean_up();
		return -1;
	}

	//绑定服务器地址
	iResult = bind(ListenSocket, (struct sockaddr *) & local, sizeof(local));
	if (iResult == SOCKET_ERROR)
	{
		printf("bind 函数调用错误,错误号:%d\n", WSAGetLastError());
		quit(ListenSocket);
		return -1;
	}

	//设置服务器位监听状态,监听队列长度为LISTENQ
	iResult = listen(ListenSocket, SOMAXCONN);
	if (iResult == SOCKET_ERROR)
	{
		printf("listen 函数调用错误,错误号:%d\n", WSAGetLastError());
		quit(ListenSocket);
		return -1;
	}
	return ListenSocket;
}

//客户端初始化函数(字符类型)
SOCKET tcp_client(char *hname, char *sname)
{
	struct sockaddr_in peer;
	SOCKET ClientSocket;
	int iResult = 0;

	//为服务器的本地地址local设置用户输入的IP和端口号
	if (set_address(hname, sname, &peer, "tcp") != 0)
		return -1;

	//创建套接字
	ClientSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (ClientSocket == INVALID_SOCKET)
	{
		printf("socket函数调用错误,错误号:%d\n", WSAGetLastError());
		clean_up();
		return -1;
	}

	//绑定服务器地址
	iResult = connect(ClientSocket, (struct sockaddr *) & peer, sizeof(peer));
	if (iResult == SOCKET_ERROR)
	{
		printf("bind 函数调用错误,错误号:%d\n", WSAGetLastError());
		quit(ClientSocket);
		return -1;
	}

	return ClientSocket;
}

//客户端初始化函数(无符号长整型)
SOCKET tcp_client(ULONG uIP, USHORT uPort)
{
	struct sockaddr_in peer;
	SOCKET ClientSocket;
	int iResult = 0;

	//为服务器的本地地址local设置用户输入的IP和端口号
	memset(&peer, 0, sizeof(peer));
	peer.sin_family = AF_INET;
	peer.sin_addr.S_un.S_addr = htonl(uIP);
	peer.sin_port = htons(uPort);

	//创建套接字
	ClientSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (ClientSocket == INVALID_SOCKET)
	{
		printf("socket函数调用错误,错误号:%d\n", WSAGetLastError());
		clean_up();
		return -1;
	}

	//绑定服务器地址
	iResult = connect(ClientSocket, (struct sockaddr *) & peer, sizeof(peer));
	if (iResult == SOCKET_ERROR)
	{
		printf("bind 函数调用错误,错误号:%d\n", WSAGetLastError());
		quit(ClientSocket);
		return -1;
	}

	return ClientSocket;
}

//回射函数
int tcp_server_fun_echo(SOCKET s)
{
	int iResult = 0;
	
	char recvline[MAXLINE];
	do {
		memset(recvline, 0, MAXLINE);
		//接收数据
		iResult = recv(s, recvline, MAXLINE, 0);
		if (iResult > 0)
		{
			printf("服务器接收到数据%s\n", recvline);
			//回射发送已收到的数据
			iResult = send( s, recvline, iResult, 0);
			if (iResult == SOCKET_ERROR)
			{
				printf("send 函数调用错误,错误号:%d\n", WSAGetLastError());
				return -1;
			}
			else
				printf("对方连接关闭,退出\n");
		}
		else
		{
			if (iResult == 0)
				printf("对方连接关闭,退出\n");
			else
			{
				printf("recv 函数调用错误,错误号:%d\n", WSAGetLastError());
				return -1;
			}
			break;
		}
	} while (iResult > 0);
	return iResult;
}

//回射函数
int tcp_client_fun_echo(FILE *fp, SOCKET s)
{
	int iResult;
	char sendline[MAXLINE], recvline[MAXLINE];
	memset(sendline, 0, MAXLINE);
	memset(recvline, 0, MAXLINE);

	while (fgets(sendline, MAXLINE, fp) != NULL)
	{
		if (*sendline == 'Q')
		{
			printf("input end!\n");
			iResult = shutdown(s, SD_SEND);
			if (iResult == SOCKET_ERROR)
			{
				printf("shutdown failed with error:%d\n", WSAGetLastError());
			}
			return 0;
		}
		iResult = send(s, sendline, strlen(sendline), 0);
		if (iResult == SOCKET_ERROR)
		{
			printf("send 函数调用错误,错误号:%d\n", WSAGetLastError());
			return -1;
		}
		printf("\r\n客户端发送数据:%s\r\n", sendline);
		memset(recvline,0,MAXLINE);
		iResult = recv(s,recvline,MAXLINE,0);
		if (iResult > 0)
			printf("客户端接收到数据 %s \r\n",recvline);
		else
		{
			if (iResult == 0)
				printf("服务器终止!\n");
			else
				printf("recv 函数调用错误,错误号:%d\n", WSAGetLastError());
			break;
		}
		memset(sendline, 0, MAXLINE);
	}
	return iResult;
}

//工作线程完成了在特定连接上接收客户端数据,并将数据发回的功能,直到客户端关闭连接或网络操作发生错误
UINT tcp_server_fun_echo(LPVOID pParam)
{
	int iResult = 0;
	char recvline[MAXLINE];
	int err;

	//将输入参数转换为连接套接字
	SOCKET s = *((SOCKET *)pParam);
	do {
		memset(recvline, 0, MAXLINE);
		//接收数据
		iResult = recv(s, recvline, MAXLINE, 0);
		if (iResult > 0)
		{
			printf("服务器接收到数据: %s\n",recvline);
			//回射发送已收到的数据
			iResult = send(s, recvline, MAXLINE, 0);
			if (iResult == SOCKET_ERROR)
			{
				printf("send 函数调用错误,错误号:%d\n", WSAGetLastError());
				err = closesocket(s);
				if(err == SOCKET_ERROR)
					printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());
				iResult = -1;
			}
			else
				printf("服务器发送数据: %s\n", recvline);
		}
		else
		{
			if (iResult == 0)
				printf("对方连接关闭,退出\n");
			else
			{
				printf("recv 函数调用错误,错误号:%d\n", WSAGetLastError());
				iResult = -1;
			}
			err = closesocket(s);
			if (err == SOCKET_ERROR)
				printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());
			break;
		}
	} while (iResult > 0);

		return iResult;
}









    3.server.cpp

#include "mysocket.h"
#define ECHOPORT "7210"

//回射主函数
/*
int main(int argc, char *argv[])
{

	int iResult = 0;
	char ip[] = "127.0.0.1";
	SOCKET ListenSocket, ConnetSocket;

	start_up();
	ListenSocket = tcp_server( ip, ECHOPORT);
	if (ListenSocket == -1)
		return 1;
	printf("服务器准备好回射服务......\n");
	for (; ; )
	{
		ConnetSocket = accept(ListenSocket, NULL, NULL);
		if (ConnetSocket != INVALID_SOCKET)
		{
			printf("\r\n建立连接成功\n\n");
			iResult = tcp_server_fun_echo(ConnetSocket);
			if (iResult == -1)
				printf("当前连接已关闭或出错\n");
		}
		else
		{
			printf("accept 函数调用错误,错误号:%d\n",WSAGetLastError());
			quit(ListenSocket);
			return 1;
		}

		if(closesocket(ConnetSocket) == SOCKET_ERROR)
			printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());
	}
		
	quit(ListenSocket);
	return 0;

}
*/

//并发回射主函数 
int main(int argc, TCHAR* argv[], TCHAR* envp[])
{
	int nRetCode = 0;

	//初始化MFC,并在失败时显示错误
	if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
	{
		printf(("错误:MFC初始化失败\n"));
		nRetCode = 1;
	}
	else
	{
		int iResult = 0;
		char ip[] = "127.0.0.1";
		SOCKET ListenSocket, ConnetSocket;
		CWinThread *pThread = NULL;

		start_up();
		ListenSocket = tcp_server(ip, ECHOPORT);
		if (ListenSocket == -1)
			return -1;
		printf("服务器准备好回射服务......\n");
		for (; ; )
		{
			ConnetSocket = accept(ListenSocket, NULL, NULL);
			if (ConnetSocket != INVALID_SOCKET)
			{
				printf("\r\n建立连接成功\n\n");
			
					pThread = AfxBeginThread(tcp_server_fun_echo, &ConnetSocket);
				
			}
			else
			{
				printf("accept 函数调用错误,错误号:%d\n", WSAGetLastError());
				quit(ListenSocket);
				return -1;
			}
		}

		return nRetCode;
	}
}

   4.client.c

#include "mysocket.h"
#define ECHOPORT "7210"

int main(int argc, char *argv[])
{

	int iResult = 0;
	SOCKET ClientSocket;

	start_up();

	printf("连接建立成功,请输入回射字符串......\n");
	ClientSocket = tcp_client((char *)argv[1], ECHOPORT);
	if (ClientSocket == -1)
		return 1;
	iResult = tcp_client_fun_echo(stdin,ClientSocket);
	quit(ClientSocket);
	return iResult;

}

//工作线程完成了在特定连接上接收客户端数据,并将数据发回的功能,直到客户端关闭连接或网络操作发生错误
UINT tcp_server_fun_echo(LPVOID pParam)
{
	int iResult = 0;
	char recvline[MAXLINE];
	int err;

	//将输入参数转换为连接套接字
	SOCKET s = *((SOCKET *)pParam);
	do {
		memset(recvline, 0, MAXLINE);
		//接收数据
		iResult = recv(s, recvline, MAXLINE, 0);
		if (iResult > 0)
		{
			printf("服务器接收到数据: %s\n", recvline);
			//回射发送已收到的数据
			iResult = send(s, recvline, MAXLINE, 0);
			if (iResult > 0)
			{
				printf("send 函数调用错误,错误号:%d\n", WSAGetLastError());
				err = closesocket(s);
				if (err == SOCKET_ERROR)
					printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());
				iResult = -1;
			}
			else
				printf("服务器发送数据: %s\n", recvline);
		}
		else
		{
			if (iResult == 0)
				printf("对方连接关闭,退出\n");
			else
			{
				printf("recv 函数调用错误,错误号:%d\n", WSAGetLastError());
				iResult = -1;
			}
			err = closesocket(s);
			if (err == SOCKET_ERROR)
				printf("closesocket 函数调用错误,错误号:%d\n", WSAGetLastError());
			break;
		}
	} while (iResult > 0);

	return iResult;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值