- 浏览: 245945 次
- 性别:
- 来自: 广州
文章分类
最新评论
-
xiebo1983:
http://www.sqkoo.com/show/detai ...
【转】Mysql SET NAMES 字符集解决方案 -
kangtahewumin:
很高些楼主资源,但是确实在IE中不显示表格数据,怪事
JQGrid+Struts2样例分享 -
wangchongbiao:
楼主威武啊
JQGrid+Struts2样例分享 -
wenxiang_tune:
我问你一个问题,我现在要用U盘引导进入WIN7怎么办?前几天还 ...
写入MBR的直接GRUB引导U盘启动DOS和PE实战 -
kary1109:
谢谢分享,正在研究中。
JQGrid+Struts2样例分享
原文地址:http://www.cic.tsinghua.edu.cn/jdx/book4/dlzb.htm
Windows Sockets是Microsoft Windows的网络程序设计接口,它是从Berkeley Sockets扩展而来的。Windows Sockets在继承了Berkeley Sockets主要特征的基础上,又对它进行了重要扩充。这些扩充主要是提供了一些异步函数,并增加了符合Windows消息驱动特性的网络事件异步选择机制。这些扩充有利于应用程序开发者编制符合Windows编程模式的软件,它使在Windows下开发高性能的网络程序成为可能。
6.3.1 Socket网络程序设计原理
Socket是BSD UNIX提供的网络应用编程接口,它采用客户/服务器的通讯机制,使网络客户方和服务器方通过Socket实现网络之间的连接和数据交换。Socket提供了一系列的系统调用,使用这些系统调用可以实现TCP, UDP,ICMP和IP等多种网络协议之间的通讯。
Socket有三种主要类型:stream sockets, datagram sockets 和raw sockets。 Stream socket接口定义了一种可靠的面向连接的服务,它实现了无差错无重复的顺序数据传输。它通过内置的流量控制解决了数据的拥塞,应用程序可以发送任意长度的数据,将数据当作字节流。Datagram socket接口定义了一种无连接的服务,数据通过相互独立的包进行传输,包的传输是无序的,并且不保证是否出错、丢失和重复。包长度是有限的(隐含长度为8,192Bytes,最大长度可设为32,768Bytes)。Raw socket接口允许对低层协议如IP和ICMP的直接存取,它主要用于新的网络协议实现的测试等。
下面我们通过一个面向连接的传输发生的典型情况来说明socket网络通信的实现。
服 务 器 客 户
socket( )
bind( )
listen( )
accept( )
close( )
read( )
...
write( )
close( )
read( )
...
write( )
socket( )
connect( )
Connection Establishment
wait connection for client
……
图6.3 面向连接的协议实现的Socket调用
从图6.3可以看出,客户和服务器的关系不是对称的,服务器首先启动,然后在某一时间启动客户与服务器建立连接。服务器和客户开始都必须用调用socket()建立一个套接字(socket),然后服务器调用bind()将套接字与一个本地网络地址捆扎在一起,再用调用listen()使套接字处于一种被动的准备接收状态,同时规定它的请求队列长度,之后服务器就可以调用accept()来接收连接了。客户在建立套接字之后,便可以通过调用connect()和服务器建立连接。连接建立后,客户和服务器之间就可以通过连接发送和接收数据(调用read()和write())。最后,待数据传送结束,双方调用close()关闭套接字。
6.3.2 WinSock对Socket的扩充
BSD Socket支持阻塞(blocking)和非阻塞(non_blocking)两种工作方式。在阻塞方式下工作,connect()、accept()、read()和recv()等调用在执行时都处于阻塞状态直到它成功或出错返回。在非阻塞方式下工作,这些调用是立即返回的,但是它们是否完成得靠查询才能知道。对于Windows这种非抢先多任务操作系统来说,这两种工作方式都是很难以接受的,为此,WinSock在尽量与BSD Socket保持一致外,又对它作了必要的扩充。
WinSock对BSD Socket的扩充主要是在基于消息、对网络事件的异步存取接口上。表6.2列出了WinSock扩充的函数功能。
表6.2 WinSock扩充函数功能表
函 数 名
功 能
WSAAsyncGetHostByAddr()
标准Berkeley函数getXbyY的异步版本,例如:函数WSAAsyncGetHostByName()就是提供了标准Berkeley函数gethostbyname的一种基于消息的异步实现。
WSAAsyncGetHostByName()
WSAAsyncGetProtoByName()
WSAAsyncGetProtoByNumber()
WSAAsyncGetServByName()
WSAAsyncGetServByPort()
WSAAsyncSelect()
函数select()的异步版本
WSACancelAsyncRequest()
取消函数WSAAsyncGetXByY执行中的实例
WSACancelBlockingCall()
取消一个执行中的“阻塞”API调用
WSACleanup()
终止使用隐含的Windows Sockets DLL
WSAGetLastError()
获取Windows Sockets API的最近错误号
WSAIsBlocking()
检测隐含的Windows Sockets DLL是否阻塞了一个当前线索的调用
WSASetBlockingHook()
设置应用程序自己的“阻塞”处理函数
WSASetLastError()
设置Windows Sockets API的最近错误号
WSAStartup()
初始化隐含的Windows Sockets DLL
WSAUnhookBlockingHook()
恢复原来的“阻塞”处理函数
从表6.2可以看出,WinSock的扩充功能可以分为如下几类:
(1) 异步选择机制
异步选择函数WSAAsyncSelect()允许应用程序提名一个或多个感兴趣的网络事件,所有非阻塞的网络I/O例程(如send()和recv()),不管它是已经使用还是即将使用,都可作为WSAAsyncSelect()函数选择的候选。当被提名的网络事件发生时,Windows应用程序的窗口函数将收到一个消息,消息附带的参数指示被提名过的某一网络事件。
(2) 异步请求例程
异步请求例程允许应用程序用异步方式获取请求的信息,如WSAAsyncGetXByY()类函数允许用户请求异步服务,这些功能在使用标准Berkeley函数时是阻塞的。函数WSACancelAsyncRequest()允许用户终止一个正在执行的异步请求。
(3) 阻塞处理方法
WinSock在调用处于阻塞时进入一个叫“Hook”的例程,它负责处理Windows消息,使得Windows的消息循环能够继续。WinSock还提供了两个函数(WSASetBlockingHook()和WSAUnhookBlockingHook())让用户能够设置和取消自己的阻塞处理例程。另外,函数WSAIsBlocking()可以检测调用是否阻塞,函数WSACancelBlockingCall()可以取消一个阻塞的调用。
(4) 出错处理
为了和以后的多线索环境(如Windows/NT)兼容,WinSock提供了两个出错处理函数WSAGetLastError()和WSASetLastError()来获取和设置本线索的最近错误号。
(5) 启动与终止
WinSock的应用程序在使用上述WinSock函数前,必须先调用WSAStartup()函数对Windows Sockets DLL进行初始化,以协商WinSock的版本支持,并分配必要的资源。在应用程序退出之前,应该先调用函数WSACleanup()终止对Windows Sockets DLL的使用,并释放资源,以利下一次使用。
在这些函数中,实现Windows网络实时通信的关键是异步选择函数WSAAsyncSelect()的使用,其原型如下:
int PASCAL FAR WSAAsyncSelect(SOCTET s, HWND hWnd, unsigned int wMsg, long lEvent);
它请求Windows Sockets DLL在检测到在套接字s上发生的lEvent事件时,向窗口hWnd发送一个消息wMsg。它自动地设置套接字s处于非阻塞工作方式。参数lEvent由表6.3所列事件的一个或多个组成。例如,我们要在套接字s读准备好或写准备好时接到通知,可以使用下面的语句:
rc = WSAAsyncSelect(s, hWnd, wMsg, FD_READ | FD_WRITE);
当套接字s上被提名的一个网络事件发生时,窗口hWnd将收到消息wMsg,变量lParam的低字指示网络发生的事件,高字指示错误码。应用程序就可以通过这些信息来决定自己的下一步动作。
表6.3 异步选择网络事件表
值
含 义
FD_READ
希望在套接字s收到数据(即读准备好)时接到通知
FD_WRITE
希望在套接字s可发送数据(即写准备好)时接到通知
FD_OOB
希望在套接字s上有带外数据到达时接到通知
FD_ACCEPT
希望在套接字s上有外部连接到来时接到通知
FD_CONNECT
希望在套接字s连接建立完成时接到通知
FD_CLOSE
希望在套接字s关闭时接到通知
6.3.3 网络程序示例Echo
本章的例子是一个简单的点对点网络实时通信程序Echo。实例分两部分:客户程序wecho.c与服务器程序wechos.c。其工作过程是:服务器首先启动,它创建套接字之后等待客户的连接;客户在启动后,创建套接字,然后和服务器建立连接;连接建立后,客户接收键盘输入,然后将数据发送到服务器,服务器收到数据后,只是简单地发送回来,客户将收到的数据在窗口中显示;如此循环,当客户接收到的输入数据是字母“Q”时,它关闭连接和套接字后退出,服务器在用户关闭窗口时关闭套接字。
6.3.3.1 网络客户程序
首先介绍客户程序,该源文件取名为wecho.c,其内容在下面列出。为了方便读者,在程序中关键部分采用中文注释的形式给出说明,读者可照这些注释加强对本节内容的理解。
/* This is a Sample for WinSock. It uses WinSock routine to communicate with Server through a port -- USERPORT.
Usage: wecho servername */
#include <winsock.h>
#include <windows.h>
#include <stdio.h>
#include <ctype.h>
#include <memory.h>
#include <string.h>
#include "wecho.h"
HANDLE hInst, AsyncHnd;
char server_address[256] = {0};
char buffer[MAXGETHOSTSTRUCT];
char FAR * lpBuffer = &buffer[0];
SOCKET s = 0;
int connected = 0;
struct sockaddr_in dst_addr;
struct hostent *hostaddr;
struct hostent hostnm;
unsigned short port = USERPORT; // 用户端口号,应大于1024。客户和服务器的端口号必须相同。
int sock_type = SOCK_STREAM;
BOOL InitWindow(HANDLE);
long FAR PASCAL ClientProc(HWND, unsigned, UINT, LONG);
VOID AlertUser(HWND, LPSTR);
BOOL Client(HWND);
BOOL set_select(HWND, long);
BOOL make_skt(HWND);
BOOL connect_skt(HWND);
BOOL send_pkt(HWND, int);
int receive_pkt(HWND);
VOID close_skt(VOID);
void DisplayInfo(HWND, int);
/****************************************************************************/
int PASCAL
WinMain( HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HWND hWnd;
MSG msg;
lstrcpy((LPSTR) server_address, lpCmdLine);
if (!hPrevInstance)
if (!InitWindow(hInstance))
return (NULL);
hInst = hInstance;
hWnd = CreateWindow("ClientClass", "Windows ECHO Client", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
if (!hWnd)
return (NULL);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
EnableMenuItem(GetMenu(hWnd), IDM_STOP, MF_DISABLED | MF_GRAYED);
PostMessage(hWnd, WM_USER,(WPARAM) 0, (LPARAM) 0);
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (msg.wParam);
}
BOOL
InitWindow( HANDLE hInstance )
{
WNDCLASSWndClass;
WndClass.style = CS_HREDRAW | CS_VREDRAW;
WndClass.lpfnWndProc = ClientProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = hInstance;
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hbrBackground = COLOR_WINDOW + 1;
WndClass.lpszMenuName = (LPSTR) "ClientMenu";
WndClass.lpszClassName = (LPSTR) "ClientClass";
return (RegisterClass((PWNDCLASS) & WndClass));
}
long FAR PASCAL
ClientProc( HWND hWnd, unsigned message, UINT wParam, LONG lParam)
{
int length;
WSADATA wsaData;
int Status;
LPSTR msgstr;
switch (message) {
case WM_USER:// 用户初始化消息。启动Windows Sockets DLL,协商版本支持。
Status = WSAStartup(0x101, &wsaData);
if (Status != 0) {
AlertUser(hWnd, "WSAStartup() failed \n");
PostQuitMessage(0);
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
AlertUser(hWnd, "WSAStartup() Version not match\n");
WSACleanup();
PostQuitMessage(0);
}
//通过主机名异步获取主机信息。
AsyncHnd = WSAAsyncGetHostByName(hWnd, UM_REQ,
server_address, buffer, MAXGETHOSTSTRUCT);
break;
case WM_COMMAND:
switch (wParam) {
case IDM_START:
if (!Client(hWnd)) {
AlertUser(hWnd, "Start Failed");
EnableMenuItem(GetMenu(hWnd), IDM_START, MF_ENABLED);
EnableMenuItem(GetMenu(hWnd),IDM_STOP,
MF_DISABLED|MF_GRAYED);
}
else {
EnableMenuItem(GetMenu(hWnd), IDM_START,
MF_DISABLED | MF_GRAYED);
EnableMenuItem(GetMenu(hWnd), IDM_STOP, MF_ENABLED);
}
break;
case IDM_STOP:
WSACleanup(); //退出前注销对Windows Sockets DLL的使用。
PostQuitMessage(0);
break;
}
break;
case WM_CHAR:
if (wParam == 'q' | wParam == 'Q') {
PostMessage(hWnd, WM_COMMAND, (WPARAM)IDM_STOP, (LPARAM)0);
break;
}
PostMessage(hWnd, UM_SOCK,(WPARAM)wParam, (LPARAM)FD_USERWRITE);
break;
case UM_REQ: //异步请求完成消息,结果在缓冲区中。
if (WSAGETASYNCERROR(lParam)) {
AlertUser(hWnd, "WSAAsyncGetHostByName ERROR \n");
WSACleanup();
PostQuitMessage(0);
}
memcpy(&hostnm, lpBuffer, sizeof(struct hostent));
break;
case UM_SOCK: //异步选择消息。
switch (lParam) {
case FD_CONNECT: //连接建立完成,置标志。
connected = 1;
break;
case FD_READ: //数据读准备好,读数据并显示。
if ((length = receive_pkt(hWnd)) == 0)
{
AlertUser(hWnd, "Receive Packet Failed");
close_skt();
break;
}
DisplayInfo(hWnd, length);
break;
case FD_WRITE: //写准备好。
break;
case FD_USERWRITE: //用户写数据消息,发送数据。
if (!connected) {
AlertUser(hWnd, "Connection not created");
break;
}
length = 1;
buffer[0] = (char)wParam;
if (!(send_pkt(hWnd, length))) {
AlertUser(hWnd, "Packet Send Failed");
close_skt();
break;
}
break;
case FD_CLOSE: //连接关闭,置标志。
connected = 0;
if (WSAAsyncSelect(s,hWnd,0,0) == SOCKET_ERROR)
AlertUser(hWnd, "WSAAsyncSelect Failed");
AlertUser(hWnd, "Socket has been closed");
EnableMenuItem(GetMenu(hWnd),IDM_START, F_ENABLED);
EnableMenuItem(GetMenu(hWnd), IDM_STOP, MF_DISABLED |
MF_GRAYED);
break;
default:
if (WSAGETSELECTERROR(lParam) != 0) {
AlertUser(hWnd, "Socket Report Failure");
close_skt();
break;
}
sprintf(msgstr, "lParam = 0x%lx, wParam = 0x%x",lParam,wParam);
AlertUser(hWnd, msgstr);
break;
}
break;
case WM_DESTROY:
close_skt();
WSACleanup(); //退出前注销对Windows Sockets DLL的使用。
PostQuitMessage(0);
break;
default:
return (DefWindowProc(hWnd, message, wParam, lParam));
}
return (NULL);
}
VOID //报警子程序。
AlertUser( HWND hWnd, LPSTR lpszWarning )
{
MessageBox(hWnd, lpszWarning, "Windows Client", MB_OK | MB_ICONEXCLAMATION);
}
/****************************************************************************/
BOOL //客户程序创建套接字并建立连接。
Client( HWND hWnd )
{
EnableMenuItem(GetMenu(hWnd), IDM_START, MF_ENABLED);
EnableMenuItem(GetMenu(hWnd), IDM_STOP, MF_ENABLED);
if (!make_skt(hWnd)) { //创建套接字。
close_skt();
return(FALSE);
}
if (!set_select(hWnd, FD_CONNECT | FD_READ | FD_WRITE)) {//异步选择网络事件。
close_skt();
return(FALSE);
}
if (!connect_skt(hWnd)) { //建立连接。
close_skt();
return(FALSE);
}
return(TRUE);
}
/****************************************************************************/
int //接收数据子程序。
receive_pkt(HWND hWnd)
{
int errno, length, len;
length = 1024;
if ((len = recv(s, lpBuffer, length, 0)) == SOCKET_ERROR) { //接收网络数据。
errno = WSAGetLastError();
if (errno==WSAEWOULDBLOCK)
return(TRUE);
AlertUser(hWnd, "Received Failed");
close_skt();
return(FALSE);
}
length = len;
if (length == 0) {
AlertUser(hWnd, "Connection was Closed");
close_skt();
}
return(length);
}
BOOL //异步选择子程序。
set_select( HWND hWnd, long lEvent)
{
if (WSAAsyncSelect(s, hWnd, UM_SOCK, lEvent) == SOCKET_ERROR) {
AlertUser(hWnd, "WSAAsyncSelect Failed");
return (FALSE);
}
return (TRUE);
}
BOOL //创建套接字子程序。
make_skt( HWND hWnd )
{
if ((s = socket(AF_INET, sock_type, 0)) == INVALID_SOCKET) {
AlertUser(hWnd, "Socket Failed");
return (FALSE);
}
return (TRUE);
}
BOOL //建立连接子程序。
connect_skt( HWND hWnd )
{
int errno;
memset((void *) &dst_addr, sizeof(dst_addr), 0);
dst_addr.sin_family = AF_INET;
dst_addr.sin_port = htons(port);
dst_addr.sin_addr.s_addr = *((unsigned long *)hostnm.h_addr_list[0]);
if (connect(s, (struct sockaddr *) & dst_addr, sizeof(dst_addr)) == SOCKET_ERROR) {
errno = WSAGetLastError();
if (errno != WSAEWOULDBLOCK) {
AlertUser(hWnd, "Connect Failed");
close_skt();
return (FALSE);
}
}
return (TRUE);
}
BOOL //发送数据子程序。
send_pkt( HWND hWnd, int len)
{
int length;
if ((length = send(s, lpBuffer, len, 0)) == SOCKET_ERROR) {
AlertUser(hWnd, "Send Failed");
close_skt();
return (FALSE);
}
else if (length != len) {
AlertUser(hWnd, "Send Length NOT Match!");
close_skt();
return(FALSE);
}
return (TRUE);
}
VOID //关闭套接字子程序。
close_skt()
{
if (s) {
(VOID) closesocket(s);
s = 0;
}
}
void //显示接收数据子程序。
DisplayInfo(HWND hWnd, int len)
{
HDC dc;
int l;
char line[16];
static int col = 0, row = 0;
buffer[len] = 0;
if (dc = GetDC(hWnd)) {
l = wsprintf((LPSTR) line, "%s", lpBuffer);
TextOut(dc, 10*col, 16*row, (LPSTR) line, l);
ReleaseDC(hWnd, dc);
}
col += len;
if (col > 40) {
col = 0;
row ++;
}
}
6.3.3.2 网络服务器程序
下面我们介绍服务器程序,源文件取名为wechos.c,其内容如下:
#include <winsock.h>
/* This is a Sample for WinSock. It uses WinSock routine as a server to communicate with Client through a port -- USERPORT.
Usage: wechos.exe */
#include <stdio.h>
#include <ctype.h>
#include <memory.h>
#include <string.h>
#include "wechos.h"
#define MAX_LENGTH 1024
HANDLE hInst;
char buffer[MAX_LENGTH];
char FAR * lpBuffer = &buffer[0];
int length=0;
SOCKET s = 0, oldskt;
struct sockaddr_in dst_addr;
unsigned short port = USERPORT;
int sock_type = SOCK_STREAM;
BOOL InitWindow(HANDLE);
long FAR PASCAL ServerProc(HWND, unsigned, UINT, LONG);
VOID AlertUser(HWND, LPSTR);
BOOL Server(HWND);
BOOL set_select(HWND, long);
BOOL make_skt(HWND);
BOOL accept_skt(HWND);
BOOL send_pkt(HWND, int);
BOOL receive_pkt(HWND, int *);
VOID close_skt(SOCKET);
/****************************************************************************/
int PASCAL
WinMain( HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HWND hWnd;
MSG msg;
if (!hPrevInstance) {
if (!InitWindow(hInstance))
return (NULL);
}
else return(NULL); // Don't allow another SERVER is executing
hInst = hInstance;
hWnd = CreateWindow("ServerClass", "Windows ECHO Server", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
if (!hWnd)
return (NULL);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
PostMessage(hWnd, WM_USER,(WPARAM) 0, (LPARAM) 0);
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (msg.wParam);
}
BOOL
InitWindow( HANDLE hInstance )
{
WNDCLASS WndClass;
WndClass.style = CS_HREDRAW | CS_VREDRAW;
WndClass.lpfnWndProc = ServerProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = hInstance;
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hbrBackground = COLOR_WINDOW + 1;
WndClass.lpszMenuName = (LPSTR) "ServerMenu";
WndClass.lpszClassName = (LPSTR) "ServerClass";
return (RegisterClass((PWNDCLASS) & WndClass));
}
long FAR PASCAL
ServerProc( HWND hWnd, unsigned message, UINT wParam, LONG lParam)
{
WSADATA wsaData;
int Status;
switch (message) {
case WM_USER: //用户消息,初始化Windows Sockets DLL,协商版本支持。
Status = WSAStartup(0x101, &wsaData);
if (Status != 0) {
AlertUser(hWnd, "WSAStartup() failed \n");
PostQuitMessage(0);
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
AlertUser(hWnd, "WSAStartup() Version not match\n");
WSACleanup();
PostQuitMessage(0);
}
if (!Server(hWnd)) {
AlertUser(hWnd, "Start Failed");
EnableMenuItem(GetMenu(hWnd), IDM_START, MF_ENABLED);
EnableMenuItem(GetMenu(hWnd), IDM_EXIT, MF_DISABLED | MF_GRAYED);
}
break;
case WM_COMMAND:
switch (wParam) {
case IDM_START:
if (!Server(hWnd)) {
AlertUser(hWnd, "Start Failed");
EnableMenuItem(GetMenu(hWnd), DM_START,MF_ENABLED);
EnableMenuItem(GetMenu(hWnd), IDM_EXIT,
MF_DISABLED | MF_GRAYED);
}
break;
case IDM_EXIT:
WSACleanup(); //退出前注销对Windows Sockets DLL的使用。
PostQuitMessage(0);
break;
}
break;
case UM_SOCK: //异步选择消息。
switch (lParam) {
case FD_ACCEPT: //接收套接字上的连接。
if (!accept_skt(hWnd)) {
AlertUser(hWnd,"Accept socket Failed");
break;
}
set_select(hWnd, FD_READ);
break;
case FD_READ: //数据读准备好,接收网络数据。
length = 1024;
if (!receive_pkt(hWnd, &length)) {
AlertUser(hWnd, "Receive Packet Failed");
break;
}
set_select(hWnd, FD_WRITE);
break;
case FD_WRITE: //写准备好,发送数据。
if (!(send_pkt(hWnd, length))) {
AlertUser(hWnd, "Send Packet Failed");
break;
}
set_select(hWnd, FD_READ);
break;
case FD_CLOSE: //连接关闭。
if (WSAAsyncSelect(s,hWnd,0,0) == SOCKET_ERROR)
AlertUser(hWnd, "WSAAsyncSelect Failed");
AlertUser(hWnd, "Socket has been closed");
EnableMenuItem(GetMenu(hWnd), IDM_START,MF_ENABLED);
break;
default:
if (WSAGETSELECTERROR(lParam) != 0) {
AlertUser(hWnd, "Socket Report Failure");
close_skt(s);
s = oldskt;
EnableMenuItem(GetMenu(hWnd),IDM_START, F_ENABLED);
}
break;
}
break;
case WM_DESTROY:
WSACleanup(); //退出前注销对Windows Sockets DLL的使用。
PostQuitMessage(0);
break;
default:
return (DefWindowProc(hWnd, message, wParam, lParam));
}
return (NULL);
}
VOID //报警子程序。
AlertUser( HWND hWnd, LPSTR lpszWarning )
{
MessageBox(hWnd, lpszWarning, "Windows Server", MB_OK| MB_ICONEXCLAMATION);
}
/****************************************************************************/
BOOL //服务器程序创建套接字,并注册FD_ACCEPT网络事件。
Server( HWND hWnd )
{
EnableMenuItem(GetMenu(hWnd), IDM_START,MF_DISABLED | MF_GRAYED);
EnableMenuItem(GetMenu(hWnd), IDM_EXIT, MF_ENABLED);
if (!make_skt(hWnd))
return(FALSE);
if (!set_select(hWnd, FD_ACCEPT))
return(FALSE);
return(TRUE);
}
/****************************************************************************/
BOOL //接收数据子程序。
receive_pkt(HWND hWnd, int *len)
{
int length, errno;
length = *len;
if ((length = recv(s, lpBuffer, length, 0)) == SOCKET_ERROR) {
errno = WSAGetLastError();
if (errno != WSAEWOULDBLOCK)
return(FALSE);
}
*len = length;
return(TRUE);
}
BOOL //异步选择子程序。
set_select( HWND hWnd, long lEvent)
{
if (WSAAsyncSelect(s, hWnd, UM_SOCK, lEvent) == SOCKET_ERROR) {
AlertUser(hWnd, "WSAAsyncSelect Failed");
return (FALSE);
}
return (TRUE);
}
BOOL //创建套接字子程序。
make_skt(HWND hWnd)
{
SOCKADDR_IN sin;
unsigned long on=1;
s = socket (AF_INET, SOCK_STREAM, 0); //创建套接字。
if (s == INVALID_SOCKET) {
AlertUser(hWnd, "Socket Failed");
PostQuitMessage(0);
return(FALSE);
}
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons (port);
if (bind (s, (LPSOCKADDR) &sin, sizeof (sin))) { //建立本地连接。
close_skt(s);
PostQuitMessage(0);
return(FALSE);
}
if (listen (s, 1)) return(FALSE); //将套接字变为被动套接字,等待接收连接。
return(TRUE);
}
BOOL //接收套接字上连接。
accept_skt(HWND hWnd)
{
SOCKET newskt;
struct sockaddr tcpaddr;
int len;
len = sizeof(struct sockaddr);
newskt = accept (s, (struct sockaddr far *)&tcpaddr, (int far *)&len);
if (newskt == INVALID_SOCKET)
return(FALSE);
else {
oldskt = s; //保存监听套接字。
s = newskt; //使用接收套接字用于数据传输。
}
return(TRUE);
}
BOOL //发送数据子程序。
send_pkt( HWND hWnd, int len)
{
if (send(s, lpBuffer, len, 0) == SOCKET_ERROR)
return (FALSE);
return (TRUE);
}
VOID //关闭套接字子程序。
close_skt(SOCKET skt)
{
(VOID) closesocket(skt);
}
客户/服务器工作模式是Socket网络程序典型的模式,通过对这一实例程序的理解,我们可以基本掌握Windows Sockets网络程序设计的基本原理及实现方法。用户可以将此程序作为范例,通过对它们进行修改和扩充,设计出符合自己要求的应用程序。
Windows Sockets是Microsoft Windows的网络程序设计接口,它是从Berkeley Sockets扩展而来的。Windows Sockets在继承了Berkeley Sockets主要特征的基础上,又对它进行了重要扩充。这些扩充主要是提供了一些异步函数,并增加了符合Windows消息驱动特性的网络事件异步选择机制。这些扩充有利于应用程序开发者编制符合Windows编程模式的软件,它使在Windows下开发高性能的网络程序成为可能。
6.3.1 Socket网络程序设计原理
Socket是BSD UNIX提供的网络应用编程接口,它采用客户/服务器的通讯机制,使网络客户方和服务器方通过Socket实现网络之间的连接和数据交换。Socket提供了一系列的系统调用,使用这些系统调用可以实现TCP, UDP,ICMP和IP等多种网络协议之间的通讯。
Socket有三种主要类型:stream sockets, datagram sockets 和raw sockets。 Stream socket接口定义了一种可靠的面向连接的服务,它实现了无差错无重复的顺序数据传输。它通过内置的流量控制解决了数据的拥塞,应用程序可以发送任意长度的数据,将数据当作字节流。Datagram socket接口定义了一种无连接的服务,数据通过相互独立的包进行传输,包的传输是无序的,并且不保证是否出错、丢失和重复。包长度是有限的(隐含长度为8,192Bytes,最大长度可设为32,768Bytes)。Raw socket接口允许对低层协议如IP和ICMP的直接存取,它主要用于新的网络协议实现的测试等。
下面我们通过一个面向连接的传输发生的典型情况来说明socket网络通信的实现。
服 务 器 客 户
socket( )
bind( )
listen( )
accept( )
close( )
read( )
...
write( )
close( )
read( )
...
write( )
socket( )
connect( )
Connection Establishment
wait connection for client
……
图6.3 面向连接的协议实现的Socket调用
从图6.3可以看出,客户和服务器的关系不是对称的,服务器首先启动,然后在某一时间启动客户与服务器建立连接。服务器和客户开始都必须用调用socket()建立一个套接字(socket),然后服务器调用bind()将套接字与一个本地网络地址捆扎在一起,再用调用listen()使套接字处于一种被动的准备接收状态,同时规定它的请求队列长度,之后服务器就可以调用accept()来接收连接了。客户在建立套接字之后,便可以通过调用connect()和服务器建立连接。连接建立后,客户和服务器之间就可以通过连接发送和接收数据(调用read()和write())。最后,待数据传送结束,双方调用close()关闭套接字。
6.3.2 WinSock对Socket的扩充
BSD Socket支持阻塞(blocking)和非阻塞(non_blocking)两种工作方式。在阻塞方式下工作,connect()、accept()、read()和recv()等调用在执行时都处于阻塞状态直到它成功或出错返回。在非阻塞方式下工作,这些调用是立即返回的,但是它们是否完成得靠查询才能知道。对于Windows这种非抢先多任务操作系统来说,这两种工作方式都是很难以接受的,为此,WinSock在尽量与BSD Socket保持一致外,又对它作了必要的扩充。
WinSock对BSD Socket的扩充主要是在基于消息、对网络事件的异步存取接口上。表6.2列出了WinSock扩充的函数功能。
表6.2 WinSock扩充函数功能表
函 数 名
功 能
WSAAsyncGetHostByAddr()
标准Berkeley函数getXbyY的异步版本,例如:函数WSAAsyncGetHostByName()就是提供了标准Berkeley函数gethostbyname的一种基于消息的异步实现。
WSAAsyncGetHostByName()
WSAAsyncGetProtoByName()
WSAAsyncGetProtoByNumber()
WSAAsyncGetServByName()
WSAAsyncGetServByPort()
WSAAsyncSelect()
函数select()的异步版本
WSACancelAsyncRequest()
取消函数WSAAsyncGetXByY执行中的实例
WSACancelBlockingCall()
取消一个执行中的“阻塞”API调用
WSACleanup()
终止使用隐含的Windows Sockets DLL
WSAGetLastError()
获取Windows Sockets API的最近错误号
WSAIsBlocking()
检测隐含的Windows Sockets DLL是否阻塞了一个当前线索的调用
WSASetBlockingHook()
设置应用程序自己的“阻塞”处理函数
WSASetLastError()
设置Windows Sockets API的最近错误号
WSAStartup()
初始化隐含的Windows Sockets DLL
WSAUnhookBlockingHook()
恢复原来的“阻塞”处理函数
从表6.2可以看出,WinSock的扩充功能可以分为如下几类:
(1) 异步选择机制
异步选择函数WSAAsyncSelect()允许应用程序提名一个或多个感兴趣的网络事件,所有非阻塞的网络I/O例程(如send()和recv()),不管它是已经使用还是即将使用,都可作为WSAAsyncSelect()函数选择的候选。当被提名的网络事件发生时,Windows应用程序的窗口函数将收到一个消息,消息附带的参数指示被提名过的某一网络事件。
(2) 异步请求例程
异步请求例程允许应用程序用异步方式获取请求的信息,如WSAAsyncGetXByY()类函数允许用户请求异步服务,这些功能在使用标准Berkeley函数时是阻塞的。函数WSACancelAsyncRequest()允许用户终止一个正在执行的异步请求。
(3) 阻塞处理方法
WinSock在调用处于阻塞时进入一个叫“Hook”的例程,它负责处理Windows消息,使得Windows的消息循环能够继续。WinSock还提供了两个函数(WSASetBlockingHook()和WSAUnhookBlockingHook())让用户能够设置和取消自己的阻塞处理例程。另外,函数WSAIsBlocking()可以检测调用是否阻塞,函数WSACancelBlockingCall()可以取消一个阻塞的调用。
(4) 出错处理
为了和以后的多线索环境(如Windows/NT)兼容,WinSock提供了两个出错处理函数WSAGetLastError()和WSASetLastError()来获取和设置本线索的最近错误号。
(5) 启动与终止
WinSock的应用程序在使用上述WinSock函数前,必须先调用WSAStartup()函数对Windows Sockets DLL进行初始化,以协商WinSock的版本支持,并分配必要的资源。在应用程序退出之前,应该先调用函数WSACleanup()终止对Windows Sockets DLL的使用,并释放资源,以利下一次使用。
在这些函数中,实现Windows网络实时通信的关键是异步选择函数WSAAsyncSelect()的使用,其原型如下:
int PASCAL FAR WSAAsyncSelect(SOCTET s, HWND hWnd, unsigned int wMsg, long lEvent);
它请求Windows Sockets DLL在检测到在套接字s上发生的lEvent事件时,向窗口hWnd发送一个消息wMsg。它自动地设置套接字s处于非阻塞工作方式。参数lEvent由表6.3所列事件的一个或多个组成。例如,我们要在套接字s读准备好或写准备好时接到通知,可以使用下面的语句:
rc = WSAAsyncSelect(s, hWnd, wMsg, FD_READ | FD_WRITE);
当套接字s上被提名的一个网络事件发生时,窗口hWnd将收到消息wMsg,变量lParam的低字指示网络发生的事件,高字指示错误码。应用程序就可以通过这些信息来决定自己的下一步动作。
表6.3 异步选择网络事件表
值
含 义
FD_READ
希望在套接字s收到数据(即读准备好)时接到通知
FD_WRITE
希望在套接字s可发送数据(即写准备好)时接到通知
FD_OOB
希望在套接字s上有带外数据到达时接到通知
FD_ACCEPT
希望在套接字s上有外部连接到来时接到通知
FD_CONNECT
希望在套接字s连接建立完成时接到通知
FD_CLOSE
希望在套接字s关闭时接到通知
6.3.3 网络程序示例Echo
本章的例子是一个简单的点对点网络实时通信程序Echo。实例分两部分:客户程序wecho.c与服务器程序wechos.c。其工作过程是:服务器首先启动,它创建套接字之后等待客户的连接;客户在启动后,创建套接字,然后和服务器建立连接;连接建立后,客户接收键盘输入,然后将数据发送到服务器,服务器收到数据后,只是简单地发送回来,客户将收到的数据在窗口中显示;如此循环,当客户接收到的输入数据是字母“Q”时,它关闭连接和套接字后退出,服务器在用户关闭窗口时关闭套接字。
6.3.3.1 网络客户程序
首先介绍客户程序,该源文件取名为wecho.c,其内容在下面列出。为了方便读者,在程序中关键部分采用中文注释的形式给出说明,读者可照这些注释加强对本节内容的理解。
/* This is a Sample for WinSock. It uses WinSock routine to communicate with Server through a port -- USERPORT.
Usage: wecho servername */
#include <winsock.h>
#include <windows.h>
#include <stdio.h>
#include <ctype.h>
#include <memory.h>
#include <string.h>
#include "wecho.h"
HANDLE hInst, AsyncHnd;
char server_address[256] = {0};
char buffer[MAXGETHOSTSTRUCT];
char FAR * lpBuffer = &buffer[0];
SOCKET s = 0;
int connected = 0;
struct sockaddr_in dst_addr;
struct hostent *hostaddr;
struct hostent hostnm;
unsigned short port = USERPORT; // 用户端口号,应大于1024。客户和服务器的端口号必须相同。
int sock_type = SOCK_STREAM;
BOOL InitWindow(HANDLE);
long FAR PASCAL ClientProc(HWND, unsigned, UINT, LONG);
VOID AlertUser(HWND, LPSTR);
BOOL Client(HWND);
BOOL set_select(HWND, long);
BOOL make_skt(HWND);
BOOL connect_skt(HWND);
BOOL send_pkt(HWND, int);
int receive_pkt(HWND);
VOID close_skt(VOID);
void DisplayInfo(HWND, int);
/****************************************************************************/
int PASCAL
WinMain( HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HWND hWnd;
MSG msg;
lstrcpy((LPSTR) server_address, lpCmdLine);
if (!hPrevInstance)
if (!InitWindow(hInstance))
return (NULL);
hInst = hInstance;
hWnd = CreateWindow("ClientClass", "Windows ECHO Client", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
if (!hWnd)
return (NULL);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
EnableMenuItem(GetMenu(hWnd), IDM_STOP, MF_DISABLED | MF_GRAYED);
PostMessage(hWnd, WM_USER,(WPARAM) 0, (LPARAM) 0);
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (msg.wParam);
}
BOOL
InitWindow( HANDLE hInstance )
{
WNDCLASSWndClass;
WndClass.style = CS_HREDRAW | CS_VREDRAW;
WndClass.lpfnWndProc = ClientProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = hInstance;
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hbrBackground = COLOR_WINDOW + 1;
WndClass.lpszMenuName = (LPSTR) "ClientMenu";
WndClass.lpszClassName = (LPSTR) "ClientClass";
return (RegisterClass((PWNDCLASS) & WndClass));
}
long FAR PASCAL
ClientProc( HWND hWnd, unsigned message, UINT wParam, LONG lParam)
{
int length;
WSADATA wsaData;
int Status;
LPSTR msgstr;
switch (message) {
case WM_USER:// 用户初始化消息。启动Windows Sockets DLL,协商版本支持。
Status = WSAStartup(0x101, &wsaData);
if (Status != 0) {
AlertUser(hWnd, "WSAStartup() failed \n");
PostQuitMessage(0);
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
AlertUser(hWnd, "WSAStartup() Version not match\n");
WSACleanup();
PostQuitMessage(0);
}
//通过主机名异步获取主机信息。
AsyncHnd = WSAAsyncGetHostByName(hWnd, UM_REQ,
server_address, buffer, MAXGETHOSTSTRUCT);
break;
case WM_COMMAND:
switch (wParam) {
case IDM_START:
if (!Client(hWnd)) {
AlertUser(hWnd, "Start Failed");
EnableMenuItem(GetMenu(hWnd), IDM_START, MF_ENABLED);
EnableMenuItem(GetMenu(hWnd),IDM_STOP,
MF_DISABLED|MF_GRAYED);
}
else {
EnableMenuItem(GetMenu(hWnd), IDM_START,
MF_DISABLED | MF_GRAYED);
EnableMenuItem(GetMenu(hWnd), IDM_STOP, MF_ENABLED);
}
break;
case IDM_STOP:
WSACleanup(); //退出前注销对Windows Sockets DLL的使用。
PostQuitMessage(0);
break;
}
break;
case WM_CHAR:
if (wParam == 'q' | wParam == 'Q') {
PostMessage(hWnd, WM_COMMAND, (WPARAM)IDM_STOP, (LPARAM)0);
break;
}
PostMessage(hWnd, UM_SOCK,(WPARAM)wParam, (LPARAM)FD_USERWRITE);
break;
case UM_REQ: //异步请求完成消息,结果在缓冲区中。
if (WSAGETASYNCERROR(lParam)) {
AlertUser(hWnd, "WSAAsyncGetHostByName ERROR \n");
WSACleanup();
PostQuitMessage(0);
}
memcpy(&hostnm, lpBuffer, sizeof(struct hostent));
break;
case UM_SOCK: //异步选择消息。
switch (lParam) {
case FD_CONNECT: //连接建立完成,置标志。
connected = 1;
break;
case FD_READ: //数据读准备好,读数据并显示。
if ((length = receive_pkt(hWnd)) == 0)
{
AlertUser(hWnd, "Receive Packet Failed");
close_skt();
break;
}
DisplayInfo(hWnd, length);
break;
case FD_WRITE: //写准备好。
break;
case FD_USERWRITE: //用户写数据消息,发送数据。
if (!connected) {
AlertUser(hWnd, "Connection not created");
break;
}
length = 1;
buffer[0] = (char)wParam;
if (!(send_pkt(hWnd, length))) {
AlertUser(hWnd, "Packet Send Failed");
close_skt();
break;
}
break;
case FD_CLOSE: //连接关闭,置标志。
connected = 0;
if (WSAAsyncSelect(s,hWnd,0,0) == SOCKET_ERROR)
AlertUser(hWnd, "WSAAsyncSelect Failed");
AlertUser(hWnd, "Socket has been closed");
EnableMenuItem(GetMenu(hWnd),IDM_START, F_ENABLED);
EnableMenuItem(GetMenu(hWnd), IDM_STOP, MF_DISABLED |
MF_GRAYED);
break;
default:
if (WSAGETSELECTERROR(lParam) != 0) {
AlertUser(hWnd, "Socket Report Failure");
close_skt();
break;
}
sprintf(msgstr, "lParam = 0x%lx, wParam = 0x%x",lParam,wParam);
AlertUser(hWnd, msgstr);
break;
}
break;
case WM_DESTROY:
close_skt();
WSACleanup(); //退出前注销对Windows Sockets DLL的使用。
PostQuitMessage(0);
break;
default:
return (DefWindowProc(hWnd, message, wParam, lParam));
}
return (NULL);
}
VOID //报警子程序。
AlertUser( HWND hWnd, LPSTR lpszWarning )
{
MessageBox(hWnd, lpszWarning, "Windows Client", MB_OK | MB_ICONEXCLAMATION);
}
/****************************************************************************/
BOOL //客户程序创建套接字并建立连接。
Client( HWND hWnd )
{
EnableMenuItem(GetMenu(hWnd), IDM_START, MF_ENABLED);
EnableMenuItem(GetMenu(hWnd), IDM_STOP, MF_ENABLED);
if (!make_skt(hWnd)) { //创建套接字。
close_skt();
return(FALSE);
}
if (!set_select(hWnd, FD_CONNECT | FD_READ | FD_WRITE)) {//异步选择网络事件。
close_skt();
return(FALSE);
}
if (!connect_skt(hWnd)) { //建立连接。
close_skt();
return(FALSE);
}
return(TRUE);
}
/****************************************************************************/
int //接收数据子程序。
receive_pkt(HWND hWnd)
{
int errno, length, len;
length = 1024;
if ((len = recv(s, lpBuffer, length, 0)) == SOCKET_ERROR) { //接收网络数据。
errno = WSAGetLastError();
if (errno==WSAEWOULDBLOCK)
return(TRUE);
AlertUser(hWnd, "Received Failed");
close_skt();
return(FALSE);
}
length = len;
if (length == 0) {
AlertUser(hWnd, "Connection was Closed");
close_skt();
}
return(length);
}
BOOL //异步选择子程序。
set_select( HWND hWnd, long lEvent)
{
if (WSAAsyncSelect(s, hWnd, UM_SOCK, lEvent) == SOCKET_ERROR) {
AlertUser(hWnd, "WSAAsyncSelect Failed");
return (FALSE);
}
return (TRUE);
}
BOOL //创建套接字子程序。
make_skt( HWND hWnd )
{
if ((s = socket(AF_INET, sock_type, 0)) == INVALID_SOCKET) {
AlertUser(hWnd, "Socket Failed");
return (FALSE);
}
return (TRUE);
}
BOOL //建立连接子程序。
connect_skt( HWND hWnd )
{
int errno;
memset((void *) &dst_addr, sizeof(dst_addr), 0);
dst_addr.sin_family = AF_INET;
dst_addr.sin_port = htons(port);
dst_addr.sin_addr.s_addr = *((unsigned long *)hostnm.h_addr_list[0]);
if (connect(s, (struct sockaddr *) & dst_addr, sizeof(dst_addr)) == SOCKET_ERROR) {
errno = WSAGetLastError();
if (errno != WSAEWOULDBLOCK) {
AlertUser(hWnd, "Connect Failed");
close_skt();
return (FALSE);
}
}
return (TRUE);
}
BOOL //发送数据子程序。
send_pkt( HWND hWnd, int len)
{
int length;
if ((length = send(s, lpBuffer, len, 0)) == SOCKET_ERROR) {
AlertUser(hWnd, "Send Failed");
close_skt();
return (FALSE);
}
else if (length != len) {
AlertUser(hWnd, "Send Length NOT Match!");
close_skt();
return(FALSE);
}
return (TRUE);
}
VOID //关闭套接字子程序。
close_skt()
{
if (s) {
(VOID) closesocket(s);
s = 0;
}
}
void //显示接收数据子程序。
DisplayInfo(HWND hWnd, int len)
{
HDC dc;
int l;
char line[16];
static int col = 0, row = 0;
buffer[len] = 0;
if (dc = GetDC(hWnd)) {
l = wsprintf((LPSTR) line, "%s", lpBuffer);
TextOut(dc, 10*col, 16*row, (LPSTR) line, l);
ReleaseDC(hWnd, dc);
}
col += len;
if (col > 40) {
col = 0;
row ++;
}
}
6.3.3.2 网络服务器程序
下面我们介绍服务器程序,源文件取名为wechos.c,其内容如下:
#include <winsock.h>
/* This is a Sample for WinSock. It uses WinSock routine as a server to communicate with Client through a port -- USERPORT.
Usage: wechos.exe */
#include <stdio.h>
#include <ctype.h>
#include <memory.h>
#include <string.h>
#include "wechos.h"
#define MAX_LENGTH 1024
HANDLE hInst;
char buffer[MAX_LENGTH];
char FAR * lpBuffer = &buffer[0];
int length=0;
SOCKET s = 0, oldskt;
struct sockaddr_in dst_addr;
unsigned short port = USERPORT;
int sock_type = SOCK_STREAM;
BOOL InitWindow(HANDLE);
long FAR PASCAL ServerProc(HWND, unsigned, UINT, LONG);
VOID AlertUser(HWND, LPSTR);
BOOL Server(HWND);
BOOL set_select(HWND, long);
BOOL make_skt(HWND);
BOOL accept_skt(HWND);
BOOL send_pkt(HWND, int);
BOOL receive_pkt(HWND, int *);
VOID close_skt(SOCKET);
/****************************************************************************/
int PASCAL
WinMain( HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HWND hWnd;
MSG msg;
if (!hPrevInstance) {
if (!InitWindow(hInstance))
return (NULL);
}
else return(NULL); // Don't allow another SERVER is executing
hInst = hInstance;
hWnd = CreateWindow("ServerClass", "Windows ECHO Server", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
if (!hWnd)
return (NULL);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
PostMessage(hWnd, WM_USER,(WPARAM) 0, (LPARAM) 0);
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (msg.wParam);
}
BOOL
InitWindow( HANDLE hInstance )
{
WNDCLASS WndClass;
WndClass.style = CS_HREDRAW | CS_VREDRAW;
WndClass.lpfnWndProc = ServerProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = hInstance;
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hbrBackground = COLOR_WINDOW + 1;
WndClass.lpszMenuName = (LPSTR) "ServerMenu";
WndClass.lpszClassName = (LPSTR) "ServerClass";
return (RegisterClass((PWNDCLASS) & WndClass));
}
long FAR PASCAL
ServerProc( HWND hWnd, unsigned message, UINT wParam, LONG lParam)
{
WSADATA wsaData;
int Status;
switch (message) {
case WM_USER: //用户消息,初始化Windows Sockets DLL,协商版本支持。
Status = WSAStartup(0x101, &wsaData);
if (Status != 0) {
AlertUser(hWnd, "WSAStartup() failed \n");
PostQuitMessage(0);
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
AlertUser(hWnd, "WSAStartup() Version not match\n");
WSACleanup();
PostQuitMessage(0);
}
if (!Server(hWnd)) {
AlertUser(hWnd, "Start Failed");
EnableMenuItem(GetMenu(hWnd), IDM_START, MF_ENABLED);
EnableMenuItem(GetMenu(hWnd), IDM_EXIT, MF_DISABLED | MF_GRAYED);
}
break;
case WM_COMMAND:
switch (wParam) {
case IDM_START:
if (!Server(hWnd)) {
AlertUser(hWnd, "Start Failed");
EnableMenuItem(GetMenu(hWnd), DM_START,MF_ENABLED);
EnableMenuItem(GetMenu(hWnd), IDM_EXIT,
MF_DISABLED | MF_GRAYED);
}
break;
case IDM_EXIT:
WSACleanup(); //退出前注销对Windows Sockets DLL的使用。
PostQuitMessage(0);
break;
}
break;
case UM_SOCK: //异步选择消息。
switch (lParam) {
case FD_ACCEPT: //接收套接字上的连接。
if (!accept_skt(hWnd)) {
AlertUser(hWnd,"Accept socket Failed");
break;
}
set_select(hWnd, FD_READ);
break;
case FD_READ: //数据读准备好,接收网络数据。
length = 1024;
if (!receive_pkt(hWnd, &length)) {
AlertUser(hWnd, "Receive Packet Failed");
break;
}
set_select(hWnd, FD_WRITE);
break;
case FD_WRITE: //写准备好,发送数据。
if (!(send_pkt(hWnd, length))) {
AlertUser(hWnd, "Send Packet Failed");
break;
}
set_select(hWnd, FD_READ);
break;
case FD_CLOSE: //连接关闭。
if (WSAAsyncSelect(s,hWnd,0,0) == SOCKET_ERROR)
AlertUser(hWnd, "WSAAsyncSelect Failed");
AlertUser(hWnd, "Socket has been closed");
EnableMenuItem(GetMenu(hWnd), IDM_START,MF_ENABLED);
break;
default:
if (WSAGETSELECTERROR(lParam) != 0) {
AlertUser(hWnd, "Socket Report Failure");
close_skt(s);
s = oldskt;
EnableMenuItem(GetMenu(hWnd),IDM_START, F_ENABLED);
}
break;
}
break;
case WM_DESTROY:
WSACleanup(); //退出前注销对Windows Sockets DLL的使用。
PostQuitMessage(0);
break;
default:
return (DefWindowProc(hWnd, message, wParam, lParam));
}
return (NULL);
}
VOID //报警子程序。
AlertUser( HWND hWnd, LPSTR lpszWarning )
{
MessageBox(hWnd, lpszWarning, "Windows Server", MB_OK| MB_ICONEXCLAMATION);
}
/****************************************************************************/
BOOL //服务器程序创建套接字,并注册FD_ACCEPT网络事件。
Server( HWND hWnd )
{
EnableMenuItem(GetMenu(hWnd), IDM_START,MF_DISABLED | MF_GRAYED);
EnableMenuItem(GetMenu(hWnd), IDM_EXIT, MF_ENABLED);
if (!make_skt(hWnd))
return(FALSE);
if (!set_select(hWnd, FD_ACCEPT))
return(FALSE);
return(TRUE);
}
/****************************************************************************/
BOOL //接收数据子程序。
receive_pkt(HWND hWnd, int *len)
{
int length, errno;
length = *len;
if ((length = recv(s, lpBuffer, length, 0)) == SOCKET_ERROR) {
errno = WSAGetLastError();
if (errno != WSAEWOULDBLOCK)
return(FALSE);
}
*len = length;
return(TRUE);
}
BOOL //异步选择子程序。
set_select( HWND hWnd, long lEvent)
{
if (WSAAsyncSelect(s, hWnd, UM_SOCK, lEvent) == SOCKET_ERROR) {
AlertUser(hWnd, "WSAAsyncSelect Failed");
return (FALSE);
}
return (TRUE);
}
BOOL //创建套接字子程序。
make_skt(HWND hWnd)
{
SOCKADDR_IN sin;
unsigned long on=1;
s = socket (AF_INET, SOCK_STREAM, 0); //创建套接字。
if (s == INVALID_SOCKET) {
AlertUser(hWnd, "Socket Failed");
PostQuitMessage(0);
return(FALSE);
}
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons (port);
if (bind (s, (LPSOCKADDR) &sin, sizeof (sin))) { //建立本地连接。
close_skt(s);
PostQuitMessage(0);
return(FALSE);
}
if (listen (s, 1)) return(FALSE); //将套接字变为被动套接字,等待接收连接。
return(TRUE);
}
BOOL //接收套接字上连接。
accept_skt(HWND hWnd)
{
SOCKET newskt;
struct sockaddr tcpaddr;
int len;
len = sizeof(struct sockaddr);
newskt = accept (s, (struct sockaddr far *)&tcpaddr, (int far *)&len);
if (newskt == INVALID_SOCKET)
return(FALSE);
else {
oldskt = s; //保存监听套接字。
s = newskt; //使用接收套接字用于数据传输。
}
return(TRUE);
}
BOOL //发送数据子程序。
send_pkt( HWND hWnd, int len)
{
if (send(s, lpBuffer, len, 0) == SOCKET_ERROR)
return (FALSE);
return (TRUE);
}
VOID //关闭套接字子程序。
close_skt(SOCKET skt)
{
(VOID) closesocket(skt);
}
客户/服务器工作模式是Socket网络程序典型的模式,通过对这一实例程序的理解,我们可以基本掌握Windows Sockets网络程序设计的基本原理及实现方法。用户可以将此程序作为范例,通过对它们进行修改和扩充,设计出符合自己要求的应用程序。
发表评论
-
用自删除dll实现应用程序的安装/卸载代码
2012-03-21 17:22 975http://www.vckbase.com/document ... -
C++Builder及VC的DLL相互调用[转]
2012-03-21 16:48 1759原文地址:http://topic.csdn. ... -
C++Builder及VC的DLL相互调用[转]
2012-03-21 16:19 0原贴地址:http://topic.csdn.net/u/20 ... -
【转】关于 #define WINVER
2011-04-20 19:25 1493原文地址:http://blog.ehomy.net/ar ... -
【转】Richedit使用大全
2011-04-06 11:10 2658一.常见问题 a.可以编译,不能执行的 AfxInitRic ... -
UpdateData() --MFC函数
2011-03-19 14:55 976UpdateData,顾名思义,是用来刷新数据的。 Up ... -
MFC控制条窗口布局原理【转】
2011-03-19 11:23 1148一、框架窗口 框架窗口 ... -
C++builder 2010 Socket 网络编程
2010-10-29 15:35 20681. 打开c++builder 2010, 打开菜单Proje ... -
BCB6 下devexpress 安装手记
2010-09-06 00:29 2980最近要开发个软件,以前用BCB自带的控件做出来的总是感觉像玩 ... -
Windows内存管理
2010-08-08 19:59 1137原文地址:http://www.cic.t ... -
c#中读取二进制结构体文件
2010-08-07 23:46 3550文件结构体中有 : unsigned short int Si ... -
VC2005程序的一个运行错误“由于应用程序的配置不正确,应用程序未能启动,重新安装...
2009-10-07 16:07 2436VC.net2005写的程序如何在 ...
相关推荐
在IT行业中,Windows Socket,通常称为Winsock,是Microsoft Windows操作系统上的网络编程接口,用于实现TCP/IP协议...通过学习和实践这些代码,开发者可以掌握网络程序的设计、调试和维护,从而在实际项目中游刃有余。
**实验报告:Windows网络程序设计** 在Windows操作系统环境中进行网络程序设计是一项复杂而重要的任务,它涉及到计算机网络的基础原理、Windows API的使用以及网络通信协议的实现。本实验报告将聚焦于这一领域的...
"socket网络程序设计作业"涉及到了利用socket接口进行网络通信的技术。在这个实验中,学生将学习如何使用两种不同的方法来实现聊天程序:一种是基于CAsyncSocket类的单点聊天程序,另一种是基于CSocket类的多点聊天...
《Windows网络程序设计教材》是一本专为初学者编写的指南,旨在引领读者深入理解如何在Windows平台上进行网络程序设计。这本书涵盖了从基础概念到高级技术的全面内容,旨在激发学习者对Windows编程的热情和兴趣。 ...
《Windows Socket程序设计教程》是一本深入探讨在Windows操作系统下如何进行Socket编程的指南。Socket编程是网络通信的基础,尤其在跨平台应用开发中扮演着关键角色。本教程旨在教授读者如何利用Windows Socket API...
《Windows Socket 1.1 程序设计》 Windows Socket,简称Winsock,是Microsoft Windows操作系统中的网络编程接口,它是从Berkeley Sockets发展而来,同时进行了扩展以适应Windows平台的特点。Winsock的设计目标是...
在Windows网络程序设计中,开发人员需要理解和掌握一系列关键技术与概念,这关乎到网络应用程序的高效性和稳定性。本文将深入探讨这一主题,特别是在西南科技大学的教育背景下,这方面的知识是学生进行答辩时需要...
《Windows网络程序设计》是一本深入探讨在Windows操作系统环境下如何进行网络编程的专业书籍。作者夏靖波通过本书向读者展示了在网络编程领域的广泛知识,涵盖了从基础的网络概念到复杂的网络应用开发技术。这本书...
《Windows Sockets网络程序设计大全》是一本深入探讨在Windows平台上使用Windows Sockets(Winsock)接口进行网络编程的专业书籍。Winsock是Windows系统提供的一个API,它为开发者提供了标准的接口来实现跨平台的...
《Windows Sockets网络程序设计指南》是一本深入探讨在Windows操作系统环境下进行网络程序设计的专业教程。Windows Sockets,简称Winsock,是微软为Windows平台提供的一个API,它遵循Berkeley Sockets接口,允许...
《Windows网络与通信程序设计(第2版)》是一本深入探讨如何在Windows平台上构建网络应用程序的专业书籍。这本书的核心目标是帮助开发者掌握Windows环境下网络编程的关键技术和实践方法。结合提供的源代码,读者可以...
**Windows网络程序设计**是计算机科学领域中的一个重要主题,它涉及到如何在Windows操作系统环境下开发能够利用网络功能的应用程序。这通常包括对TCP/IP协议栈的理解、套接字编程、网络通信以及多线程技术等。以下是...
在IT行业中,网络程序设计是构建分布式系统的关键技术,特别是在Windows平台上。本主题将深入探讨“Windows网络程序设计”,特别是围绕“流式套接口编程”这一核心概念,它包括客户端和服务器端的实现。 流式套接口...
《Windows网络与通信程序设计》第三版是一本深入探讨如何在Windows操作系统环境下开发网络和通信应用的专业书籍。这本书由陈香凝和王烨阳等专家共同撰写,旨在为读者提供全面且实践性强的Windows网络编程知识。配套...
总的来说,《Windows网络与通信程序设计》通过实例代码讲解了如何在Windows环境下进行网络程序设计,涉及了网络通信的基础知识、高级特性以及性能优化策略。这些源代码不仅有助于学习理论知识,还能提供实践经验,...
本实验“实验4-基于socket的简单网络程序设计”旨在帮助学习者理解和掌握如何利用Socket进行TCP和UDP的网络编程,以及如何在Windows环境下使用Winsock库。 TCP(Transmission Control Protocol)和UDP(User ...
【Windows网络程序设计】课程是深入探讨如何在Windows操作系统环境下编写网络应用程序的专业学科。课程涵盖了网络编程的基础概念、协议、接口以及实现技术。通过学习,开发者可以掌握在Windows平台上进行高效且可靠...
《Windows网络与通信程序设计源码》是一份深入探讨Windows平台下网络通信程序开发的宝贵资源。这份压缩包包含了详细的源代码以及相关的教学材料,旨在帮助开发者理解和实践网络通信协议的实现。 首先,我们要理解...
《Windows网络与通信程序设计》是一本深入探讨在Windows操作系统环境下进行网络通信程序开发的专业书籍。结合提供的PDF文档和源码,我们可以深入了解网络编程的基本概念、API调用、协议实现以及实际开发中的技巧和...
### Windows Socket网络编程实验教程知识点概述 #### 一、实验目的 本次实验旨在通过实践操作,帮助学习者深入了解网络通信的基础知识以及Windows环境下基于Socket的编程技术。具体目标包括: 1. **熟悉TCP/IP协议...