网络编程入门(windows&linux)

 LINUX:

 网络编程,一定离不开套接口;那什么是套接口呢?在Linux下,所有的I/O操作都是通过读写文件描述符而产生的,文件描述符是一个和打开的文件相关联的整数,这个文件并不只包括真正存储在磁盘上的文件,还包括一个网络连接、一个命名管道、一个终端等,而套接口就是系统进程和文件描述符通信的一种方法。目前最常用的套接口是字:字节流套接口(基于TCP)和数据报套接口(基于UDP),当然还有原始套接口(原始套接口提供TCP套接口和UDP套接口所不提供的功能,如构造自己的TCP或UDP分组)等,我们这里主要介绍字节流套接口和数据报套接口。

 

  要学习网络编程,一定离不开网络库的函数,在Linux系统下,可以用"man 函数名"来得到这个函数的帮助,不过为了照顾E文不大好的朋友,下面就将常用的网络函数和用法列出来供大家参考:

 

  1、socket函数:为了执行网络输入输出,一个进程必须做的第一件事就是调用socket函数获得一个文件描述符。

 


-----------------------------------------------------------------
#include
int socket(int family,int type,int protocol);    
    返回:非负描述字---成功   -1---失败
-----------------------------------------------------------------

 

  第一个参数指明了协议簇,目前支持5种协议簇,最常用的有AF_INET(IPv4协议)和AF_INET6(IPv6协议);第二个参数指明套接口类型,有三种类型可选:SOCK_STREAM(字节流套接口)、SOCK_DGRAM(数据报套接口)和SOCK_RAW(原始套接口);如果套接口类型不是原始套接口,那么第三个参数就为0。

 

  2、connect函数:当用socket建立了套接口后,可以调用connect为这个套接字指明远程端的地址;如果是字节流套接口,connect就使用三次握手建立一个连接;如果是数据报套接口,connect仅指明远程端地址,而不向它发送任何数据。

 


-----------------------------------------------------------------
#include       
int connect(int sockfd,const struct sockaddr * servaddr,socklen_t
addrlen);  
          返回:0---成功   -1---失败
-----------------------------------------------------------------

 

  第一个参数是socket函数返回的套接口描述字;第二和第三个参数分别是一个指向套接口地址结构的指针和该结构的大小。

 

  这些地址结构的名字均已“sockaddr_”开头,并以对应每个协议族的唯一后缀结束。以IPv4套接口地址结构为例,它以“sockaddr_in”命名,定义在头文件;以下是结构体的内容:

 


------------------------------------------------------------------
struct in_addr {
in_addr_t s_addr;     
};
struct sockaddr_in {
uint8_t sin_len;
sa_family_t sin_family;

in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};   
-------------------------------------------------------------------

 

  3、bind函数:为套接口分配一个本地IP和协议端口,对于网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位的TCP或UDP端口号的组合;如指定端口为0,调用bind时内核将选择一个临时端口,如果指定一个通配IP地址,则要等到建立连接后内核才选择一个本地IP地址。

 


-------------------------------------------------------------------
#include  
int bind(int sockfd,const struct sockaddr * myaddr,socklen_t
addrlen);
返回:0---成功   -1---失败 
-------------------------------------------------------------------

 

  第一个参数是socket函数返回的套接口描述字;第二和第第三个参数分别是一个指向特定于协议的地址结构的指针和该地址结构的长度。

 

  4、listen函数:listen函数仅被TCP服务器调用,它的作用是将用sock创建的主动套接口转换成被动套接口,并等待来自客户端的连接请求。

 


-------------------------------------------------------------------
#include
int listen(int sockfd,int backlog);   
返回:0---成功   -1---失败
-------------------------------------------------------------------

 

  第一个参数是socket函数返回的套接口描述字;第二个参数规定了内核为此套接口排队的最大连接个数。由于listen函数第二个参数的原因,内核要维护两个队列:以完成连接队列和未完成连接队列。未完成队列中存放的是TCP连接的三路握手为完成的连接,accept函数是从以连接队列中取连接返回给进程;当以连接队列为空时,进程将进入睡眠状态。

 

  5、accept函数:accept函数由TCP服务器调用,从已完成连接队列头返回一个已完成连接,如果完成连接队列为空,则进程进入睡眠状态。

 


