网络编程笔记

来源:互联网 发布:淘宝魔盒 编辑:程序博客网 时间:2024/06/02 07:53
 

二种线程同步的方式---事件对象
件对象也属于内核对象,包含一个使用计数,一个用于指明该事件对象是一个自动重置的事件还是一个人工重置的事件的布尔值,另一个用于指明该事件处于已通知状态还是未通知状态的布尔值。
有两种不同类型的事件对象,一种是人工重置的事件,另一种是自动重置的事件。当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度线程(所谓可调度线程就是当CPU时间片到来时,线程可以执行)当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。
当一个线程等待到一个人工重置的事件对象之后,该事件仍然处于有信号状态,所以别的线程也可以获得该事件的所有全,变为可调度线程。所以,人工重置的事件对象得到通知时,所有等待该事件对象的线程都变为可调度线程。
线程一旦获得了某个事件对象且使其处于了无信号状态,线程内的执行代码就变为受保护代码,在此期间别的线程不可以运行,即使别的线程获得了CPU的时间片。
如果一个事件始终处于无信号状态,则别的线程都无发请求获得该事件,别的线程都无法执行,只有次线程可以执行。
HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL bManualReset,
  BOOL bInitialState,
  LPCTSTR lpName
);创建或着打开一个命名的或者没有命名的事件对象。
BOOL bManualReset:TRUE--人工重置,FALSE--自动重置。
如果选择了TRUE(人工重置),则必须调用ResetEvent(handle)函数来重置这个事件为无信号状态。
如果为FALSE(自动重置事件),则当一个等待线程被释放以后,系统自动设定这个事件为无信号状态。
也就是说,如果我们创建的是人工重置的事件对象,当我们等待到事件对象之后,我们需要手动的调用ResetEvent()函数将这个事件设为无信号状态。
如果我们创建的是自动重置的事件对象,当一个线程等待到这个事件对象之后,系统将自动将这个事件对象设置为无信号状态。
BOOL bInitialState:设置事件创建时的初始状态。TRUE--有信号,FALSE--无信号。
如果在创建一个对象时将bInitialState设置为了无信号状态(FALSE),则别的线程就不能再申请到此事件对象直到该事
件对象被该线程释放。

BOOL SetEvent(
  HANDLE hEvent
);设定指定的对象为有信号状态。

在单CPU的系统中,同一时刻只能有一个线程运行。当一个线程已经进入到被保护的代码运行后,即使再把它的事件对象设置为非信号状态,他也不能再起作用了。人工重置的事件对象除非人工调用SetEvent将其设置为有信号状态,否则它将始终处于非信号状态。

对于线程同步间的同步来讲,我们不要采用人工重置的事件对象。


在线程同步时,一个线程获得了一个自动重置的事件对象后, 系统自动将这个事件对象设置为非信号状态,这个时候其他线程无法获取此事件对象,只能等待,这个线程也不能再一次获取这个事件对象。
所以,当一个线程获取了事件对象执行完受保护的代码后,应该用SetEvent()将这个事件对象设置为有信号状态。
在主线程退出之前要调用CloseHandle()--关闭事件对象。

通过创建一个命名的事件对象,我们也可以完成让一个程序只有一个实例运行,例如金山词霸;用创建命名的互斥对象也可以做到。
第三种线程同步的方式--关键代码段
关键代码段也叫做临界区,他工作在用户方式下。
关键代码段(临界区)是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。
我们可以把能够访问同一种资源的代码段叫做关键代码段。关键代码段非常类似与公用电话厅,当我们进入公用电话亭之前要判断一下电话亭当中是否有人。如果已经有人在使用这种资源,那么我们就只能在外面等候,当别人用完了电话我们才能用。

void EnterCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);
等待指定的临界区对象的所有权,当调用线程被赋予所有权后,这个函数就返回。否则该函数将一直等待。从而导致我们线程的等待。
LPCRITICAL_SECTION lpCriticalSection:指向关键代码段结构体的一个指针。其功能类似于建立一个公用电话亭。

void InitializeCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);
void LeaveCriticalSection
( LPCRITICAL_SECTION
lpCriticalSection );
释放关键代码段的所有权。这样别的线程就可以获得关键代码段的所有权。

void DeleteCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);当关键的代码段不需要再存在的时候可以调用这个函数,该函数释放一个没有被拥有的临界区对象相关的所有资源。
线程死锁
哲学家进餐的问题:
有一群哲学家在一起共进晚餐, 但是每个哲学家手上都只有跟筷子,这个时候如果有一个哲学家把筷子交出来让其他的哲学家先吃,别人吃完之后,别人再将筷子交回来,那么这些问都可以完成进餐。但是所有的哲学家都这么想,都想让别人把筷子交出来自己先吃。最终,这些哲学家都只能看着满桌子的食物而都没法进餐。
这就是线程死锁的描述。
线程1拥有了临界区对象A,等待临界区对象B的所有权;线程2拥有了临界区对象B,等待临界区对象B的所有权,这样就造成了死锁。
所以在做多线程同步时,要避免发生线程的死锁。
三种线程同步方法--互斥对象,事件对象,关键代码段(临界区)的比较


1--互斥对象和事件对象属于内核对象,利用内核对象进行线程同步,速度较慢,但利用互斥对象和事件对象这样的内核对象可以在多个进程中的各个线程间进行同步。
2--关键代码段是工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时无法设定超时值。


我们在进行多线程同步时,首选的是关键代码段。如果是在MFC程序中使用,我们可以在一个类的构造函数中去调用InitializeCriticalSection(),在类的析构函数中去调用DeleteCriticalSection(),在我们所要保护的代码的前面调用EnterCriticalSection(),在访问完我们调用的资源之后调用LeaveCriticalSection()。使用关键代码段时一定要注意,在调用了EnterCriticalSection只有一定要调用LeaveCriticalSection,否则,其他线程都没有办法执行。
如果构造在多个临街区对象,要注意线程的死锁,

基于消息的异步套接字
1---Windows套接字在两种模式下执行I/O操作,阻塞和非阻塞。在阻塞模式下,在I/O操作完成前,执行操作的Winsock函数会一直等待下去,不会立即返回程序(将控制权交还给程序)。而在非阻塞模式下,Winsock函数无论如何都会立即返回。譬如我们调用一个receive()函数,如果这个时候网路上没有数据到来,receive函数就会阻塞,导致我们的线程暂停运行,而不会影响主程序的运行。
对于非阻塞,Winsock函数无论如何都会立即返回,当函数执行完成之后,如果我们接收的数据操作完成了,系统会采用一种方式通知我们,我们就会知道我们的接收操作是正常完成了还是出错了。(这里还是不太明白)
2---Windows sockets为了支持Windows的消息机制,使应用程序开发者能够方便的处理网络通信,他对网络事件采用了基于消息的异步存取策略。在Windows环境下,因为阻塞套接字在很多应用环境当中,会影响我们程序的性能,所以在有些情况下我们需要采用非阻塞的套接字编程。工作在非阻塞模式下有多种机制,其中一种是异步选择机制。
3--Windows Sockets的异步选择函数WSAAsyncSelect()提供了消息机制的网络事件选择,当使用它登记的网络事件发生时,Windows应用程序相应的窗口函数将收到一个消息,消息中指示了发生的网路事件,以及与事件相关的一些信息。譬如登记一个网络读取事件,那么一旦有数据到来的时候,就会触发这个事件,操作系统就会通过一个消息来通知我们,我们就可以在消息响应函数中去receive这个数据。采用异步选择机制能够有效的提高我们应用程序的性能。

int WSAAsyncSelect(
  SOCKET s,
  HWND hWnd,
  unsigned int wMsg,
  long lEvent
);
为一个套接字申请基于关于网络事件的通知。该函数自动设定socket为非阻塞模式。也就是说当我们调用WSAAsyncSelect去注册一个网路事件后,该套接字就为非阻塞模式。
s:要申请的套接字,
hWnd:事件发生时通知的窗口的句柄。
wMsg:事件发生时窗口接收到的消息。该消息为自定义消息。
lEvent:是我们感兴趣的网络事件的一个位掩码组合。
WSAAsyncSelect

When one of the nominated network events occurs on the specified socket s, the application's windowhWnd receives message wMsg. The wParam parameter identifies the socket on which a network event has occurred. The low word oflParam specifies the network event that has occurred. The high word of lParam contains any error code. The error code be any error as defined in Winsock2.h.

