当前位置:文档之家› socket连接处理

socket连接处理

socket连接处理
socket连接处理

Socket 连接处理

长链接:当数据发送完成后socket链接不断开。一直保留到异常或者是程序退出为止,这种方式的好处是不用每次去发起连接断开,在速度上可以比短连接要快一些,但是相对来说对服务器的资源压力也要大些。长链接用的范围很广,比如游戏系统,qq等等,长(常)链接一般还需要定时向服务器ping数据,以保证socket链接畅通。当ping不通服务器时,需要重新开启链接。

短链接:当一次数据发送完毕后,主动断开链接,每次发送数据都要一次链接、断开操作,这种方式的好处是:对服务器的资源占用相对来说比较小,但是由于每次都要重新链接,速度开销上也比较大,这种方式对于那种不需要经常与服务器交互的情况下比较适用。

上面两种方法在用户量非常大的情况下都存在着很大的不足,因此,我们考虑可以用一种折衷的办法,那就是使用socket的连接池。

程序一开始初始化创建若干数量的长链接。给他们设置一个标识位,这个标识位表示该链接是否空闲的状态。当需要发送数据的时候,系统给它分配一个当前空闲的链接。同时,将得到的链接设置为“忙”,当数据发送完毕后,把链接标识位设置为“闲”,让系统可以分配给下个用户,这样使得两种方式的优点都充分的发挥出来了。杭州携购网络科技有限公司旗下的携购独立购物网(https://www.doczj.com/doc/1d16253325.html,)系统采用的就是这种方式。用户数量足够多的时候,只需要动态增加链接池的数量即可。

下面我们用具体的程序来讲解下:

首先我们声明一个socket类:

public class XieGouSocket

{

public Socket m_socket; //Socket对象

public bool m_isFree; //判断是否空闲

public int m_index; //在链接缓存池中的索引值

}

下面的函数是创建socket链接池,这里为了使代码更加清晰,我特地把异常处理部分全部取掉了。

public XieGouSocket[] m_socket; //先定义个缓冲池

public void CreateSocketPool()

{

string ip= “127.0.0.1”;

string port= 2003;

IPAddress serverIp=IPAddress.Parse(ip);

int serverPort=Convert.ToInt32(port);

IPEndPoint iep=new IPEndPoint(serverIp,serverPort);

m_socket = new XieGouSocket[200];

for(int i =0; i < 200 ; i ++)

{

m_socket[i] = new XieGouSocket();

m_socket[i].m_index = i ;

m_socket[i].m_isFree = true;

m_socket[i].m_socket =new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);

m_socket[i].m_socket.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.SendTime out,1000);

m_socket[i].m_socket.Connect(iep);

}

}

下面的函数是获取当前空闲的socket链接:

因为是多线程,所以我们需要加一个原子操作,定义一个原子变量,以防止多个线程之间抢占资源问题的发生。

private static Mutex m_mutex=new Mutex();

public static XieGouSocket GetFreeConnection()

{

m_mutex.WaitOne(); //先阻塞

for(int i =0; i < m_socket.Length ; i ++)

{

if(m_socket[i].m_isFree) //如果找到一个空闲的

{

m_socket[i].m_isFree = false;

m_mutex.ReleaseMutex();//释放资源

return m_socket[i];

}

}

//如果没有空闲的链接,要么等待,要么程序再动态创建一个链接。

m_mutex.ReleaseMutex();//释放资源

return null;

}

当数据发送完毕后,程序必须将m_isFree 设置为False。否则只使用不释放,程序很快就溢出了。

基本的思路就是这样的,大家可以在此基础上好好的改进下,这样运行的效率就比较高了。

资料引用:https://www.doczj.com/doc/1d16253325.html,/532345.html

Public Declare Function CreateFile _

Lib "kernel32" _

Alias "CreateFileA" _

(ByVal lpFileName As String, _

ByVal dwDesiredAccess As Long, _

ByVal dwShareMode As Long, _

ByRef lpSecurityAttributes As Long, _

ByVal dwCreationDisposition As Long, _

ByVal dwFlagsAndAttributes As Long, _

ByVal hTemplateFile As Long) _

As Long