-------------------------------------------------------------------
#include         
int accept(int sockfd,struct sockaddr *
cliaddr,socklen_t * addrlen);  
回:非负描述字---成功   -1---失败
-------------------------------------------------------------------

 

  第一个参数是socket函数返回的套接口描述字;第二个和第三个参数分别是一个指向连接方的套接口地址结构和该地址结构的长度;该函数返回的是一个全新的套接口描述字;如果对客户段的信息不感兴趣,可以将第二和第三个参数置为空。

 

  6、inet_pton函数:将点分十进制串转换成网络字节序二进制值,此函数对IPv4地址和IPv6地址都能处理。

 


-------------------------------------------------------------------
#include
int inet_pton(int family,const char * strptr,void * addrptr);
返回:1---成功 0---输入不是有效的表达格式 -1---失败
-------------------------------------------------------------------

 

  第一个参数可以是AF_INET或AF_INET6:第二个参数是一个指向点分十进制串的指针:第三个参数是一个指向转换后的网络字节序的二进制值的指针。

 

  7、inet_ntop函数:和inet_pton函数正好相反,inet_ntop函数是将网络字节序二进制值转换成点分十进制串。

 


-------------------------------------------------------------------
#include  
const char * inet_ntop(int family,const void *
addrptr,char * strptr,size_t len);  
   返回:指向结果的指针---成功   NULL---失败
-------------------------------------------------------------------

 

  第一个参数可以是AF_INET或AF_INET6:第二个参数是一个指向网络字节序的二进制值的指针;第三个参数是一个指向转换后的点分十进制串的指针;第四个参数是目标的大小,以免函数溢出其调用者的缓冲区。

 

  8、fock函数:在网络服务器中,一个服务端口可以允许一定数量的客户端同时连接,这时单进程是不可能实现的,而fock就分配一个子进程和客户端会话,当然,这只是fock的一个典型应用。

 


-------------------------------------------------------------------
#include  
pid_t fock(void);
返回:在子进程中为0,在父进程中为子进程ID   -1---失败
-------------------------------------------------------------------

 

  fock函数调用后返回两次,父进程返回子进程ID,子进程返回0。

 

  有了上面的基础知识,我们就可以进一步了解TCP套接口和UDP套接口

 

  1、TCP套接口

 

  TCP套接口使用TCP建立连接,建立一个TCP连接需要三次握手,基本过程是服务器先建立一个套接口并等待客户端的连接请求;当客户端调用connect进行主动连接请求时,客户端TCP发送一个SYN,告诉服务器客户端将在连接中发送的数据的初始序列号;当服务器收到这个SYN后也给客户端发一个SYN,里面包含了服务器将在同一连接中发送的数据的初始序列号;最后客户在确认服务器发的SYN。到此为止,一个TCP连接被建立。

 

  下面就用二个例子来说明服务器和客户是怎么连接的:

例子1:

 


-------------------------------------------------------------------

#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[]) {
int sockfd,numbytes;
char buf[100];
struct hostent *he;
struct sockaddr_in their_addr;
int i = 0;
//将基本名字和地址转换
he = gethostbyname(argv[1]);
//建立一个TCP套接口
if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) {
perror("socket");
exit(1);
}
//初始化结构体,连接到服务器的2323端口
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(2323);
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero),8);
//和服务器建立连接
if(connect(sockfd,(struct sockaddr *)&their_addr,
sizeof(struct sockaddr))
==-1){
perror("connect");
exit(1);
}
//向服务器发送字符串"hello!"
if(send(sockfd,"hello!",6,0)==-1) {
perror("send");
exit(1);
}
//接受从服务器返回的信息
if((numbytes = recv(sockfd,buf,100,0))==-1) {
perror("recv");
exit(1);
}
buf[numbytes] = '';
printf("result:%s",buf);
close(sockfd);
return 0;
}
--------------------------------------------------------------------