int WSAEnumProtocols(
  LPINT lpiProtocols,
  LPWSAPROTOCOL_INFO lpProtocolBuffer,
  LPDWORD lpdwBufferLength
);//取得可用的传输协议
Win32平台支持多种不同的网络协议,采用Winsock32,就可以编写可以直接使用任何一种协议的网络应用程序了。通过WSAEnumProtocols函数可以获取到系统中安装的网络协议的相关信息。
lpiProtocols:一个以NULL结尾的信息。可选,如果为NULL将返回所有可用协议的信息;否则值返回数组中列的协议的信息。
lpProtocolBuffer:一个以WSAPROTOCOL_INFO结构填充的缓冲区。
lpdwBufferLength:输入时描述了lpProtocolBuffer的长度;输出时能够存放协议信息的最小缓冲区长度。该函数不能重复调用。

SOCKET WSASocket(
  int af,
  int type,
  int protocol,
  LPWSAPROTOCOL_INFO lpProtocolInfo,
  GROUP g,
  DWORD dwFlags
);//创建一个基于某种传输协议的socket。
前三个参数同于socket(  int af,
  int type,
  int protocol)函数的参数,
LPWSAPROTOCOL_INFO lpProtocolInfo:指向一个LPWSAPROTOCOL结构体的指针,该结构指定了所创建的套接字的特性。如果该参数为NULL,winsock2 DLL自己判断决定使用那一个服务体供者,他选择能够支持规定的地址族、套接字类型、和协议值的第一个传输提供者。如果不为NULL,则套接字绑定到指定的结构WSAPROTOCOL_INFO相关的提供者。
g:保留,
dwFlags:套接字属性的描述。

int WSARecvFrom(
  SOCKET s,//套接字描述符
  LPWSABUF lpBuffers,//WSABUF结构的指针,该结构包含windows socket buffer的信息
  DWORD dwBufferCount,//windows socket buffer 的个数
  LPDWORD lpNumberOfBytesRecvd,//实际接收到的字节数
  LPDWORD lpFlags,
  struct sockaddr* lpFrom,
  LPINT lpFromlen,
  LPWSAOVERLAPPED lpOverlapped,
  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);//接收数据报, 存储源地址

typedef struct __WSABUF {  u_long len;  char FAR* buf;
} WSABUF结构。

int WSASendTo(
  SOCKET s,
  LPWSABUF lpBuffers,//WSABUF 结构体指针
  DWORD dwBufferCount,//WSABUF结构体个数
  LPDWORD lpNumberOfBytesSent,//实际发送的字节数
  DWORD dwFlags,
  const struct sockaddr* lpTo,
  int iToLen,
  LPWSAOVERLAPPED lpOverlapped,
  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);


基于消息的异步套接字编程步骤:
1--创建套接字WSASocket()
2--绑定套接字调用bind
3--调用WSAAsyncSelect()为某个套接字请求某种事件的通知
4--等事件到来后在消息相应函数中进行处理,

阻塞模式下套接字相关函数VS基于消息的异步套接字

windows下的套接字函数包含了对原始套接字函数的扩充;
1--创建套接字   socket               WSASocket();
2--关闭套接字   closesocket        closesocket
3--绑定套接字    bind               bind
4--接收数据      recvfrom            WSARecvFrom()
5--发送数据      sendto               WSASendTo()

gethostbyname()将主机名转换为 IP地址
The gethostbyname function retrieves host information corresponding to a host name from a host database.
可以通过主机名获取主机的信息, ip,别名等
内存之间可以互相转换,只要内存的数据模型是兼容的就可以。
譬如dword 类型指针指向的数据占四个字节,ulong 类型数据占四个字节
HOSTENT *pHostent;
pHostent = gethostbyname("papa");
addrTo.sin_addr.S_un.S_addr = *((DWORD*)pHost->h_adrr_list[0]);
其中, pHost->h_adrr_list[0]为 char*的网络字节序,
可以将其转变为DWORD*类型, 因为dword类型的数据与ulong类型的数据内存模型相同,所以可以直接将其值付给S_addr。DOWRD*类型取值正好取出四个字节,与ulong类型相同。


DWORD *dwP;
char *cP;
(DWROD*)cp;
ulong 类型占四个字节。

struct HOSTENT* FAR gethostbyaddr(
  const char* addr,
  int len,
  int type
);
//通过地址获取主机名
pHost = gethostbyaddr((char *)&addrFrom.sin_addr_S_un.S_addr)...)

addrFrom.sin_addr_S_un.S_addr).为ulong类型地址, 取地址后为ulong类型指针, 然后再将其转换为char *类型指针即可。

原创粉丝点击