转载自:http://blog.csdn.net/xuanyinliang/archive/2008/08/06/2779345.aspx
一:BSD socket编程,winsock编程,MFC socket编程。
(1) Socket是一种支持互联网通信的API,它采用客户/服务器的通信机制,使网络客户方和服务器方通过socket实现网络间的连接和数据交换。
Socket 有StreamSocket , DatagramSocket, RawSocket。数据流socket是TCP传输协议的接口,它定义了一种可靠的面向连接的服务,实现无差错无重复的数据传输。数据报socket是UDP数据报服务的接口,它定义了一个无连接的服务,数据通过相互独立的包进行传输。原始socket接口允许对底层协议(如IP,ICMP)进行直接存取,主要用于新网络协议的测试等
Accept()
功能:用于在一个套接字上接收一个连接
定义:SOCKET PASCAL FAR accept(SOCKET s , struct sockaddr FAR* addr , int FAR*addreln);
Bind()
功能:将一个本地地址与一个套接字连接。
定义:int PASCAL FAR bind( SOCKET s , const struct sockaddr FAR*name ,int namelen);
Closesocket()
功能:关闭一个套接字。
定义:int PASCAL FAR closesocket( SOCKET s );
Getsockname()
功能:获得一个套接字的本地名字。
定义:int PASCAL FAR getsockname ( SOCKET s , struct socksddr FAR*name , int FAR* namelen);
Listen()
功能:创建一个套接字并监听申请的连接。
定义:int PASCAL FAR listen( SOCKET s , int backlog);
Recv ()
功能:从已连接的数据报或流式套接字接收数据。
定义; int PASCAL FAR recv ( SOCKET s , char FAR* buf , int len , int flags) ;
Send()
功能:用于向一个已连接的套接字发送数据。
定义; int PASCAL send ( SOCKET s , const FAR* buf , int len , int flags);
Socket()
功能; 创建一个套接字。
定义; SOCKET PASCAL FAR socket( int af , int type , int protocol) ;
BSD SOCKET 提供的服务函数
gethostbyaddr()获取网络名一致的名和地址。gethostnam()获取本地主机名。
Gethostbyname()获取本地主机一致的名和地址。Getprotobyneme()获取协议名一致的协议名和协议号。Getprotobynumber()获得与协议号一致的协议名和协议号。
Getservbyname() 获得服务名一致的服务名和端口。Getservbyport()获得与服务端口 一致的服务名和端口。
(2)winsock编程
Winsock是在于BSD socket 保持一致的前提下,做了必要的扩充。
WSAAsyncGetXByY()
功能:这一系列函数是GetXByY()的异步版本,可以异步实现原函数的功能。
定义:#include <wunsock.h> HANDLE PASCAL FAR WSAAsyncGetXByY(HWND hWnd ,unsigned int wMsg…… , char FAR* buf , int buflen)
WSAAsyncSelect()
功能:对套接字请求事件给出通知。
定义:#include <winsock.h> int PASCAL FAR WSAAsyncSelect(SOCKET s , HWND hwnd , unsigned int wMsg , long lEvent)
WSACancelAsyncRequest()
功能:取消一个未完成的异步操作。
定义:#include <winsock.h> int PASCAL FAR WSACancleAsyncRequest( HANDLE hAsncTaskHandle )
WSACancleBlockingCall()
功能:取消当前正在运行的阻塞调用。
定义:#include <winsock.h> int PASCAL FAR WSACancleBlockingCsll( void)
WASCleanup()
功能:中断使用Windows socket DLL
定义:#include <winsock.h> int PASCAL FAR WASCleanup()
WASGetLastError()
功能; 获取上一次失败操作的错误代码
定义:#include <winsock.h> int PASCAL FAR WASGetLastError(void)
WASIsBlocking()
功能:判断当前是否正有调用被阻塞。
定义:#include <winsock.h> BOOLL PASCAL FAR WASIsBlocking( void)
WASSetblockingHook()
WSASetLastError()
WSASartup()
WSAUnhookBlockingHook()
WSAEnumProtocols()
WSASocket()
WSAEnumNameSpaceProviders(0
WSAStringToAddress()
WSAAddressToString()
WSACreateEvent()
WSACloseEvent()
WSASetEvent()
WSAEventSelect()
(3)MFC socket 编程
MFC用两个Winsck API类来进行封装:CAsyncSocket类和 CSocket类。
CSocket类 由CAsyncSocket类派生,封装性较高。
二 结构体
struct sockaddr这个结构为许多类型的套接字储存套接字地址信息:
struct sockaddr {
unsigned short sa_family; /* 地址家族, AF_xxx */
char sa_data[14]; /*14字节协议地址*/
};
sa_family 能够是各种各样的类型,在intenet中一般为 "AF_INET"。 sa_data包含套接字中的目标地址和端口信息。
为了处理struct sockaddr,程序员创造了一个并列的结构: struct sockaddr_in ("in" 代表 "Internet"。)
struct sockaddr_in {
short int sin_family; /* 通信类型 */
unsigned short int sin_port; /* 端口 */
struct in_addr sin_addr; /* Internet 地址 */
unsigned char sin_zero[8]; /* 与sockaddr结构的长度相同*/
};
用这个数据结构可以轻松处理套接字地址的基本元素。 sin_zero (它被加入到这个结构,并且长度和 struct sockaddr 一样) 应该使用函数 bzero() 或 memset() 来全部置零。 同时,这一重要的字节,一个指向 sockaddr_in结构体的指针也可以被指向结构体sockaddr并且代替它。这 样的话即使 socket() 想要的是 struct sockaddr *,仍然可以使用 struct sockaddr_in,并且在最后转换。同时,注意 sin_family 和 struct sockaddr 中的 sa_family 一致并能够设置为 "AF_INET"。最后,sin_port和 sin_addr 必须是网络字节顺序 (Network Byte Order)
数据结构: struct in_addr, 有这样一个联合 (unions): /* Internet 地址 (一个与历史有关的结构) */
struct in_addr {
unsigned long s_addr;
};
它曾经是个最坏的联合,但是现在那些日子过去了。如果你声明 "ina" 是数据结构 struct sockaddr_in 的实例,那么 "ina.sin_addr.s_addr" 就储 存4字节的 IP 地址(使用网络字节顺序)。如果你不幸的系统使用的还是恐 怖的联合 struct in_addr ,你还是可以放心4字节的 IP 地址并且和上面 一样(这是因为使用了“#define”。)
三:实现异步性
listen()函数开始侦听,再通过accept()调用等待接收连接以完成连接的建立:
//连接请求队列长度为1,即只允许有一个请求,若有多个请求,
//则出现错误,给出错误代码WSAECONNREFUSED。
listen(sock,1);
//开启线程避免主程序的阻塞
AfxBeginThread(Server,NULL);
……
UINT Server(LPVOID lpVoid)
{
……
int nLen=sizeof(SOCKADDR);
pView-> newskt=accept(pView-> sock,(LPSOCKADDR)& pView-> sockin,(LPINT)& nLen);
……
WSAAsyncSelect(pView-> newskt,pView-> m_hWnd,WM_SOCKET_MSG,FD_READ|FD_CLOSE);
return 1;
}
这里之所以把accept()放到一个线程中去是因为在执行到该函数时如没有客户连接服务器的请求到来,服务器就会停在accept语句上等待连接请求的到来,这势必会引起程序的阻塞,虽然也可以通过设置套接字为非阻塞方式使在没有客户等待时可以使accept()函数调用立即返回,但这种轮询套接字的方式会使CPU处于忙等待方式,从而降低程序的运行效率大大浪费系统资源。考虑到这种情况,将套接字设置为阻塞工作方式,并为其单独开辟一个子线程,将其阻塞控制在子线程范围内而不会造成整个应用程序的阻塞。对于网络事件的响应显然要采取异步选择机制,只有采取这种方式才可以在由网络对方所引起的不可预知的网络事件发生时能马上在进程中做出及时的响应处理,而在没有网络事件到达时则可以处理其他事件,这种效率是很高的,而且完全符合Windows所标榜的消息触发原则。前面那段代码中的WSAAsyncSelect()函数便是实现网络事件异步选择的核心函数。
通过第四个参数注册应用程序感兴取的网络事件,在这里通过FD_READ|FD_CLOSE指定了网络读和网络断开两种事件,当这种事件发生时变会发出由第三个参数指定的自定义消息WM_SOCKET_MSG,接收该消息的窗口通过第二个参数指定其句柄。在消息处理函数中可以通过对消息参数低字节进行判断而区别出发生的是何种网络事件:
void CNetServerView::OnSocket(WPARAM wParam,LPARAM lParam)
{
int iReadLen=0;
int message=lParam & 0x0000FFFF;
switch(message)
{
case FD_READ://读事件发生。此时有字符到达,需要进行接收处理
char cDataBuffer[MTU*10];
//通过套接字接收信息
iReadLen = recv(newskt,cDataBuffer,MTU*10,0);
//将信息保存到文件
if(!file.Open("ServerFile.txt",CFile::modeReadWrite))
file.Open("E:ServerFile.txt",CFile::modeCreate|CFile::modeReadWrite);
file.SeekToEnd();
file.Write(cDataBuffer,iReadLen);
file.Close();
break;
case FD_CLOSE://网络断开事件发生。此时客户机关闭或退出。
……//进行相应的处理
break;
default:
break;
}
}
在这里需要实现对自定义消息WM_SOCKET_MSG的响应,需要在头文件和实现文件中分别添加其消息映射关系:
头文件:
//{{AFX_MSG(CNetServerView)
//}}AFX_MSG
void OnSocket(WPARAM wParam,LPARAM lParam);
DECLARE_MESSAGE_MAP()
实现文件:
BEGIN_MESSAGE_MAP(CNetServerView, CView)
//{{AFX_MSG_MAP(CNetServerView)
//}}AFX_MSG_MAP
ON_MESSAGE(WM_SOCKET_MSG,OnSocket)
END_MESSAGE_MAP()
在进行异步选择使用WSAAsyncSelect()函数时,有以下几点需要引起特别的注意:
1. 连续使用两次WSAAsyncSelect()函数时,只有第二次设置的事件有效,如:
WSAAsyncSelect(s,hwnd,wMsg1,FD_READ);
WSAAsyncSelect(s,hwnd,wMsg2,FD_CLOSE);
这样只有当FD_CLOSE事件发生时才会发送wMsg2消息。
2.可以在设置过异步选择后通过再次调用WSAAsyncSelect(s,hwnd,0,0);的形式取消在套接字上所设置的异步事件。
3.Windows Sockets DLL在一个网络事件发生后,通常只会给相应的应用程序发送一个消息,而不能发送多个消息。但通过使用一些函数隐式地允许重发此事件的消息,这样就可能再次接收到相应的消息。
4.在调用过closesocket()函数关闭套接字之后不会再发生FD_CLOSE事件。
以上基本完成了服务器方的程序设计,下面对于客户端的实现则要简单多了,在用socket()创建完套接字之后只需通过调用connect()完成同服务器的连接即可,剩下的工作同服务器完全一样:用send()/recv()发送/接收收据,用closesocket()关闭套接字:
sockin.sin_family=AF_INET; //地址族
sockin.sin_addr.S_un.S_addr=IPaddr; //指定服务器的IP地址
sockin.sin_port=m_Port; //指定连接的端口号
int nConnect=connect(sock,(LPSOCKADDR)&sockin,sizeof(sockin));
本文采取的是可靠的面向连接的流式套接字。在数据发送上有write()、writev()和send()等三个函数可供选择,其中前两种分别用于缓冲发送和集中发送,而send()则为可控缓冲发送,并且还可以指定传输控制标志为MSG_OOB进行带外数据的发送或是为MSG_DONTROUTE寻径控制选项。在信宿地址的网络号部分指定数据发送需要经过的网络接口,使其可以不经过本地寻径机制直接发送出去。这也是其同write()函数的真正区别所在。由于接收数据系统调用和发送数据系统调用是一一对应的,因此对于数据的接收,在此不再赘述,相应的三个接收函数分别为:read()、readv()和recv()。由于后者功能上的全面,本文在实现上选择了send()-recv()函数对,在具体编程中应当视具体情况的不同灵活选择适当的发送-接收函数对。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/xuanyinliang/archive/2008/08/06/2779345.aspx