#include
#include
#include
#include
#include
#include
#include
#include
main() {
int sockfd,new_fd;
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
int sin_size;
//建立TCP套接口
if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) {
perror("socket");
exit(1);
}
//初始化结构体,并绑定2323端口
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(2323);
my_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(my_addr.sin_zero),8);
//绑定套接口
if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct
sockaddr))==-1)
{
perror("bind");
exit(1);
}
//创建监听套接口
if(listen(sockfd,10)==-1) {
perror("listen");
exit(1);
}
//等待连接
while(1) {
sin_size = sizeof(struct sockaddr_in);
perror("server is run");
//如果建立连接,将产生一个全新的套接字
if((new_fd = accept(sockfd,(struct sockaddr *)
&their_addr,&sin_size))==-1)
{
perror("accept");
exit(1);
}
//生成一个子进程来完成和客户端的会话,父进程继续监听
if(!fork()) {
//读取客户端发来的信息
if((numbytes = recv(new_fd,buff,strlen(buff),0))==-1)
{
perror("recv");
exit(1);
}
printf("%s",buff);
//将从客户端接收到的信息再发回客户端
if(send(new_fd,buff,strlen(buff),0)==-1)
perror("send");
close(new_fd);
exit(0);
}
close(new_fd);
}
close(sockfd);
}
------------------------------------------------------------------

 

   现在让我们来编译这两个程序:

 


root@linuxaid#gcc -o server server.c
root@linuxaid#gcc -o client client.c

 

  然后在一台计算机上先运行服务器程序,再在另一个终端上运行客户端就会看到结果;如果不运行服务器程序而先运行客户程序将立即提示"Connect: Connection refused",这就是TCP套接口的好处,如果是UDP套接口将会有一个延时才会得到错误信息(UDP套接口后面有介绍)。

 

  建立一个TCP连接需要三次握手,而断开一个TCP则需要四个分节。当某个应用进程调用close(主动端)后(可以是服务器端,也可以是客户端),这一端的TCP发送一个FIN,表示数据发送完毕;另一端(被动端)发送一个确认,当被动端待处理的应用进程都处理完毕后,发送一个FIN到主动端,并关闭套接口,主动端接收到这个FIN后再发送一个确认,到此为止这个TCP连接被断开。

 

  2、UDP套接口

 

  UDP套接口是无连接的、不可靠的数据报协议;既然他不可靠为什么还要用呢?其一:当应用程序使用广播或多播是只能使用UDP协议;其二:由于他是无连接的,所以速度快。因为UDP套接口是无连接的,如果一方的数据报丢失,那另一方将无限等待,解决办法是设置一个超时。

 

  在编写UDP套接口程序时,有几点要注意:建立套接口时socket函数的第二个参数应该是SOCK_DGRAM,说明是建立一个UDP套接口;由于UDP是无连接的,所以服务器端并不需要listen或accept函数;当UDP套接口调用connect函数时,内核只记录连接放的IP地址和端口,并立即返回给调用进程,正因为这个特性,UDP服务器程序中并不使用fock函数,用单进程就能完成所有客户的请求。

 

例子2:

1.头文件介绍

errno.h

返回错误信息,用的是perro(),所以头文件有errno.h

netdb.h

定义struct hostent *gethostbyname(const char *hostname)要用的头文件.

#include
#include
#include

可能是网络编程用套节字必须的吧!

这是客户端程序,从服务器接受数据,别写入文件中.



#include
#include
#include
#include
#include
#include
#include
#include
#define SERVPORT 3333
#define MAXDATASIZE 100

int main(int argc, char *argv[])
{
 int sockfd, recvbytes;
 char buf[MAXDATASIZE];
 FILE *fp;
 struct hostent *host;
 struct sockaddr_in serv_addr;
 if( argc < 2)
    {           
        fprintf(stderr, "Please enter the server's hostname!/n");
        exit(1);
    }

    if((host=gethostbyname(argv[1]))==NULL)
    {
        herror("gethostbyname出错!");
        exit(1);
    }

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket创建出错!");
        exit(1);
    }

    serv_addr.sin_family=AF_INET;
    serv_addr.sin_port=htons(SERVPORT);
    serv_addr.sin_addr = *(struct in_addr*)host->h_addr;
    bzero(&(serv_addr.sin_zero),8);

    if (connect(sockfd, (struct sockaddr *)&serv_addr,
                        sizeof(struct sockaddr)) == -1)
    {
        perror("connect出错!");
        exit(1);
    }

    if ((fp = fopen("output_file", "w")) == NULL)
    {
        printf ("can't open file");
        exit(0);
    }

    while (1)
    {
        memset(buf, 0, 100);  
        recvbytes=recv(sockfd, buf, 100, 0);

        if (recvbytes == 0)  
        {
            printf("数据已经接收完!");
            close(fp);     
            close(sockfd);  
            exit(0);
        }

        printf("%s", buf);
        fprintf(fp, "%s", buf); 
    }

    close (fp);
    close(sockfd);

    return 0;
}

 

