网络编程

来源:互联网 发布:阿里云备案账号 编辑:程序博客网 时间:2024/04/30 22:49
个人认为黑客的精髓是网络编程
网络编程有很多(不是ASP,JSP等)下面给大家介绍下,大家应该都有C的基础吧(没有?我倒……回家补习去)
本章主要介绍一下网络编程的基本知识。由于书中后面章节都有一些简单的源程序实例来对各章的基本概念进行解释,因此必须具备必要的网络编程知识。
在平时工作中,为了查找安全漏洞,也需要编写一些短小精悍的程序来代替复杂的手工命令输入。
在操作系统一章中对Linux中的C语言编程和调试已经作了介绍。本章在前两章的基础上,首先对Linux中的网络编程作介绍,Linux对网络通信提供了很好的支持。由于Windows系统目前很流行,特别是开发环境Visual C++,所以,本章也对Windows环境下的网络编程作了介绍。
第一节 Linux网络编程(Berkeley Sockets)
我们可以认为套接字是将Unix系统的文件操作推广到提供点对点的通信。如果要操作文件,应用程序会根据应用程序的需要为之创建一个套接字。操作系统返回一个整数。应用程序通过引用这个正数来使用这个套接字。文件描述符和套接字描述符的不同点在于,在程序调用open()时,操作系统将一个文件描述符绑定到一个文件或设备,但在创建一个套接字时,可以不将它绑定到一个目标地址。程序可以在任何想要用这个套接字的时候指定目标地址。
在点对点的通信程序中,我们将请求服务或数据的程序叫做客户端程序,提供数据或服务的软件叫做服务器程序。
图1是一个面向连接的服务器程序和客户端程序的流程图。
对于使用无连接协议的服务器程序和客户端程序的流程,请参见图2。图中,客户端程序并不和服务器程序建立连接,它是通过使用服务器地址作为参数的sendto()系统调用,发送一个数据报给服务器的。同样,服务器并不接受客户端的连接,而是用recvfrom()调用等待从客户端来的数据。

套接字系统调用
  下面解释一下几个基本的套接字系统调用函数。只要你将下面的函数与系统的输入输出函数调用加以对比,就能很快地掌握这些函数调用了。 socket()
------------------------------------------------------------
#include < sys/types.h>
#include < sys/socket.h>

int socket(int family, int type, int protocol);
------------------------------------------------------------

int family参数指定所要使用的通信协议,取以下几个值。
值 含义
AF_UNIX Unix内部协议
AF_INET Internet协议
AF_NS Xerox NS协议
AF_IMPLINK IMP 连接层

int type 指定套接字的类型,取以下几个值

值 含义
SOCK_STREAM 流套接字
SOCK_DGRAM 数据报套接字
SOCK_RAW 未加工套接字
SOCK_SEQPACKET 顺序包套接字

int protocol 参数通常设置为0。

  socket()系统调用返回一个整数值,叫做套接字描述字sockfd,它的原理与文件描述符一样。网络I/O的第一步通常就是调用这个函数。

socektpair()
------------------------------------------------------------
#include < sys/types.h>
#include < sys/socket.h>

int socketpair(int family, int type, int protocol, int sockvec[2]);
------------------------------------------------------------

  这个调用返回两个套接字描述符, sockvec[0]和sockvec[1],它们没有名字,但是连着的。这个调用与管道系统调用类似。由这个调用创建的结构叫做一个流管道。

bind()
------------------------------------------------------------
#include < sys/types.h>
#include < sys/socket.h>

int bind(int sockfd, struct sockaddr *myaddr, int addrlen);
------------------------------------------------------------

  这个调用将一个名字命名给一个没有名字的套接字。第二个参数myaddr是指向一个特定协议地址的指针,第三个参数是这个地址结构的大小。
  bind()有三个作用:
   服务器在系统里登记它们的地址
   客户为它自己注册一个地址
   一个没有连接的客户确保系统固定分配给它一个唯一的地址

connect()
------------------------------------------------------------
#include < sys/types.h>
#include < sys/socket.h>

int connect(int sockfd, struct sockaddr *servaddr, int addrlen);
------------------------------------------------------------

  这个过程在socket()调用后,将一个套接字描述符和一个与服务器建立的连接的联系。sockfd是一个由socket()调用返回的套接字描述符。第二个参数是服务器套接字地址的指针,第三个参数是这个地址的长度。

listen()
------------------------------------------------------------
#include < sys/types.h>
#include < sys/socket.h>

