LINUX:
网络编程,一定离不开套接口;那什么是套接口呢?在Linux下,所有的I/O操作都是通过读写文件描述符而产生的,文件描述符是一个和打开的文件相关联的整数,这个文件并不只包括真正存储在磁盘上的文件,还包括一个网络连接、一个命名管道、一个终端等,而套接口就是系统进程和文件描述符通信的一种方法。目前最常用的套接口是字:字节流套接口(基于TCP)和数据报套接口(基于UDP),当然还有原始套接口(原始套接口提供TCP套接口和UDP套接口所不提供的功能,如构造自己的TCP或UDP分组)等,我们这里主要介绍字节流套接口和数据报套接口。
要学习网络编程,一定离不开网络库的函数,在Linux系统下,可以用"man 函数名"来得到这个函数的帮助,不过为了照顾E文不大好的朋友,下面就将常用的网络函数和用法列出来供大家参考:
1、socket函数:为了执行网络输入输出,一个进程必须做的第一件事就是调用socket函数获得一个文件描述符。
|
第一个参数指明了协议簇,目前支持5种协议簇,最常用的有AF_INET(IPv4协议)和AF_INET6(IPv6协议);第二个参数指明套接口类型,有三种类型可选:SOCK_STREAM(字节流套接口)、SOCK_DGRAM(数据报套接口)和SOCK_RAW(原始套接口);如果套接口类型不是原始套接口,那么第三个参数就为0。
2、connect函数:当用socket建立了套接口后,可以调用connect为这个套接字指明远程端的地址;如果是字节流套接口,connect就使用三次握手建立一个连接;如果是数据报套接口,connect仅指明远程端地址,而不向它发送任何数据。
|
第一个参数是socket函数返回的套接口描述字;第二和第三个参数分别是一个指向套接口地址结构的指针和该结构的大小。
这些地址结构的名字均已“sockaddr_”开头,并以对应每个协议族的唯一后缀结束。以IPv4套接口地址结构为例,它以“sockaddr_in”命名,定义在头文件
|
3、bind函数:为套接口分配一个本地IP和协议端口,对于网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位的TCP或UDP端口号的组合;如指定端口为0,调用bind时内核将选择一个临时端口,如果指定一个通配IP地址,则要等到建立连接后内核才选择一个本地IP地址。
|
第一个参数是socket函数返回的套接口描述字;第二和第第三个参数分别是一个指向特定于协议的地址结构的指针和该地址结构的长度。
4、listen函数:listen函数仅被TCP服务器调用,它的作用是将用sock创建的主动套接口转换成被动套接口,并等待来自客户端的连接请求。
|
第一个参数是socket函数返回的套接口描述字;第二个参数规定了内核为此套接口排队的最大连接个数。由于listen函数第二个参数的原因,内核要维护两个队列:以完成连接队列和未完成连接队列。未完成队列中存放的是TCP连接的三路握手为完成的连接,accept函数是从以连接队列中取连接返回给进程;当以连接队列为空时,进程将进入睡眠状态。
5、accept函数:accept函数由TCP服务器调用,从已完成连接队列头返回一个已完成连接,如果完成连接队列为空,则进程进入睡眠状态。
|
第一个参数是socket函数返回的套接口描述字;第二个和第三个参数分别是一个指向连接方的套接口地址结构和该地址结构的长度;该函数返回的是一个全新的套接口描述字;如果对客户段的信息不感兴趣,可以将第二和第三个参数置为空。
6、inet_pton函数:将点分十进制串转换成网络字节序二进制值,此函数对IPv4地址和IPv6地址都能处理。
|
第一个参数可以是AF_INET或AF_INET6:第二个参数是一个指向点分十进制串的指针:第三个参数是一个指向转换后的网络字节序的二进制值的指针。
7、inet_ntop函数:和inet_pton函数正好相反,inet_ntop函数是将网络字节序二进制值转换成点分十进制串。
|
第一个参数可以是AF_INET或AF_INET6:第二个参数是一个指向网络字节序的二进制值的指针;第三个参数是一个指向转换后的点分十进制串的指针;第四个参数是目标的大小,以免函数溢出其调用者的缓冲区。
8、fock函数:在网络服务器中,一个服务端口可以允许一定数量的客户端同时连接,这时单进程是不可能实现的,而fock就分配一个子进程和客户端会话,当然,这只是fock的一个典型应用。
|
fock函数调用后返回两次,父进程返回子进程ID,子进程返回0。
有了上面的基础知识,我们就可以进一步了解TCP套接口和UDP套接口
1、TCP套接口
TCP套接口使用TCP建立连接,建立一个TCP连接需要三次握手,基本过程是服务器先建立一个套接口并等待客户端的连接请求;当客户端调用connect进行主动连接请求时,客户端TCP发送一个SYN,告诉服务器客户端将在连接中发送的数据的初始序列号;当服务器收到这个SYN后也给客户端发一个SYN,里面包含了服务器将在同一连接中发送的数据的初始序列号;最后客户在确认服务器发的SYN。到此为止,一个TCP连接被建立。
下面就用二个例子来说明服务器和客户是怎么连接的:
例子1:
|
现在让我们来编译这两个程序:
|
然后在一台计算机上先运行服务器程序,再在另一个终端上运行客户端就会看到结果;如果不运行服务器程序而先运行客户程序将立即提示"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!”); } |
#include main(int arge,char *argv[]) { printf(“Hello,World!”); } |
#include APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow) { MessageBox(NULL,"Hello,World!","第一个Windows C程序",MB_OK|MB_ICONASTERISK); } |
int MessageBox(HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UNIT uType) |
#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 PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData); |
int PASCAL FAR WSACleanup(void); |
int PASCAL FAR gethostname (char FAR * name, int namelen); |
struct hostent FAR * PASCAL FAR gethostbyname(const char FAR * name); |
Struct hostent { char FAR * h_name; char FAR FAR ** h_aliases; short h_addrtype; char FAR FAR ** h_addr_list; } |
#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; } |
BOOL APIENTRY Hostname_ipDlgPro(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam); |
#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网络编程等实用编程技术课程,不仅可以让学生尽早地接触到前沿的实用编程技术,而且还可以极大地调动学生的学习积极性,在有限的时间里,学到更多的知识和技术。