服务器从文件读入数据到缓冲区,再发送给客户端.



#include
#include
#include
#include
#include
#include
#include
#include
#define SERVPORT 3333
#define BACKLOG 10
#define MAXDATASIZE 100

int main()
{
     int sockfd,client_fd;
     char buff[MAXDATASIZE];
     FILE *fp;
     struct sockaddr_in my_addr;
     struct sockaddr_in remote_addr;
     socklen_t sin_size = sizeof(struct sockaddr_in);
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
         perror("socket创建出错!");
         exit(1);
    }

    my_addr.sin_family=AF_INET;
    my_addr.sin_port=htons(SERVPORT);
    my_addr.sin_addr.s_addr = inet_addr("192.168.1.211");
    bzero(&(my_addr.sin_zero),8);

    if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))
                                    == -1)
    {
        perror("bind出错!"); 
        exit(1);
    }

    if (listen(sockfd, BACKLOG) == -1) 
    {
        perror("listen出错!");
        exit(1);
    }
    printf("server is running......../n");

    if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr,
            &sin_size)) == -1)   
    {
        perror("accept出错");
        exit(1);
    }
    else
    {
        printf("客户端已经连上/n");
    }

    printf("开始发送数据......./n");

    if ((fp = fopen("input_file", "r")) == NULL)
    {
        printf ("can't open file");
        exit (0);
    }

    while (!feof(fp))
  {
        if (fgets(buff, 256, fp) == NULL)  
        {
            close(client_fd);
            close(sockfd);
            printf("发送完毕,退出!/n");
            exit(0);
        }

        if (send(client_fd, buff, 100, 0) == -1)
        {
            perror("send出错!");
            close(client_fd);
            exit(0);
        }

        printf ("send buff = %s", buff);
    }

    close(fp);
    close(client_fd);
    close(sockfd);

    return 0;
}

 

这个是input_file中的信息

name:xiaoxia sex:male age:90
name:xiaoxiao sex:male age:12
name:x sex:male age:15
name:xi sex:male age:32
name:xia sex:male age:18
name:xiao sex:male age:17
name:xiaox sex:male age:16
~

大家有兴趣的话可以拷贝程序试验,需要两个屏幕操作,结果所得到的文件output_file和input_file信息格式一样.

 

windows:

   C语言的学习,一般的方式是,先学C,然后是C++,最好还要有汇编语言和微机原理基础,然后才是Visual C++。这样的方式,对学习者来说,要花费很多时间和耐力。而在学校教学中,也没有时间深入学习Windows编程的实用技术了。

  其实,具有了C语言基础后,再有一些基本的C++类的概念,就可以直接学习Windows C编程了。

  一、走近Windows C语言

  很多语言都把显示一个“Hello,World!”做为第一个入门程序, C语言的第一个程序是这样的:

#include
main()
{
 printf(“Hello,World!”);
}

  如果把main函数写成带参数的main函数,应该是:

#include
main(int arge,char *argv[])
{
 printf(“Hello,World!”);
}

  Windows C的第一个程序和这个程序在形式和原理上都是一致的,只是有两点不同:

  1. 主函数接收的形参不只是命令行中的字符串的个数和字符串的首地址。

  2. C语言的很多函数在Windows C中都可以继续使用,但象printf()屏幕显示等函数就不能继续使用了。因为Windows是多任务 操作系统,屏幕已不再为某一个应用程序所独有,Windows C应用程序要显示字符串,需要使用Windows提供的API函数,开自己的窗口

  下面是一个最简单的,显示“Hello,World!”的Windows C程序:

