Board logo

标题: 网络程序设计小组[专帖] [打印本页]

作者: x86    时间: 2005-7-15 13:40     标题: 网络程序设计小组[专帖]

[这个贴子最后由x86在 2005/07/23 12:03pm 第 5 次编辑] 一:从一个简单的程序来认识winsock Socket就象两台计算机之间进行通信的接口一样,指定了一个标准。我们可以把 他想象成一个对数据进行包装和发送的管道。 这是一个利用ip获取目标机器名(或者为域名,后面详细说),或者利用目标域 名获取目标ip的程序。 这个程序实际上还没有涉及到管道,不过对理解winsock环境和网络字节有一定帮 助。 visual c++6.0下编译通过。 #pragma comment (lib,"Ws2_32.lib")//添加库文件 #include #include void main(int argc,char* argv[]) { WSADATA WSAData;//定义一个WSADATA型数据,用来存放系统返回的一些关于 //winsock stack的资料 HOSTENT *dest_entry;//一个HOSTENT结构,用来存放一些主机信息 unsigned int destip;//一个无符号整型,存放32位的ip地址 if(argc!=2) { printf("usage: find destname|destip\n"); exit(0); } if(WSAStartup(MAKEWORD(1,1),&WSAData))//初始化winsock { printf("WSAStartup error!\n"); exit(0); } if(inet_addr(argv[1])==-1)//判断参数为ip还是域名 {dest_entry=gethostbyname(argv[1]);//获取主机信息并返回到DEST_ENTRY中 if(dest_entry!=0) { printf("%s:%d.%d.%d.%d", dest_entry->h_name,//通过dest_entry结构中的h_name显示域 //名 (dest_entry->h_addr_list[0][0]&0x00ff),//依次4字节的数 (dest_entry->h_addr_list[0][1]&0x00ff),//据转换为整型。 (dest_entry->h_addr_list[0][2]&0x00ff),// (dest_entry->h_addr_list[0][3]&0x00ff));// exit(0); } else { printf("无法获取目标ip,请检查你的输入,然后重新再试 ^oo^\n"); exit(0); } } else {destip=inet_addr(argv[1]);//将主机字节的ip换成网络字节顺序 dest_entry=gethostbyaddr((char*)(&destip),sizeof((char*)//通过ip获取目 (&destip)),AF_INET);//标HOSTENT信息 if(dest_entry!=0) { printf("%s:%s",argv[1],dest_entry->h_name); exit(0); } else { printf("无法获取目标信息,请检查你的输入,然后重新再试 ^oo^\n"); exit(0); } } } 对代码的解释: #pragma comment (lib,"Ws2_32.lib")//添加库文件 所有的winsock程序开头都必须有这条语句,也可在project--settings--link-- object/library moudles中直接添加。 WSADATA WSAData;//定义一个WSADATA型数据,用来存放系统返回的一些关于 //winsock stack的资料 if(WSAStartup(MAKEWORD(1,1),&WSAData))//初始化winsock { printf("WSAStartup error!\n"); exit(0); } Winsock目前有两个版本:2.2和1.1。在Windows下,Socket是以DLL的形式实现的 。1.1版本的DLL为Winsock.dll,而2.2版本的DLL则为Wsock32.dll,其中在2.2版 本的系统中,对Winsock1.1函数的调用会由Wsock32.dll自动映射到Winsock.dll 。WSAStartup函数的功能就是初始化DLL。每个winsock应用必须加载winsock dll 的相应版本。所以在调用之前必须使用WSAStartup函数加载winsock库!函数定义 如下: int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData); 其中wVersionRequested指定了要加载的winsock版本,其中低字节为主版本,高 字节为副版本!因此该参数可以是0x101或0x202,也可用宏MAKEWORD(X,Y);第二 个参数是一个WSADATA结构,用于接收函数的返回信息!WSAStartup函数调用成功 会返回0,否则返回非0值! HOSTENT *dest_entry;//一个HOSTENT结构,用来存放一些主机信息 这个结构主要用来存放一些主机的信息,如主机名,ip地址等等。结构格式如下 : struct hostent { char FAR * h_name;//主机名 char FAR * FAR * h_aliases;//主机备用名 short h_addrtype;//返回的地址家族 short h_lengtf;//h_addr_list的字节长度 char FAR * FAR * h_addr_list;//主机ip }; h_name字段是正式的主机名。如果网络采用了“域内命名系统”(DNS)。它就是 导致命名服务器返回响应的“全限定域名”(FQDN)。如果网络使用一个本地“ 多主机”文件,主机名就是IP地址之后的第一个条目。h_aliases字段是一个由主 机备用名组成的空中止数组。h_addrtype表示即将返回的地址家族。h_length字 段则对h_addr_list字段中的每一个地址定义字节长度进行定义。h_addr_list字 段是一个由主机IP地址组成的空中止数组(可以为一个主机分配若干个IP地址) 。这个数组中的每个地址都是按网络字节顺序返回的。一般情况下,应用程序都 采用该数组中的第一个地址。但是,如果返回的地址不止一个,应用程序就会相 应地选择一个最恰当的,而不是一直都用第一个地址。 inet_addr(argv[1])==-1)//主机字节到网络字节的转换 计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先。Internet 上数据以高位字节优先顺序在网络上传输,所以对于在内部是以低位字节优先方 式存储数据的机器,在Internet上传输数据时就需要进行转换,否则就会出现数 据不一致。   下面是几个字节顺序转换函数: ·htonl():把32位值从主机字节序转换成网络字节序 ·htons():把16位值从主机字节序转换成网络字节序 ·ntohl():把32位值从网络字节序转换成主机字节序 ·ntohs():把16位值从网络字节序转换成主机字节序 ·inet_addr():函数将点分法表示的IP地址转换为32位网络字节顺序无符号整型 ·inet_ntoa():函数将32位网络字节顺序无符号整型转换为点分法的IP地址 dest_entry=gethostbyname(argv[1]);//获取主机信息并返回到DEST_ENTRY中 dest_entry=gethostbyaddr((char*)(&destip),sizeof((char*)//通过ip获取目 (&destip)),AF_INET);//标HOSTENT信息 这2个函数是程序的关键,函数分别声明如下: struct hostent FAR * gethostbyname(char FAR*name) 参数name是目标host的名称。返回值如果成功则指向一个 hostent结构的指针, 失败返回NULL (可以调用WSAGetLastError()得知错误原因)。从返回的hostent结 构中,可以得到主机的域名,ip地址等信息。 struct hostent FAR * gethostbyaddr(char FAR*addr, int len, int type ); 参数addr是一个网络字节排列方式的ip地址,len是addr的字节长度,type可以为 PF_INET(AF_INET) 返回值如果成功则指向一个hostent结构的指针,失败则返回NULL (同样可以调用 WSAGetLastError()得知错误原因) 我们要记住的东西:初始化winsock,网络字节和主机字节的转换,以及一个重要 的数据结构:struct hostent。我们的目标是得到他并获取其中的信息!
作者: x86    时间: 2005-7-23 12:04     标题: 网络程序设计小组[专帖]

[这个贴子最后由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); }




欢迎光临 黑色海岸线论坛 (http://bbs.thysea.com/) Powered by Discuz! 7.2