通用基于TCP协议的C/S模型的代码

最近跟老师做一个项目,里面用到了客户端服务器模型。本科的时候也做过,但是那时候纯粹是为了完成任务,没有考虑什么东西,只是实现了单个客户和服务器的通信,发送一些图片什么的。看到老师写的代码很牛逼,直接拿以前的代码的动态库过来就可以使用了,而且工作的很好,因此有了模仿的想法。

我的目的:构造一个灵活的服务器,顺便构造处理相同类型的客户端;服务器端:windows采用select 模型 和完成端口实现两个;linux采用epoll模型实现。客户端比较简单,谈不到什么模型的概念。

2012-7-28 实现了windows端的select模型的服务器:

select模型是一个比较简单的模型,但是在构造服务器方面其有一个短处,就是接待客户端的个数,这个个数受FD_SETSIZE的限制,windows上是64, linux上是1024。作为一个服务器,一般不能假设你的client少于某个数目,所以进行了改造下,运用线程来对客户进行分组,这样就没有了客户数量的限制了。

核心代码如下:

#include <stdio.h>
#include "server.h"
#include "wrap.h"

#define PORT          5150
#define MSGSIZE       1024
#pragma comment(lib, "ws2_32.lib")
DWORD WINAPI ProcessThread(LPVOID lpParam);
DWORD WINAPI ListenThread(LPVOID lpParam);
void NewClientIn(SOCKET client);
PROCESS_HANDLER  req_handler;//回调函数,处理client请求
/*
虽然是个简单的select模型,但是还是要搞熟
问题:超过FD_SETSIZE个客户端的限制,一种方法是在#include <winsock.h>之上加上一个宏的定义,重新定义FD_SIZE为一个更大的数字但是这样做不太保险,可能引起系统的故障,因为可能和数据长度有关
第二种方法是每个FD_SETSIZE个客户分配一个线程,但是用户的加入倒还好,但是用户推出就比较难以处理,需要有一个线程的管理者来管理这些创建的线程。难点无法避免,同时也能让自己学习下线程的用法,编程学习之。
思路: 
	1.创建一个管理函数,用来管理新来的用户给哪个线程处理;若线程已满,则创建一个新的线程
	2.数据结构:client采用一个节点;处理线程一个数据结构,存放一个链表和当前的套接字的数目;管理者一个链表,链接存储所有的线程数据结构
	ok
	*/