int listen(int sockfd, int backlog)
------------------------------------------------------------

  面向连接的服务器使用这个系统调用,来表示它希望接受连接。
  这个系统调用通常在socket()和bind()之后,在accept()调用之前调用。参数backlog表示当它们等待执行accept()系统调用之前,系统能对多少个连接请求进行排队。

accept()
------------------------------------------------------------
#include < sys/types.h>
#include < sys/socket.h>

int accept(int sockfd, struct sockaddr *peer, int *addrlen);
------------------------------------------------------------
  在一个建立好连接的服务器执行了listen()系统调用之后,一个实际和客户的连接过程等待服务器调用accept()系统调用。
  accept()取出在队列里的第一个连接请求,并且创建另一个和sockfd有相同属性套接。如果队列中没有连接请求,这个调用就将调用者阻塞,知道有请求为止。
  peer和addrlen 参数用来返回连接的客户的地址。调用者在调用之前设置addrlen的值,系统调用通过它返回一个值。

send(), sendto(), recv(), recvfrom()
------------------------------------------------------------
#include < sys/types.h>
#include < sys/socket.h>

int send(int sockfd, char *buff, int nbytes, int flags);

int sendto(int sockfd, char *buff, int nbytes, int flags,
struct sockaddr *to, int addrlen);

int recv(int sockfd, char *buff, int nbytes, int flags);

int recvfrom(int sockfd, char *buff, int nbytes, int flags,
struct sockaddr *from, int addrlen);
------------------------------------------------------------

  这些调用与标准的系统调用read()和write()相似。
  这些调用需要附加的参数。Flag参数可以是0或者下列常数:
   MSG_OOB 接受或发送绑定外的数据
   MSG_PEEK 监视进入信息
   MSG_DONTROUTE 绕过路由

close()
------------------------------------------------------------
#include < sys/types.h>
#include < sys/socket.h>

int close(int sockfd);
------------------------------------------------------------

  关闭一个套接字。
编程实例
从一个描述符读n字节数据
/* 从一个描述符读n字节数据 */
int readn(register int fd, register char *ptr, register int nbytes)
{
int nleft, nread;

nleft=nbytes;
while (nleft > 0){
nread=read(fd,ptr,nleft);
if(nread < 0)
return(nread);
else if (nread==0)
break;
nleft-=nread;
ptr +=nread;
}
return(nbytes - nleft);
}

写n字节数据到一个描述符
/* 写n字节数据到一个描述符 */
int writen(register int fd, register char *ptr, register int nbytes)
{
int nleft, nwritten;
nleft=nbytes;
while(nleft>0){
nwritten=write(fd,ptr,nleft);
if(nwritten< =0)
return(nwritten);
nleft -= nwritten;
ptr += nwritten;
}
return(nbytes-nleft);}

TCP编程
/* inet.h
* 服务器和客户端程序的头文件。
*/
#include < stdio.h>
#include < sys/types.h>
#include < sys/socket.h>
#include < netinet/in.h>
#include < arpa/inet.h>

#define SERV_UDP_PORT 6000
#define SERV_TCP_PORT 6000
#define SERV_HOST_ADDR "192.43.235.6" /* host addr for server */

char *pname;

服务器程序如下:
/* TCP服务器程序 */
#include "inet.h"

main(int argc, char * argv)
{
int sockfd, newsockfd, clilen, childpid;
struct sockaddr_in cli_addr, serv_addr;

pname = argv[0];

/* 打开一个TCP套接字 (一个Internet流套接字) */

if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_dump("server: can't open stream socket");

/* 绑定本地地址,这样,客户机就能访问到服务器。*/

bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(SERV_TCP_PORT);

if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
err_dump("server: can't bind local address");

listen(sockfd, 5);

for ( ; ; ) {
/* 等待一个来自客户机的连接进程,这是一个并发的服务器。*/

clilen = sizeof(cli_addr);
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0)
err_dump("server: accept error");

if ( (childpid = fork()) < 0)
err_dump("server: fork error");

else if (childpid == 0) { /* 子进程 */
close(sockfd); /* 关闭原来的套接字 */
str_echo(newsockfd); /* 处理请求 */
exit(0);
}

close(newsockfd); /* 父进程 */
}
}

服务机代码:
/* 使用TCP协议客户机 */
#include "inet.h"

main(argc, argv)
int argc;
char *argv[];
{
int sockfd;
struct sockaddr_in serv_addr;

pname = argv[0];

/* 在结构"serv_addr"里填入想要连接的服务器的地址*/

bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(SERV_HOST_ADDR);
serv_addr.sin_port = htons(SERV_TCP_PORT);

/* 打开一个TCP套接字(一个Internet 流套接字) */

if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_sys("client: can't open stream socket");

/* 连到服务器上*/

if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
err_sys("client: can't connect to server");

str_cli(stdin, sockfd); /* 全部输出 */

close(sockfd);
exit(0);
}

