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

来源:互联网 发布:c语言1到n求和 编辑:程序博客网 时间:2024/04/19 21:29

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

我的目的:构造一个灵活的服务器,顺便构造处理相同类型的客户端;服务器端: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){DWORDthread_id;ClientSocket *new_client;GBOOL if_new_thread = TRUE;LIST_HEAD*list;ThreadSocket*max_left_thread, *temp_thread;UINT16min_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){SOCKETsocket_listen, socket_client;SOCKADDR_IN local, client;intiaddrSize = sizeof(SOCKADDR_IN);intcount=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);}// Listenif(listen(socket_listen, SOMAXCONN) == SOCKET_ERROR ){EPRINTF("ERROR: Listen client error.\n");exit(-1);}while (TRUE){printf("waiting for connect ...\n");// Accept a connectionif((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。

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