#include
APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpCmdLine,int nCmdShow)
{
 MessageBox(NULL,"Hello,World!","第一个Windows C程序",MB_OK|MB_ICONASTERISK);
}

  主函数的形参有四个:

  1) Hinstance:接收程序运行时当前实例的句柄;
  2) HprivInstance:前一个实例的句柄;
  3) LpCmdLine:程序命令行指针;
  4) NcmdShow:一个用来指定窗口显示方式的整数。

  这几个参数的使用我们会在深入的学习中介绍的。

  显示Hello,Word!字符串,我们使用了一个MessageBox函数,这个函数会在屏幕上显示一个对话框,它的原型是:

int MessageBox(HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UNIT uType)

  四个参数分别是:

  1) HWnd:父窗口的句柄;
  2) LpText:要显示字符串的指针;
  3) LpCaption:对话框标题字符串的指针;
  4) UType:显示在对话框上的小图标的类型。

  使用这个函数要包含windows.h头文件。

  调试一下,怎么样?窗口上弹出了一个“第一个Windows C程序”对话框,上面有一行字:“Hello,World!”。

  世界真的很美好啊!!

  深入编程:

  在C语言中,函数的声明,如果没有指明返回值类型,缺省值为void,这个程序的主函数就没有返回值。不过,在Windows编程时,我们最好养成个好习惯,指明函数的返回值类型,因为在C++中,函数返回值类型是不可以缺省的。而我们在Windows C编程时,还是会用到C++的一些概念,这样做,有利于以后深入地学习。

  规范一点的程序应该是这样的:

#include
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpCmdLine,int nCmdShow)
{
MessageBox(NULL,"Hello,World!","第一个Windows C程序",MB_OK|MB_ICONASTERISK);
return 0;
}

  这里,我们声明的类型为int型,并且返回一个值0,这样的函数就可以使用在复杂一点的函数调用中了。

  在这一节中,我们有几处都提到了句柄的概念,句柄和指针的概念不同,它是作为操作系统内部索引表中的一个值来使用的,这样可以防止应用程序直接访问名对象的内部结构,体现了Windows资源管理的优越性。譬如说,一个窗口找开之后,好对应 内存中的一个内存块,这个窗口所在的内存快地址往往会由操作系统做动态的调整,但其却不会随之变化。不过,通过它可以访问这个窗口,所以在使用的时候,可以把它当做指针一样看待。
 
   二、 获取本地计算机的主机名和IP地址

  和C语言一样,函数是Windows C编程的最基本的单位。不过,Windows C主要使用API函数,而 网络编程则主要使用Winsock提供的API函数。

  Winsock是90年代初,为了方便网络编程,由Microsoft联合了其他几家公司共同制定的一套WINDOWS下的网络编程接口,它是通过C语言的动态链接库方式提供给用户及 软件开发者的,主要由winsock.h头文件和动态链接库winsock.dll组成,目前有两个版本:Winsock1.1和Winsock2.0。

  在Win32平台上,访问众多的基层网络协议,Winsock是首选接口。

  用Visual C++6.0编译Windows C程序,使用Winsock API函数时,首先要把wsock32.lib添加到它的库模块中,否刚在链接的时候,会出现“error LNK2001”错误。添加wsock32.lib的具体步骤是:打开工程菜单,选择设置,在弹出的Project settings对话框中,点击link选项卡,然后在对象/库模块文本框中添加wsock32.lib。

  最简单的网络编程是获取本机的主机名和IP地址,这个程序使用了WSAStart()、WSAClenaup()、gethostname()、gethostbyname()四个winsock API函数,这四个函数的功能和使用方法介绍如下:

  1. WSAStartup():

  【函数原型】

int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData);

  【使用说明】

  每一个使用winsock的应用程序,都必须进行WSAStart函数调用,并且只有在调用成功之后才能使用其它的winsock网络操作函数。

  WVersionRequired:<输入>表示欲使用的Winsock版本,这是一个WORD类型的整数,它的高位字节定义的是次版本号,低位字节定义的是主版本号。

  LpWSAData:<输出>是一个指向WSADATA资料的指针。这个资料我们一般不使用。

  返回值:调用成功返回0;否则,返回出错信息。

  2. WSAClenaup():

  【函数原型】