Public Declare Function FormatMessage _

Lib "kernel32" _

Alias "FormatMessageA" _

(ByVal dwFlags As Long, _

ByRef lpSource As Any, _

ByVal dwMessageId As Long, _

ByVal dwLanguageZId As Long, _

ByVal lpBuffer As String, _

ByVal nSize As Long, _

ByVal Arguments As Long) _

As Long

Public Declare Function HidD_FreePreparsedData _ Lib "hid.dll" _

(ByRef PreparsedData As Long) _

As Long

Public Declare Function HidD_GetAttributes _

Lib "hid.dll" _

(ByVal HidDeviceObject As Long, _

ByRef Attributes As HIDD_ATTRIBUTES) _

As Long

'Declared as a function for consistency,

'but returns nothing. (Ignore the returned value.) Public Declare Function HidD_GetHidGuid _

Lib "hid.dll" _

(ByRef HidGuid As GUID) _

As Long

Public Declare Function HidD_GetPreparsedData _ Lib "hid.dll" _

(ByVal HidDeviceObject As Long, _

ByRef PreparsedData As Long) _

As Long

Public Declare Function HidP_GetCaps _

Lib "hid.dll" _

(ByVal PreparsedData As Long, _

ByRef Capabilities As HIDP_CAPS) _

As Long

Public Declare Function HidP_GetValueCaps _ Lib "hid.dll" _

(ByVal ReportType As Integer, _

ByRef ValueCaps As Byte, _

ByRef ValueCapsLength As Integer, _

ByVal PreparsedData As Long) _

As Long

Public Declare Function lstrcpy _

Lib "kernel32" _

Alias "lstrcpyA" _

(ByVal dest As String, _

ByVal source As Long) _

As String

Public Declare Function lstrlen _

Lib "kernel32" _

Alias "lstrlenA" _

(ByVal source As Long) _

As Long

Public Declare Function ReadFile _

Lib "kernel32" _

(ByVal hFile As Long, _

ByRef lpBuffer As Byte, _

ByVal nNumberOfBytesToRead As Long, _

ByRef lpNumberOfBytesRead As Long, _

ByVal lpOverlapped As Long) _

As Long

Public Declare Function RtlMoveMemory _ Lib "kernel32" _

(dest As Any, _

src As Any, _

ByVal Count As Long) _

As Long

Public Declare Function SetupDiCreateDeviceInfoList _

Lib "setupapi.dll" _

(ByRef ClassGuid As GUID, _

ByVal hwndParent As Long) _

As Long

Public Declare Function SetupDiDestroyDeviceInfoList _

Lib "setupapi.dll" _

(ByVal DeviceInfoSet As Long) _

As Long

Public Declare Function SetupDiEnumDeviceInterfaces _

Lib "setupapi.dll" _

(ByVal DeviceInfoSet As Long, _

ByVal DeviceInfoData As Long, _

ByRef InterfaceClassGuid As GUID, _

ByVal MemberIndex As Long, _

ByRef DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA) _ As Long

Public Declare Function SetupDiGetClassDevs _

Lib "setupapi.dll" _

Alias "SetupDiGetClassDevsA" _

(ByRef ClassGuid As GUID, _

ByVal Enumerator As String, _

ByVal hwndParent As Long, _

ByVal Flags As Long) _

As Long

Public Declare Function SetupDiGetDeviceInterfaceDetail _ Lib "setupapi.dll" _

Alias "SetupDiGetDeviceInterfaceDetailA" _

(ByVal DeviceInfoSet As Long, _

ByRef DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA, _ ByVal DeviceInterfaceDetailData As Long, _

ByVal DeviceInterfaceDetailDataSize As Long, _

ByRef RequiredSize As Long, _

ByVal DeviceInfoData As Long) _

As Long

Public Declare Function WriteFile _

Lib "kernel32" _

(ByVal hFile As Long, _

ByRef lpBuffer As Byte, _

ByVal nNumberOfBytesToWrite As Long, _

ByRef lpNumberOfBytesWritten As Long, _

ByVal lpOverlapped As Long) _

As Long