套接字和信号量
  在使用一个套接字时,可以产生三个信号量。
   (SIGIO) 这个信号量表示一个套接字已经准备好进行异步I/O了。这个信号量会发给这个套接字的所有进程。这些进程是通过用FIOSETOWN 或 SIOCSPGRP 调用ioctl而建立的。或者是用F_SETOWN调用fcntl建立的。这个信号量只在这个进程在这个套接字上,用FIOASYNC调用ioctl或用FASYNC调用fcntl后,可以进行异步I/O后发给这些进程的。
   (SIGURG) 这个信号量表示出现了一个紧急情形。一个紧急情形是任何一个在套接字上一个出现了一个超过带宽的数据的到达信息。超过带宽表示在用户进程到达的数据超出了I/O缓冲区了。
   (SIGPIPE) 这个信号量表明我们不再会向套接字,管道或FIFO写数据了。
异步I/O
  异步I/O允许进程通知操作系统内核,如果一个指定的描述符可以进行I/O时,内核通知该进程。这通常叫做信号量驱动I/O。内核通知进程的信号量是SIGIO。
  为了实现异步I/O,一个进程必须:
   建立一个处理SIGIO信号量的程序。
   将进程ID或进程组ID设置好,能接受SIGIO信号量。这是由fcntl命令实现的。
   进程必须用dcntl系统调用,激活异步I/O。
第二节 Windows网络编程(WinSock)
  这里介绍WinSock创建TCP流套接字程序。Winsock的编程和第一部分将的非常的相似。

创建TCP流套接字服务器程序
  用socket()函数打开一个流套接字。用AF_INET指定地址格式参数,SOCK_STREAM指定类型参数。

if ((WinSocket = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
wsprintf (szError, TEXT("Allocating socket failed. Error: %d"),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
return FALSE;
}

  使用SOCKADDR_IN结构作为地址参数,用bind()函数命名套接字。
  用socket()函数打开一个套接字时,这个套接字没有名字,仅仅在一个地址家族名字空间里分配了一个描述符。为了让客户端套接字区分开来,一个TCP流套接字服务器程序必须命名它的套接字。但不必用bind()函数命名客户端的套接字。
  一个套接字的名字在TCP/TP协议里由三部分组成:协议名称,主机地址和一个表征应用程序的端口数字。这些地址域sin_family, sin_addr, sin_port都是SOCKADDR_IN结构的成员。必须在调用bind()之前初始化SOCKADDR_IN结构。
  下面的这段代码示范怎样初始化SOCKADDR_IN结构和调用bind()函数。

// 填写本地套接字地址数据
local_sin.sin_family = AF_INET;
local_sin.sin_port = htons (PORTNUM);
local_sin.sin_addr.s_addr = htonl (INADDR_ANY);

// 将本地地址和WinSocket相连
if (bind (WinSocket,
(struct sockaddr *) &local_sin,
sizeof (local_sin)) == SOCKET_ERROR)
{
wsprintf (szError, TEXT("Binding socket failed. Error: %d"),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
closesocket (WinSocket);
return FALSE;
}

  使用listen()函数侦听。为了准备一个TCP流套接字服务器的一个名字连接,必须侦听从客户端来的连接。
  下面这个例子说明了怎样使用listen()函数。

if (listen (WinSocket, MAX_PENDING_CONNECTS) == SOCKET_ERROR)
{
wsprintf (szError,
TEXT("Listening to the client failed. Error: %d"),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
closesocket (WinSocket);
return FALSE;
}

  使用accept()接受客户端的连接。
  TCP流服务器套接字使用这个函数来完成服务器和客户端的名字连接过程。
  Accept()函数创建一个新的套接字。初始的由服务器打开的套接字继续侦听该端口,可以一直接受连接,知道关闭。服务器程序必须负责关闭侦听套接字以及在接受客户连接是创建的所有套接字。
  下面的代码是accept()函数应用的示范。

accept_sin_len = sizeof (accept_sin);

// 接受一个试图在WinSocket上连接的请求
ClientSock = accept (WinSocket,
(struct sockaddr *) &accept_sin,
(int *) &accept_sin_len);

// 停止对客户连接的侦听
closesocket (WinSocket);

if (ClientSock == INVALID_SOCKET)
{
wsprintf (szError, TEXT("Accepting connection with client
failed.") TEXT(" Error: %d"), WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
return FALSE;
}



http://202.101.18.235/club/bbs/showEssence.asp?id=21876