int PASCAL FAR WSACleanup(void);

  【使用说明】

  winsock使用后,要调用WSACleanup函数关闭网络设备,以便释放其占用的资源。

  3.gethostname()

  【函数原型】

int PASCAL FAR gethostname (char FAR * name, int namelen);

  【使用说明】

  该函数可以获取本地主机的主机名,其中:

  name:<输出>用于指向所获取的主机名的缓冲区的指针。

  Namelen:<输入>缓冲区的大小,以字节为单位。

  返回值:若无错误,返回0;否则,返回错误代吗。

  4.gethostbyname()

  【函数原型】

struct hostent FAR * PASCAL FAR gethostbyname(const char FAR * name);

  【使用说明】

  该函数可以从主机名数据库中得到对应的“主机”。

  该函数唯一的参数name就是前面调用函数gethostname()得到的主机名。若无错误,刚返回一个指向hostent结构的批针,它可以标识一个“主机”列表。

  Hostent结构定义如下:

Struct hostent
{
char FAR * h_name;
char FAR FAR ** h_aliases;
short h_addrtype;
char FAR FAR ** h_addr_list;
}

  其中:

  h_name:<输入>主机名地址(PC)。
  h_aliases:一个由主机备用名组成的空中止数组。
  H_addrtype:返回地址的类型,对于Winsock,这个域总是PF_INET。
  H_lenth:每个地址的长度(字节数),对应于PF_INET域应该为4。
  H_addr_list:应该以空指针结尾的主机地址的列表,返回的地址是以网络顺序排列的。

  其中,h_addr_list[0]存放的就是本地主机的4个字节的IP地址,即:

  h_addr_list[0][0].h_addr_list[0][1].h_addr_list[0][2].h_addr_list[0][3]

  一个简单的用消息框显示主机名和IP地址的源程序如下:

#include

int WSA_return;
WSADATA WSAData;

HOSTENT *host_entry;
char host_name[256];
char host_address[256];

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpCmdLine,int nCmdShow)
{
 WSA_return=WSAStartup(0x0101,&WSAData);

 if(WSA_return==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));

   MessageBox(NULL,host_address,host_name,MB_OK);
  }
 }
 WSACleanup();
 return 0;
}

  深入编程:

  前面显示IP地址的时候,我们使用的是消息框,规范一点的编程应该使用对话框,如何编辑一个对话框,很多书中都有介绍,编辑的对话框可参考图5的运行界面。

  头文件Get_IP.h如下:

BOOL APIENTRY Hostname_ipDlgPro(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam);


  这个程序只使用了一个对话框过程,一般把这个过程的声明放在头文件中。

  源程序Get_IP.c:

#include
#include"Get_IP.h"
#include"resource.h" //这个头文件在创建资源的时候会自动生成,
//并会在插入资源时自动生成控件标识号.
int WSA_return;
WSADATA WSAData;

HOSTENT *host_entry;
char host_name[256];
char host_address[256];

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpCmdLine,int nCmdShow)
{
 WSA_return=WSAStartup(0x0101,&WSAData);
 if(WSA_return==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));
  }
 }
 WSACleanup();
 DialogBox(hInstance,"DIALOG1",NULL,(DLGPROC)Hostname_ipDlgPro);
 return 0;
}

BOOL APIENTRY Hostname_ipDlgPro(HWND hDlg,UINT message,
WPARAM wParam,LPARAM lParam)
{
 switch(message)
 {
  case WM_INITDIALOG:
   return(TRUE);
  case WM_COMMAND:
   if(LOWORD(wParam)==IDOK)
   {
    SetDlgItemText(hDlg,IDC_EDIT1,host_name);
    SetDlgItemText(hDlg,IDC_EDIT2,host_address);
    SetDlgItemText(hDlg,IDCANCEL,"确定");
   }
   if(LOWORD(wParam)==IDCANCEL)
    EndDialog(hDlg,TRUE);
    return(TRUE);
   break;
 }
 return(FALSE);
}

 三、利用VisualC++6.0编译Windows C程序

  利用Visual C++6.0编译Windows C程序一般要经过以下四个步骤:新建项目、添加代码、添加资源和编译链接。下面我们简单地介绍一下程序上面介绍的规范的获取本机的主机名和IP地址程序的编译过程:

  (一) 新建项目

  1.启动MicrosoftVisualC++,然后在【文件】菜单中先择【新建】命令,弹出如图1所示的【新建】对话框:


图1


  2.在【新建】对话框中,系统打开的是默认的【工程】选项卡,【工程】选项卡左侧的列表框中有多种建立工程的方式,我们选中“Win32 Application”选项。

  3. 在【位置】文本框中输入新建工程的路径(例如:F:/),在【工程】文本框中输入工程名称(例如:Get_IP)。

  4. 选中【平台】列表框中的Win32复选框,然后单击【确定】按钮。

  5. 在随后的对话框中,都选择默认设置,完成后,进入图2示界面:


图2


  (二) 添加代码

  在VisualC++6.0中,源代码一般存放在源代码文件和头文件中,往项目中添加源代码是非常方便的,为项目新建一个源代码文件一般要按下述方法操作:

  1. 选择【工程】|【添加工程】|【新建】选项,弹出图3所示【新建】对话框:


图3


  2. 在对话框的【文件】选项卡中,左侧的列表框选中“C++ Source File”选项,右侧选中【添加工程】复选框,并在【文件】文本框中输入源文件名(例如:Get_IP.c)。

  3. 单击【确定】按钮,【新建】对话框将被闭,用户就可以在新建的Get_IP.c中输入程序的源代码了。

  4. 添加头文件Get_IP.h的方法和上面所述过程一样,只是在【文件】选项卡中,左侧的列表框要先中“C/C++ Header File”选项。在【文件】文本框中输入头文件名(例如:Get_IP.h)。

  (三) 添加资源

  在添加资源前,必须在项目中先添加一个资源文件,然后可利用Visual C++6.0提供的资源编辑器为项目新建一个资源,具体步骤如下:

  1. 选择【工程】|【添加工程】|【新建】选项,弹出图3所示【新建】对话框。

  2. 在对话框的【文件】选项卡中,左侧的列表框选中“Rsource Script”选项,右侧选中【添加工程】复选框,并在【文件】文本框中输入资源文件名(例如:Get_IP.rc)。

  3. 单击确定,回到主窗口后,选择【插入】|【资源】选项,打开【插入资源】对话框,如图4所示, 在【资源类型】列表框中选中“Dialog”选项,单击【新建】按钮,返回主窗口后,即可利用对话框编辑器进行编辑了。编辑后的对话框如图


图4

图5


  (四) 编译链接

  在添加了源代码与资源文件后,就可以对程序编译连接了,可按Ctrl+F7键编译,按F7键连接,按Ctrl+F5键运行程序。在连接前是要注意,资源文件Get_IP.rc也要进行编译。

  由于这个程序引用了Winsock API函数,在编译连接前,还要添加wsock32.dll,具体方法前面已经介绍过,这里就不再赘述了。

  一点看法:

  利用C语言编写Windows应用程序有两种方式:一种是Windows C编程方式,另一种是Visual C++编程方式。在一般情况下,Visual C++编程方式编写的程序源代码量小、开发时的工作量小、工作难度也较小,但编译后的代码量较大,运行速度略低;而Windows C编程方式编写的程序源代码量虽然较大,但可执行代码效率高。随着技术的进步,Visual C++编程方式已被广泛采用,但象网络编程等一些对速度要求高、对硬件操作较多的程序,大多数还是用Windows C编程方式开发的。另外,学习Windows C程序设计,还有助于更深入地了解Windows的内幕和Windows API。

  从教学角度讲,在学生具备了C语言和其它一些前导课程基础后,直接进入Windows C网络编程等实用编程技术课程,不仅可以让学生尽早地接触到前沿的实用编程技术,而且还可以极大地调动学生的学习积极性,在有限的时间里,学到更多的知识和技术。

你可能感兴趣的:(5.网络协议,1.C/C++探析,windows,编程,网络,linux,struct,socket)