“The Windows API (Application Programming Interface) consists of the functions, messages, data structures, data types, and statements you can use in creating applications that run under Microsoft Windows. The parts of the API you use most are code elements for calling API functions from Windows. These include procedure declarations (for the Windows functions), user-defined type definitions (for data structures passed to those functions), and constant declarations (for values passed to and returned from those functions).”

-Microsoft-

“Before an application can exchange data with a HID, it has to identify the device and get information about its reports. The application first finds out what HIDs are attached to the system. It then examines information about each until it finds one with the desired attributes. For a custom device, the application can search for specific Vendor and Product IDs. Or the application can search for a device of a particular type, such as a mouse or joystick. After finding a device, the application can exchange information with it by sending and receiving reports.”

-USB Complete-

The following table lists the API functions used in establishing communications and exchanging data with a HID, such as our custom USB device. The functions are listed in a typical order that

Windows平台利用完成端口模型创建高性能网络服务器

众所周知,高并发的大型服务器程序一直面临着架构复杂、线程众多难以管理、并发性能提升困难的问题。为此,各种平台都提供系统级的高级设施来协助开发者解决这个难题,例如Linux平台的epoll。对于我们熟悉的Windows平台,则有一个名为IOCP(完成端口)的内核对象,通过它,我们可以方便地创建高并发、高性能、可伸缩的网络服务器程序。

IOCP即I/0 Completion Parts,它是迄今为止Windows平台上最为复杂的一种I/O模型,假如一个程序需要管理为数众多的套接字,那么采用这种模型往往可以达到最佳的系统性能。当你的应用程序需要同时管理数百乃个至上千个套接字的时候,而且希望随着系统内安装的CPU的数量增多,应用程序的性能也可以线性地提升时,你应该考虑使用IOCP模型。IOCP是Windows平台唯一适用于高负载服务器的一个技术,它利用一些线程帮助平衡”I/O请求”所引起的负载,这样的构架特别适用于产生所谓的”Scalable”服务器,这是一种能够籍着增加RAM、磁盘空间、CPU个数而提升应用程序效能的一种系统。

在决定使用IOCP之前,我们先来回顾一下传统的网络服务程序的工作模式。传统的工作模式大致是一个listen线程监听到来自网络的服务请求时,创建一个线程来对请求作出响应。这样,随着并发数的提高,系统中就会活跃着大量的线程,最终,疲于奔命的Windows内核不得不耗费大量的时间用于线程的上下文切换,而对于有价值的应用计算则力不从心。尽管可以引入线程池来改善线程的创建和管理,使系统的运行有条不紊!但创建这样的应用所需要的技巧和繁琐的逻辑足以使人敬而远之,你需要自己小心翼翼地处理一切!与系统提供的设施相比,无论从兼容性上还是性能上,传统的工作模式都不是最好的选择,你应该放弃”发明轮子”的念头。

本质上,利用IOCP模型,我们也需要线程池的思想。我们需要事先创建一定数量的工作线程,所有工作线程在同一个IOCP对象上阻塞,并等待一次IO操作完成,当一次IO操作完成时,被绑定的这个IOCP对象将得到状态的更新,IOCP 对象的状态更新通知会激活工作者线程的执行。

接下来,我们看看一个最小巧的利用IOCP模型的服务器应用是如何炮制并工作的。

一、首先,你需要创建IOCP内核对象。

一个系统级的API——CreateIoCompletionPort可用于IOCP对象的创建。

HANDLE CreateIoCompletionPort(

IN HANDLE FileHandle,

IN HANDLE ExistingCompletionPort,

IN ULONG_PTR CompletionKey,

IN DWORD NumberOfConcurrentThreads

);

这个函数不仅用于IOCP对象的创建,还用于将一个句柄同IOCP对象的绑定。这个函数有4个参数,但在创建IOCP对象时,我们对前3个参数都不感兴趣。第4个参数NumberOfConcurrentThreads用于告诉系统在这个IOCP对象上的线程并发数,即允许多少个线程被同时执行。为了避免频繁的线程场景切换,人们一般希望在每一个处理器上运行一个线程,这样,我们可以把这个参数设置为0,这表明为这个IOCP对象同时服务的线程数量随系统中的CPU数量而定,有多少个CPU就允许多少个线程被同时运行。

HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 0);

这样,我们就创建了一个IOCP对象,它的句柄保存在hIOCP变量中。接下来,我们就可以把IO操作的句柄与这个IOCP对象建立联系。

二、IO操作与IOCP对象的绑定。

事实上,IOCP除了接受网络套接字的绑定还可以接受WIN32的其它对象,例如文件句柄。这里,我们只关心socket句柄的绑定问题。

我们需要建立一个socket监听,然后把accept返回的socket绑定到IOCP对象上,让IOCP对象与IO操作的状态建立起联系。有关socket的建立问题在此用代码代替讨论、略过不表:

SOCKET socketServer = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

if (socketServer == INVALID_SOCKET)

{

return FALSE;

}

sockaddr_in addrServer;

ZeroMemory(&addrServer, sizeof(sockaddr_in));

addrServer.sin_family = AF_INET;

addrServer.sin_addr.s_addr = INADDR_ANY;

addrServer.sin_port = htons(usPort);

if (bind(socketServer, (sockaddr*)&addrServer, sizeof(sockaddr_in)) == SOCKET_ERROR)

{

int nError = WSAGetLastError();

closesocket(m_SocketServer);

return FALSE;

}

if (listen(socketServer, MAX_CONNECT_QUEUE) == SOCKET_ERROR)

{

closesocket(m_SocketServer);

return FALSE;

}

上面的步骤创建一个用于监听的socket并开始网络监听,然后我们使用WSAAccept不断接受来自网络的接入请求,把该函数返回的socket句柄与IOCP 对象绑定,随后,我们将在该socket上执行异步的网络操作,操作完成时IOCP 对象将使挂起的工作线程立即进入工作状态。

While (TRUE)

{

socketAccept = WSAAccept(socketServer, NULL, NULL, NULL, 0);

LPPerHandleData pPerHandleData =

(LPPerHandleData)GlobalAlloc(GPTR, sizeof(PerHandleData));

pPerHandleData->sock = socketAccept;

if (CreateIoCompletionPort((HANDLE)socketAccept, hIOCP, (ULONG_PTR)pPerHandleData , 0) == NULL)

{

ASSERT(FALSE);

}

// 接下来,就可以就可以使用WSARecv/WSASend来异步地接收/发送消息了。

WSARecv(…);

WSASend(…);

}

注意CreateIoCompletionPort的第三个参数CompletionKey,它指向一个被称作单句柄对象的自定义数据结构体地址,当IO操作完成时,工作线程籍由IOCP 对象获得这个状态的改变,IOCP将原原本本地把绑定时指定的这个结构体指针传给工作线程。我们可以把socket句柄保存在单句柄结构体中,这样,借助于这个自定义的结构体,工作线程可以明确地知道是与哪个句柄相关的操作发生了状态的改变。

可以定义类似于下面的结构体用于单句柄数据:

// 单句柄数据结构

typedef struct tagPerHandleData{

bool bUsed; // 是否正在使用

SOCKET sock;

sockaddr_in addrClient;

LPPerIoOperData lpPerIoOpDataRecv;

LPPerIoOperData lpPerIoOpDataSend;

ZJRecvBufItem stRecvBufItem;

}PerHandleData, *LPPerHandleData;

一般情况,我们并不象上述代码那样直接GlobalAlloc出来一个单句柄结构体,使用一个所谓的单句柄池来管理是更好的选择。每个新接入的socket需要单句柄数据时,我们从单句柄池中获取,socket断开后,我们再把与之相关联的单句柄归还给单句柄池。

三、创建工作线程。

在完成了IOCP的创建之后,我们最重要的工作还没有做,那就是创建工作线程。工作线程创建多少个为宜,需要根据应用的性质来确定,一般来说,推荐的数量是CPU数量的两倍略多一点。因此,我们首先获得CPU的数量,然后乘2加2。

int nThreadsNum = nNumberOfProcessors * 2 + 2;

HANDLE hThread = NULL;