/*
	第二步尝试:将用户请求的数据显示出来,然后回送回去 ok
*/
/*
	第三步尝试:将该程序做成一个模块,其他模块直接调用即可
	思路:首先这个程序的main函数需要改造,做成一个线程。函数自己的main函数在最后等待这个监听线程结束;
				其次,扩展的数据结构的处理需要提供一个接口,这个接口的参数有请求的类型、请求的套接字和请求的内容,作为一个线程共享的全局回调函数来调用;
	ok
	现在只需要改动自己写好处理函数然后调用InitServer即可使用,处理函数处理用户需要的信息,具体的包在wrap中定义
*/
/*
	函数调用结果均没有判断,加上去;以及临界区的管理(这个模块中链表是共享的数据结构,必须互斥访问)
*/
/*记录每个客户的socket信息*/
typedef struct{
	SOCKET client; /*clien socket*/
	LIST_HEAD sibling_list;/*client socket所在线程的下一个兄弟socket指针*/
}ClientSocket;
typedef struct{
	LIST_HEAD head;/*the list head of clients of this thread*/
	UINT16 sock_num;/*the client num of this thread*/
	LIST_HEAD sibling_list;/*thread sibling pointer*/
}ThreadSocket;
LIST_HEAD thread_head;/*global variable for managing thread*/
CRITICAL_SECTION cri_sec;/*critical section variable*/
/*
	辅助函数: 读取指定字节数的数据
*/
int recvNumBytes(SOCKET s, char *buf, int len)
{
	int left = len;
	int ret;

	while( left > 0){
		ret= recv(s, buf+len-left, left, 0);
		left -=ret;
		if(ret == 0 || (SOCKET_ERROR == ret && WSAECONNRESET == WSAGetLastError()))
			return -1;
	}
	return len;
}
/*
	此函数负责client的接入工作
*/
void NewClientIn(SOCKET client)
{
	DWORD	thread_id;
	ClientSocket *new_client;
	GBOOL if_new_thread = TRUE;
	LIST_HEAD	*list;
	ThreadSocket	*max_left_thread, *temp_thread;
	UINT16	min_connected = FD_SETSIZE;

	list_for_each(list, &thread_head)
	{
		temp_thread = ENTRY(list, ThreadSocket, sibling_list);
		if(temp_thread->sock_num < min_connected)
		{
			min_connected = temp_thread->sock_num;
			max_left_thread = temp_thread;
			if_new_thread = FALSE;
		}
	}	
	if(if_new_thread)/*we need to create a new thread to process the incoming client*/
	{
		ThreadSocket *new_thread_socket;

		new_thread_socket = (ThreadSocket *)malloc(sizeof(ThreadSocket));
		new_client = (ClientSocket *)malloc(sizeof(ClientSocket));
		if(NULL ==new_thread_socket || NULL  == new_client)
		{
			EPRINTF("ERROR: sorry, I have encountered a memory error.\n");
			exit(-1);
		}
		/*每个线程的第一个客户是不需要对数据进行互斥管理的,因为根本不会有线程访问这个线程数据*/
		/*fill in client*/
		new_client->client = client;
		INIT_LIST_NODE(&(new_client->sibling_list));
		/*fill in thread socket*/
		INIT_LIST_HEAD(&(new_thread_socket->head));
		INIT_LIST_NODE(&(new_thread_socket->sibling_list));
		list_add_tail(&(new_client->sibling_list), &(new_thread_socket->head));
		new_thread_socket->sock_num = 1;
		/*fill in global thread info*/
		list_add_tail(&(new_thread_socket->sibling_list), &(thread_head));
		/*create thread*/
		if(CreateThread(NULL, 0, ProcessThread, new_thread_socket, 0, &thread_id) == NULL)
		{
			EPRINTF("ERROR: sorry, I have encountered a thread error.\n");
			exit(-1);/*exit the system*/
		}
	}
	else
	{
		new_client = (ClientSocket *)malloc(sizeof(ClientSocket));
		if(NULL  == new_client)
		{
			EPRINTF("ERROR: sorry, I have encountered a memory error.\n");
			exit(-1);
		}
		/*enter critical section*/
		EnterCriticalSection(&cri_sec);
		/*fill in client*/
		new_client->client = client;
		INIT_LIST_NODE(&(new_client->sibling_list));
		/*add to thread info*/
		list_add_tail(&(new_client->sibling_list),&(max_left_thread->head));
		++max_left_thread->sock_num;
		/*leave critical section*/
		LeaveCriticalSection(&cri_sec);
		/*printf the thread info*/
		printf("Thread %p : client num %d\n", max_left_thread, max_left_thread->sock_num);
	}
}
/*
	处理用户请求的线程,每个线程最多处理FD_SETSIZE个客户
*/
DWORD WINAPI ProcessThread(LPVOID lpParam)
{
	ThreadSocket *thread_socket= (ThreadSocket *)lpParam;
	ClientSocket *client_socket;
	fd_set          fdread;
	char              szMessage[MSGSIZE];
	LIST_HEAD		*list;
	struct timeval 	  tv = {1, 0};
	int               ret;
	Req_head head;

	while (TRUE)
	{
		/*clear the set*/
		FD_ZERO(&fdread);
		/*add in all fd*/
		list_for_each(list, &(thread_socket->head))
		{
			client_socket = ENTRY(list, ClientSocket, sibling_list);
			FD_SET(client_socket->client, &fdread);
		}
		/*We only care read event*/
		tv.tv_sec = 1;
		ret = select(thread_socket->sock_num, &fdread, NULL, NULL, &tv);
		/*no read event occur*/
		if (ret == 0)
		{
			//Sleep(1000);/*in winxp, need to uncomment this line;win7 no need*/
			continue;
		}
		/*check which socket has read event*/
		list_for_each(list, &(thread_socket->head))
		{
			client_socket = ENTRY(list, ClientSocket, sibling_list);
			if(FD_ISSET(client_socket->client,&fdread))
			{
				/*a read event has occured*/
				ret = recvNumBytes(client_socket->client, &head, sizeof(Req_head));
				/*error occur while reading*/
				if(-1 == ret)
				{
					printf("client socket %d closed\n", client_socket->client);
					list_del(&(client_socket->sibling_list));
					--thread_socket->sock_num;
					if(0 == thread_socket->sock_num)
					{
						list_del(&(thread_socket->sibling_list));
						return 0;/*exit this thread*/
					}
				}
				else{
					/*read extra data*/
					ret = recvNumBytes(client_socket->client, szMessage, head.length - sizeof(Req_head));
					if(-1 == ret)
					{
						list_del(&(client_socket->sibling_list));
						--thread_socket->sock_num;
						if(0 == thread_socket->sock_num)
						{
							list_del(&(thread_socket->sibling_list));
							return 0;/*exit this thread*/
						}
					}
					/*process the req*/
					req_handler(client_socket->client, head.type, szMessage, ret);
				}
			}
		}//list for each
	}
	return 0;	
}
DWORD WINAPI ListenThread(LPVOID lpParam)
{
	SOCKET	socket_listen, socket_client;
	SOCKADDR_IN local, client;
	int	iaddrSize = sizeof(SOCKADDR_IN);
	int	count=0;

	/*Create listening socket*/
	if((socket_listen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
	{
		EPRINTF("ERROR: Listen socket create error.\n");
		exit(-1);
	}
	/*Bind*/
	local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	local.sin_family = AF_INET;
	local.sin_port = htons(PORT);
	if(bind(socket_listen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
	{
		EPRINTF("ERROR: Bind socket to address error.\n");
		exit(-1);
	}
	// Listen
	if(listen(socket_listen, SOMAXCONN) == SOCKET_ERROR )
	{
		EPRINTF("ERROR: Listen client error.\n");
		exit(-1);
	}
	while (TRUE)
	{
		printf("waiting for connect ...\n");
		// Accept a connection
		if((socket_client = accept(socket_listen, (struct sockaddr *)&client, &iaddrSize)) == INVALID_SOCKET )
		{
			EPRINTF("ERROR: Accept a client error.\n");
			continue;/*abondon the client*/
		}
		NewClientIn(socket_client);
		printf("Accepted %d client, This clien info:addr %s, port%d\n", ++count, inet_ntoa(client.sin_addr), ntohs(client.sin_port));
	}
}
GBOOL *InitServer(PROCESS_HANDLER handler)
{
	WSADATA        wsa_data;
	DWORD          thread_id;

	/*request handler initialize*/
	if(NULL == handler)
		return FALSE;
	req_handler = handler;
	/*init thread managerment data*/
	INIT_LIST_HEAD(&thread_head);
	/*init critical section variable*/
	InitializeCriticalSection(&cri_sec);
	/*Initialize Windows socket library*/
	WSAStartup(0x0202, &wsa_data);
	/*create listen thread*/
	if(CreateThread(NULL, 0, ListenThread, NULL, 0, &thread_id) == NULL)
	{
			EPRINTF("ERROR: sorry, I have encountered a thread error.\n");
			return FALSE;
	}
	return TRUE;
}
使用服务器很简单,先InitServer, 需要提供一个回调函数来对收到的请求进行处理。然后主函数需要一个机制来保证服务器线程退出之前,main函数不会退出。

既然是通信,肯定要设定一个协议,设定客户和服务器的数据的组织方式。我所采用的方式很简单,每个数据包分成两个部分,一个头head和一个数据块data,head中存放的是请求或者回复的类型和这个数据包的长度,data是请求的具体数据。

下面说下服务器的构造思路:

1. 首先,创建一个监听线程来接收用户的connect请求;

2. 当有client连接过来时,让一个管理函数来负责这个用户的“接待”工作:若没有负责数据通信的线程则创建一个线程来处理;若有多个线程来处理,选择一个负载最小的线程来处理;若客户退出来导致其负责通信的线程没有客户需要处理,则该线程自动退出。线程内部的管理相当简单,就是采用select模型对其负责的每个client进行询问,有请求则接收数据,然后调用InitServer指定的回调函数来处理该请求。

实现这个模型最主要的地方还是在于线程的管理,为此上面的文件中,设立了三个数据结构,采用链表对其进行了处理。

额,这个服务器的代码都在这里了,希望对其他人有参考作用。不懂可以email, rsqmail@163.com。

我来接着实现完成端口的服务器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值