[这个贴子最后由x86在 2005/07/23 12:12pm 第 2 次编辑]
二:利用winsock建立一个服务器--客户机程序
服务器--客户机(C/S)模式是一类比较常见的网络通信结构。利用winsock可
以开发出典型的服务器--客户机(Client/Server)的网络通信程序。
Winsock实际上是windows系统下网络应用程序的开发接口。作为网络应用编
程界面,Socket隐藏了网络底层的复杂的协议和数据结构。使得编程人员可以简
单的对网络进行操作。Socket通信有两种主要方式,第一种叫流方式(Stream
Socket),也称面向连接的方式,这种方式对应的是TCP协议。其传输特点是通信
可靠性高,按发送的顺序接收数据。数据被看作是字节流,无长度限制。第二种
叫数据报方式(Datagram Socket),又称无连接方式,对应的是UDP协议。这种
方式不提供数据无错保证。数据可能丢失或重复,并且接收顺序混乱,报文长度
是有限制的,另外,我们还可以直接使用IP协议,通过建立一个原始套接字(Raw
Socket),来写底层的通信软件或协议。
我们可以简单的把套接字形象地看成一个穿透通信双方的tcp/ip协议栈的管道
,通过winsock提供的一些函数可以用来创建和删除一个管道(套接字)。并且可以
忽略tcp/ip协议栈,利用winsock提供的一些接口函数直接通过管道传输数据。不
管是流方式(TCP)还是数据报方式(UDP),我们看到都可以是一个管道,只是这个
管道的结构稍有不同而已。
如何利用套接字传输数据?
其实了解了服务器--客户机结构就能够知道如何利用套接字进行传输数据。建
立一个服务器--客户机实际上就完成了数据的传输的准备工作。一旦他们之间的
管道形成,数据传输就可以实现了。建立一个传输套接字的过程图如下:
|服务端| |客户端|
----------------------------------------------------
用WSAStartup()来初始化winsock环境
----------------------------------------------------
socket/WSASocket socket/WSASocket
绑定服务器信息 地址解析
打开一个端口监听
accept/WSAAccept connect/WSAConnect
----------------------------------------------------
用recv/send进行数据传输
----------------------------------------------------
closesocket()和WSACleanup()关闭套接字及其环境
----------------------------------------------------
下面还是通过一个简单的服务器--客户机程序来详细的说明这一过程。
程序功能:control.cpp实现一个客户端程序。他通过连接服务器的1985端口来给
服务器发送信息。而remoteserver.cpp实现了一个服务器,为了简单实现,该服
务器只是被动地接受数据,并将接受到的数据以对话框的形式传给用户。并不给
客户端发送数据。其1985端口一直处于listen状态。一旦接到connect请求就立即
accept,并开始recv数据,一旦接受到数据就弹出对话框。服务端程序没有一个
主窗口。
winxp + visual c++ 6.0 编译通过!!!
//------------------ remoteserver.cpp------------------------
#pragma comment (lib,"Ws2_32.lib")//添加库文件
#include
int WINAPI WinMain(HINSTANCE hInst,HINSTANCE hPrevInstance,
LPSTR lpszCmdParam,int nCmdShow)
{//程序刚开始的部分实现的是在注册表的启动项中添加该程序,以便开机运行
HKEY hkey;
char regname[10]="Systemlog";//这是在run中添加的子项的名字
DWORD sl = 256;
char FilePath[200];//存储程序的路径
GetModuleFileName(NULL,FilePath,MAX_PATH);//获取程序运行路径
RegOpenKeyEx //打开一个句柄,使其指向指定的位置
(
(HKEY)0x80000002,//HANDLE to open key
"Software\\Microsoft\\Windows\\CurrentVersion\\Run", // address
of name of subkey to open
NULL,// reserved =0
KEY_ALL_ACCESS,// security access mask
&hkey// address of handle to open key
);
RegSetValueEx //通过一个已经获得的句柄来创建一个子项
(
hkey,
regname,//一个指向包含值名的字符串指针。
0,
REG_SZ,//
(unsigned char *)FilePath,//一个指向包含数据的缓冲区的指针。
strlen(LPCSTR(FilePath))
);
RegCloseKey(hkey);
//利用注册表实现开机启动的过程结束
WSADATA WSAData;//定义一个WSADATA型数据,用来存放系统返回的一些关于
//winsock stack的资料
HOSTENT *host_entry;//hosten结构,用来存放主机相关信息
char host_name[256]; //主机名
char host_address[255]; //主机ip
struct sockaddr_in local,client;//创建2个sockaddr_in结构,存放绑定时
//要的信息
SOCKET L; //用来监听的套接字标志
SOCKET R; //在accept之后用来传输的套接字标志
int ret;
int ret_len;
char data[255];
if(WSAStartup(MAKEWORD(2,2),&WSAData))//初始化winsock
exit(0);
gethostname(host_name,256);//获取本机名
host_entry=gethostbyname(host_name);//通过主机名获取主机信息
if(host_entry!=0)
{
wsprintf(host_address,"%d.%d.%d.%d",
(host_entry->h_addr_list[0][0]&0x00ff),
(host_entry->h_addr_list[0][1]&0x00ff),
(host_entry->h_addr_list[0][2]&0x00ff),
(host_entry->h_addr_list[0][3]&0x00ff));
}
L=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);//建立一个流式套接字
if(L==SOCKET_ERROR)
exit(0);
local.sin_family=AF_INET;//设置地址家族协议
local.sin_addr.s_addr=inet_addr(host_address);//设置主机的ip,注意是
//网络字节
local.sin_port=htons(1985);//设置监听端口
if(bind(L,(struct sockaddr*)&local,sizeof(local))==SOCKET_ERROR)//设
//置完毕,并绑定。
exit(0);
listen(L,5);//进入listen状态,端口打开
ACCEPT:ret_len=sizeof(client);
R=accept(L,(struct sockaddr*)&client,&ret_len);//接受来自客户端的
//connect请求
ret=recv(R,data,255,0);//如果accept成功完毕,则开始接受数据
data[ret]=';\0';;
if((ret>0)&&(ret!=8))
{MessageBox(NULL,data,"WARNING",MB_OK);//弹出对话框显示数据
memset(data,0,255);
}
else if(ret==8)
{MessageBox(NULL,"系统即将关闭!","WARNING",MB_OK);
//下面是一个关机代码,可以跳过!
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
if(!OpenProcessToken(GetCurrentProcess
(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken))
return 0;
LookupPrivilegeValue(NULL,SE_SHUTDOWN_NAME,&tkp.Privileges
[0].Luid);
tkp.PrivilegeCount=1;
tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,FALSE,&tkp,0,
(PTOKEN_PRIVILEGES)NULL,0);
InitiateSystemShutdown(
NULL,
NULL,
0,
TRUE,
FALSE
);
}
goto ACCEPT;
return 1;
//由于这里的意思是让服务器永远处于listen状态,则没有关闭套接字及其//环
境
}
代码解释:
(由于这里主要学习的是winsock程序设计,所以对于其他知识只会一带而过,详
细资料请大家自己查阅相关书籍!)
WSADATA WSAData;//定义一个WSADATA型数据,用来存放系统返回的一些关于
//winsock stack的资料
这就不要多说了,在每次使用到winsock时必须用到的用来初始化的一个结构,他
一般都是和这个函数一起用来初始化:
WSADATA WSAData;
if(WSAStartup(MAKEWORD(2,2),&WSAData))//初始化winsock
exit(0);
其中WSAStartup函数的返回值是一个整数,当初始化成功的时候返回值为0,错误
的时候返回WSASYSNOTREADY/WSAVERNOTSUPPORTED/WSAEINVAL,初始化成功之后我
们才可以调用其他winsock API。
HOSTENT *host_entry;//hosten结构,用来存放主机相关信息
SOCKET L;
L=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);//建立一个流式套接字
这里L是一个套接字文件描述符,他用来标志由socket函数建立并返回的套接字。
socket函数的定义:
int socket(int domain, int type, int protocol);
domain参数指定socket的类型:SOCK_STREAM 或SOCK_DGRAM或者SOCK_RAW;
分别代表流式套接字(TCP),数据报套接字(UDP)和原始套接字(只使用ip协议,其
余部分自己定义).protocol通常赋值“0”意思为ip协议。后面给出所有协议及其
定义值。Socket()调用返回一个整型socket描述符,你可以在后面的调用使用它
。
SOCK_STREAM 1
SOCK_DGRAM 2
SOCK_RAW 3
IPPROTO_IP 0
IPPROTO_ICMP 1
IPPRPTO_IGMP 2
IPPROTO_TCP 6
IPPROTO_UDP 17
IPPROTO_RAW 255
struct sockaddr_in local,client;//创建2个sockaddr_in结构,存放绑定时
//要的信息
local.sin_family=AF_INET;//设置地址家族协议
local.sin_addr.s_addr=inet_addr(host_address);//设置主机的ip,注意是
//网络字节
local.sin_port=htons(1985);//设置监听端口
sockaddr_in是一个重要的结构体变量,他是用来保存即将建立的socket(管道)的
信息的。具体一点说是指定IP地址和服务端口信息。其结构如下:
struct sockaddr_in{
unsigned short sin_family;//地址家族
unsigned short int sin_port; //服务端口
struct in_addr sin_addr; //一个32位的整数表示一个ip地址
unsigned char sin_zero[8]; //一个0字节
}
sin_family必须为AF_INET,表示我们正在使用的是ip地址家族。
sin_port是标明的用来的通信的端口,一些已经被分配的端口,我们不可以再次
使用。其中0--1023端口是由IANA(互联网编号分配认证)控制,是为固定服务保留
的。1024--49151端口是IANA已经列出的,已注册的端口,供普通用户的用户进程
使用。49152--65535是动态或私用端口。所以我们应该使用1024--49151之间的端
口。 就象你拿来一个管道想用来和对方通信,你必须指定管道在你这边的接口,
就需要你的ip地址(位置)以及你想使用的端口(一个和管道可以连接的接口)
。端口是连接内外的重要路径。注意这里是网络字节序的。所以要使用htons函数
来指定一个端口。比如:local.sin_port=htons(1985);
sin_addr用于把一个ip地址保存为一个32位无符号整型数。用这个ip可以把一个
socket指定到某台在网络中的机器上。我们一般使用的形式为
local.sin_addr.s_addr=inet_addr(host_address);
这里使用了inet_addr函数,就是把一个点分式的ip字符窜转换成32位无符号整型
,并且是以网络字节顺序存放的。
最后一个sin_zero[8]是用来充当填充任务的字节。因为原始的用来指定socket信
息的结构体并不是sockaddr_in而是sockaddr,其定义如下:
struct sockaddr {
unsigned short sa_family; /* 地址族, AF_xxx */
char sa_data[14]; /* 14 字节的协议地址 */ };
他们2者的大小是相等的。但是为了表示方便,人们定义了第一种看起来更明白的
结构来代替sockaddr。指向sockaddr_in 的指针和指向sockaddr的指针可以相互
转换,这意味着如果一个函数所需参数类型是sockaddr时,你可以在函数调用的
时候将一个指向sockaddr_in的指针转换为指向sockaddr的指针;或者相反。
if(bind(L,(struct sockaddr*)&local,sizeof(local))==SOCKET_ERROR)
这时我们已经通过socket调用返回一个socket描述符,并且也已经指定了即将使
用的socket的地址和端口。这时应该将该socket与本机上的一个端口相关联(往
往当在设计服务器端程序时需要调用该函数。随后就可以在该端口监听服务请求;
而客户端一般无须调用该函数)。
Bind函数原型为:
int bind(int sockfd,struct sockaddr *my_addr, int addrlen);
Sockfd是一个socket描述符,my_addr是一个指向包含有本机IP地址及端口号等
信息的sockaddr类型的指针;addrlen常被设置为sizeof(struct sockaddr)。
如果发生错误会返回SOCKET_ERROR.
绑定成功后,就应该调用listen函数开始监听端口。
listen(L,5);//进入listen状态,端口打开
其中L是刚刚绑定的套接字描述符,5是指定的能同时连接的最大数目,系统将为
这个套接字分配一个队列用来存放请求。最大请求数为5,一旦超过就会被系统丢
弃。这个数一般都可以自己指定。
R=accept(L,(struct sockaddr*)&client,&ret_len);
这是服务端程序特有的代码。为已知的socket接受一个请求。当客户端调用
connect时,服务端会受到一个请求,当调用accept后,系统会返回一个新的套接
字用于通信,而原来的套接字继续用来监听其他连接。函数定义如下:
int accept(int sockfd, struct sockaddr *addr,int *addrlen)
sockfd:是listen后的socket文件描述符.
addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了.
bind,listen和accept是服务器端用的函数,accept调用时,服务器端的程序会一直
阻塞到有一个 客户程序发出了连接. accept成功时返回最后的服务器端的文件描
述符,这个时候服务器端可以向该描述符写信息了.也就是管道正式可以使用了。
失败时返回-1 (INVAILD_SOCKET)
ret=recv(R,data,255,0);
这个函数用来从已经建立的套接字接受信息。注意这里的R为accept之后返回的套
接字。而非原来的监听套接字L。函数定义如下:
int recv(SOCKET s,char *buf,int len,int flags);
s为调用accept之后的返回的新的套接字描述符。buf为存放接受到的资料的缓冲
区。len 是buf的长度,flags为此函数被调用的方式。一般为0表示拷贝缓冲中的
内容并移走。成功的时候返回接受到的资料的字节长度,失败返回SOCKET_ERROR.
//--------------------client---------------------------------------
#pragma comment (lib,"Ws2_32.lib")//添加库文件
#include
#include
void main(int argc,char*argv[])
{WSADATA WSAData;//定义一个WSADATA型数据,用来存放系统返回的一些关于
//winsock stack的资料
struct sockaddr_in server;
SOCKET C;
int ret;
char data[255];
if(argc!=2)
{printf("usage: client destip\n");
exit(0);
}
if(WSAStartup(MAKEWORD(2,2),&WSAData))//初始化winsock
exit(0);
C=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(C==SOCKET_ERROR)
exit(0);
server.sin_family=AF_INET;
server.sin_port=htons(1985);
server.sin_addr.s_addr=inet_addr(argv[1]);
ret=connect(C,(struct sockaddr*)&server,sizeof(server));
if(ret==SOCKET_ERROR)
{printf("connect error!\n");
exit(0);
}
printf("connect successed!\nplease input the message you want to send:");
scanf("%s",data);
ret=send(C,data,strlen(data),0);
if(ret== SOCKET_ERROR)
{printf("send error!ERROR:%d\n",WSAGetLastError());
exit(0);
}
closesocket(C);
WSACleanup();
exit(0);
}
|