for (int i=0; i

{

hThread = CreateThread(NULL, 0, WorkerThread, hIOCP, 0, NULL);

}

DWORD WINAPI WorkerThread(LPVOID lpParam)

{

HANDLE hIOCP = (HANDLE)lpParam;

DWORD dwBytesTransfered;

LPPerHandleData lpPerHandleData;

LPPerIoOperData lpPerIoOperData;

while (TRUE)

{

// 等待绑定到hIOCP指定的完成端口上的socket完成IO操作

GetQueuedCompletionStatus(hIOCP, &dwBytesTransfered, (LPDWORD)&lpPerHandleData, (LPOVERLAPPED*)&lpPerIoOperData, INFINITE);

if (dwBytesTransfered == 0 && lpPerHandleData == 0 && lpPerIoOperData == 0)

{

// WorkerThread线程收到退出消息!

break;

}

// 处理网络事件

}

}

GetQueuedCompletionStatus函数用于获取指定IOCP的”排队完成状态”,它让一个以上的线程在此函数上挂起,等待指定的IOCP的状态通知。当IOCP状态更新后,在此IOCP上挂起的线程中的某一个线程将被立即激活。该函数原型是:

BOOL GetQueuedCompletionStatus(

IN HANDLE CompletionPort,

OUT LPDWORD lpNumberOfBytesTransferred,

OUT PULONG_PTR lpCompletionKey,

OUT LPOVERLAPPED *lpOverlapped,

IN DWORD dwMilliseconds

);

参数lpNumberOfBytesTransferred是指一次IO操作完成的字节数,lpCompletionKey是我们在调用CreateIoCompletionPort绑定socket句柄与IOCP时指定的单句柄数据,前面已经提到,通过这个参数我们可以知道是在哪个socket上发生的。lpOverlapped用于获取WIN32重叠IO操作的一个重叠结果,本质上,IOCP利用了WIN32重叠IO的机制。我们使用WSASend和WSARecv 函数异步地投递发送和接收请求时,需要一个OVERLAPPED的结构体,WSASend 和WSARecv将直接返回,操作完成后这个OVERLAPPED结构体指针由GetQueuedCompletionStatus函数取回。

由于IO操作的数据在IO操作完成后是必须要让工作线程知道的,而WSASend、WSARecv及GetQueuedCompletionStatus函数均接受一个OVERLAPPED指针,因此,我们可以定义一个结构体来保存OVERLAPPED结构和IO操作的数据,让OVERLAPPED成为第一个成员,并在调用那三个函数时把参数强转成OVERLAPPED 指针。这样的结构体叫作”单IO操作数据”,它的定义可以是这样的:

// 单IO操作数据结构

typedef struct tagPerIoOperData{

WSAOVERLAPPED stOverlapped

bool bUsed;

WSABUF stDataBuf;

char szBuf[TCP_BUF_SIZE];

OperationType emOperType;

LPZJSendBufItem lpSendBuf;

tagPerHandleData* pPHD;

}PerIoOperData, *LPPerIoOperData;

至此,一个以IOCP模型建立服务器应用的基本步骤就完成了。但实际的应用还必须考虑更多的细节,迎接更多的挑战。例如,如何投递近饱和的accept请求以更快的速度响应更多网络接入请求,如何通知上层应用接收到了来自网络的数据包,以及如何向网络发送数据包……只要理解了基本的IOCP模型,其它的尽管有些繁琐,但并不复杂,基于这种模型去深入地考虑问题,一切的问题都可以迎刃而解!

如果一种类型的对象需要经常被创建、销毁,为了提高性能,我们通常需要使用“池”技术,就如线程池、T

CP连接池等一样。那么需要使用池技术的对象一般有哪些特征了?

(1)创建过程耗时

(2)不需要保存客户状态

(3)对象体积较大

(4)频繁创建/销毁

为了省事,我希望实现一个万能对象池组件,该对象池可以缓存任意类型的对象。下面给出对象池的接口:

public interface IObjectPool

{

//objType为缓存的对象的类型,cArgs为缓存对象的构造参数

bool Initialize(Type objType ,object[] cArgs ,int minNum ,int maxNum) ;

object RentObject() ;

void GiveBackObject(int objHashCode) ;

void Dispose() ;

int MinObjCount {get ;}

int MaxObjCount {get ;}

int CurObjCount {get ;}

int IdleObjCount {get ;}

event CallBackObjPool PoolShrinked ;

event CallBackObjPool MemoryUseOut ; //内存分配失败

}

public delegate void CallBackObjPool() ;

上面接口中的各个方法的含义很清楚。其中PoolShrinked表示池中的对象个数有Max变为Min。

我们可以考虑这样一种情况,当我们需要缓存的对象需要维持和一个客户之间的状态,那么也是可以的,如果是这样,所缓存的类型最好实现下面的IPooledObjSupporter接口。

public interface IPooledObjSupporter : IDisposable

{

void Reset() ; //恢复对象为初始状态,当IObjectPool.GiveBackObject时调用

}

对象在实现该接口后,就可以被对象池在收到归还的对象时重置其状态了。整个对象池的实现代码如下:

using System;

using System.Collections ;

using System.Reflection;

namespace EnterpriseServerBase.Infrastructure

{

///

/// IObjectPool 的默认实现。

///作者:朱伟 sky.zhuwei@https://www.doczj.com/doc/1d16253325.html,

///

#region ObjectPool

public class ObjectPool :IObjectPool

{

#region members

private Type destType = null ;

private object[] ctorArgs = null ;

private int minObjCount = 0 ;

private int maxObjCount = 0 ;

private int shrinkPoint = 0 ;

private Hashtable hashTableObjs = new Hashtable() ;

private Hashtable hashTableStatus = new Hashtable() ; //key - isIdle 其中key

就是hashcode

private ArrayList keyList = new ArrayList() ;

private bool supportReset = false ;

#endregion

#region IObjectPool 成员

public event CallBackObjPool PoolShrinked ;

public event CallBackObjPool MemoryUseOut ;

public bool Initialize(Type objType, object[] cArgs, int minNum, int maxNum)

{

if(minNum < 1)

{

minNum = 1 ;

}

if(maxNum < 5)

{

maxNum = 5 ;

}

this.destType = objType ;

this.ctorArgs = cArgs ;

this.minObjCount = minNum ;

this.maxObjCount = maxNum ;

double cof = 1 - ((double)minNum /(double)maxNum) ;

this.shrinkPoint = (int)(cof * minNum) ;

//缓存的类型是否支持IPooledObjSupporter接口

Type supType = typeof(IPooledObjSupporter) ;

if(supType.IsAssignableFrom(objType))

this.supportReset = true ;

}

this.InstanceObjects() ;

return true ;

}

private void InstanceObjects()

{

for(int i=0 ;i

{

this.CreateOneObject() ;

}

}

#region CreateOneObject ,DistroyOneObject

private int CreateOneObject()

{

object obj = null ;

try

{

obj = Activator.CreateInstance(this.destType ,this.ctorArgs) ;

}

catch(Exception ee) //分配内存失败!

{

ee = ee ;

this.maxObjCount = this.CurObjCount ;

{

this.minObjCount = this.CurObjCount ;

}

if(this.MemoryUseOut != null)

{

this.MemoryUseOut() ;

}

return -1 ;

}

int key = obj.GetHashCode() ;

this.hashTableObjs.Add(key ,obj) ;

this.hashTableStatus.Add(key ,true ) ;

this.keyList.Add(key) ;

return key ;

}

private void DistroyOneObject(int key)

{

object target = this.hashTableObjs[key] ;

IDisposable tar = target as IDisposable ;

if(tar != null)

{

tar.Dispose() ;

}

this.hashTableStatus.Remove(key) ;

this.keyList.Remove(key) ;

}

#endregion

public object RentObject()

{

lock(this)

{

object target = null ;

foreach(int key in this.keyList)

{

if((bool)this.hashTableStatus[key]) //isIdle

{

this.hashTableStatus[key] = false ;

target = this.hashTableObjs[key] ;

break ;

}

}

if(target == null)

{

if(this.keyList.Count < this.maxObjCount)

{

int key = this.CreateOneObject() ;

if(key != -1)

{

this.hashTableStatus[key] = false ;

target = this.hashTableObjs[key] ;

相关主题
文本预览
相关文档 最新文档