当前位置:首页 > 芯闻号 > 充电吧
[导读]Welcome to my blog — ZwelLDo my best…网络编程本章主要介绍一下网络编程的基本知识。由于书中后面章节都有一些简单的源程序实例来对各章的基本概念进行解释,因此必须具备必

Welcome to my blog — ZwelL

Do my best…


网络编程

本章主要介绍一下网络编程的基本知识。由于书中后面章节都有一些简单的源程序实例来对各章的基本概念进行解释,因此必须具备必要的网络编程知识。
在平时工作中,为了查找安全漏洞,也需要编写一些短小精悍的程序来代替复杂的手工命令输入。
在操作系统一章中对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;
}

  使用send() and recv()函数发送和接受客户的数据。
  一旦客户端和服务端的套接字连接上后,就能使用上述两个函数交换数据。
  Send()函数将数据输出到套接字上。Recv()函数从套接字中读取数据。
  下面的代码是上述两个函数的应用示范。

for (;;)
{
// 从客户端接受数据
iReturn = recv (ClientSock, szServerA, sizeof (szServerA), 0);

// 确认数据收到后,显示数据
if (iReturn == SOCKET_ERROR)
{
wsprintf (szError, TEXT(“No data is received, receive failed.”)
TEXT(” Error: %d”), WSAGetLastError ());
MessageBox (NULL, szError, TEXT(“Server”), MB_OK);
break;
}
else if (iReturn == 0)
{
MessageBox (NULL, TEXT(“Finished receiving data”),
TEXT(“Server”), MB_OK);
break;
}
else
{
// 将ASCII字符串转换成Unicode字符串
for (index = 0; index < = sizeof (szServerA); index++)
szServerW[index] = szServerA[index];

// 显示从客户端接收到的数据
MessageBox (NULL, szServerW, TEXT(“Received From Client”),
MB_OK);
}
}

// 从服务器给客户端发个数据
if (send (ClientSock, “To Client.”, strlen (“To Client.”) + 1, 0)
== SOCKET_ERROR)
{
wsprintf (szError,
TEXT(“Sending data to the client failed. Error: %d”),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT(“Error”), MB_OK);
}

  成功地完成send()函数的调用并不能说明数据的发送是成功的。
  使用closesocket()函数来断开连接。当服务器和客户端数据交换结束后,使用这个函数关闭套接字。为了在一个TCP连接确认数据已经交换了,一个程序应该在调用这个函数之前调用shutdown()函数。
  一个程序应该在程序结束前,关闭所有打开的程序,以便将套接字资源返回给操作系统。对于TCP流套接字,当一个套接字连接结束后,服务器关闭了有accept()创建的套接字,但最先的侦听套接字还是打开的。在程序结束前要将侦听套接字也关闭。

创建TCP流套接字客户端程序
  用socket()函数打开一个流套接字。 调用这个函数时使用AF_INET作为地址格式参数,用SOCK_STREAM做类型参数。
  用SOCKADDR_IN结构作为名字参数调用connect()函数和服务器连接。TCP流套接字客户端通过这个函数将名字和服务器相连。
  在调用connect()函数之前要初始化SOCKADDR_IN 结构,这和bind()函数调用类似,但是sin_port 和sin_addr用远程的套接字名字,而不是本地的。
  下面这段代码显示怎样和服务器相连。

// 建立一个和服务器套接字的连接
if (connect (ServerSock,
(PSOCKADDR) &destination_sin,
sizeof (destination_sin)) == SOCKET_ERROR)
{
wsprintf (szError,
TEXT(“Connecting to the server failed. Error: %d”),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT(“Error”), MB_OK);
closesocket (ServerSock);
return FALSE;
}

  用send()和recv*(函数和服务器交换数据。用closesocker()函数关闭连接。

第三节 MFC中的编程

  Visual C++的MFC提供了CSocket类用来实现网络通信。下图给出了CSocket 类的继承关系。

  下面介绍VC++在Windows 95中实现Socket的 CSocket 类相关成员函数(这些成员函数实际上是从CAsyncSocket 类继承来的)的使用。

(1) BOOL Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, long lEvent = FD_READ |FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT| FD_CLOSE,LPCTSTR lpszSocketAddress = NULL )
  该函数用来建立Socket。 其中,nSocketPort 为所选择的Socket 端口,一般要大于 1023, 如果该参数为0, 则由系统选定一端口,默认值为0 ;nSocketType 为套接字类型:SOCK_STREAM 表示为流套接字,SOCK_DGRAM 表示为数据报套接字,默认值为SOCK_STREAM ;lEvent 标识该Socket 要完成哪种工作,默认值为FD_READ|FD_WRITE|FD_OOB| FD_ACCEPT|FD_CONNECT|FD_CLOSE ;lpszSockAddress 为网络地址信息结构指针,包含网络地址, 默认值为NULL 。

(2)BOOL Bind( UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL )
  该函数的作用是将Socket 端口与网络地址连接起来。参数含义同上 。
(3)BOOL Listen( int nConnectionBacklog = 5 )
  该函数的作用是等待Socket请求。其中,nConnec-tionBacklog 表示等待队列的长度,默认值为最大值5 。

(4)virtual BOOL Accept( CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL )
  该函数的作用是取得队列上第一个连接请求并建立一个具有与Socket相同特性的套接字。其中,rConnectedSocket 表示一个新的Socket 。

(5)BOOL Connect( LPCTSTR lpszHostAddress, UINT nHostPort )
  该函数的作用是提出请求。其中,lpszHostAddress 和 nHostPort 为接受请求进程的网络地址和Socket 端口号。

(6)virtual void Close( )
  该函数的作用是关闭该Socket 。

  利用CSocket类直接进行数据通信有两种方式:一种是利用CSocketFile 类和Archive 类去实现,另一种是利用CSocket的成员函数 Receive、Send、ReceiveFrom、SendTo、Listen 和 Accept 等来实现(这些成员函数实际上也是从CAsyncSocket 类继承的)。
  两种方法的实现步骤如下 :

  Server : Construct-> Creat-> Bind -> Listen-> Accept-> Send->Close ;

  Cilent : Construct ->Creat-> Connect-> Receive-> Close。

   下面就用VC++的代码分别介绍如何运用上述两种方法来实现Socket 编程。

  1、 利用CSocketFile类和Archive 类实现

  (1)服务器程序流程
  // 创建一个套接字对象
  CSocket sockSrvr;

  //为上述套接字对象创建一个套接字

  sockSrvr.Create(nPort);

  //开始侦听
  sockSrvr.Listen( );

  //创建一个新的套接字对象
  CSocket sockRecv;

  //接受连接
  sockSrvr.Accept( sockRecv );

// 创建文件对象
CSocketFile file(&sockRecv);

  //创建一个archive对象
  CArchive arIn(&file, CArchive::load);

  /*or*/_CArchive arOut(&file, CArchive::store);

  //使用archive对象传输数据
  arIn >> dwValue;

  /*or*/ arOut < < dwValue;

  (2)客户端程序流程
  //创建一个套接字对象
  CSocket sockClient;

  //为这个对象创建一个套接字
  sockClient.Create( );

  //寻找一个连接
  sockClient.Connect(strAddr, nPort);

  //创建一个文件对象
  CSocketFile file(&sockClient);

  //创建一个archive对象
  CArchive arIn(&file, CArchive::load);

  /*or*/_CArchive arOut(&file, CArchive::store);

  //使用这个对象传输数据
  arOut < < dwValue;

  /*or*/ arIn >> dwValue;

  上述程序中, nPort 是Socket 的端口号,strAddr 是该机器的IP地址(如202.197.1.3 或 FTP://RedAlert.com等),这两个变量在Server和Client中要一致。当Server进程运行至Listen 后便处于睡眠状态直到Client进程执行Connect 时才被唤醒,而后两个进程便开始传输数据了。

  2、利用CSocket的成员函数实现
  (1)服务器流程
  //套接字初始化
  if(!AfxSocketInit()){
   MessageBox(“WindowsSocket initial failed!”,”Send”,MB_ICONSTOP);
   Return;
  }

  // 创建两个套接字对象
  CSocket ChatSend,server;

  // 创建一个套接字
  if(!ChatSend.Create(nPort)) // nPort=1025
   MessageBox(“SendSocket create failed!”, “Send”,MB_ICONSTOP);
  else{
   // 把本地地址给套接字
ChatSend.Bind(nProt,strAddr);
  // strAddr=”202.196.111.1″
   // 开始侦听
   ChatSend.Listen();

   // 创建一个新的套接字并和他相连
   ChatSend.Accept(Server);
  }
  //发送一个CString 对象
  Server.SendTo(csSendText,csCounts,nPort,strAddr);

  // 关闭这两个套接字
  Server.Close();
  ChatSend.Close();

  (2)客户端程序流程

  // 套接字初始化
  if(!AfxSocketInit()){
   MessageBox(“WindowsSocket initial failed!”, “Receive”,MB_ICONSTOP);
   return;
  }

  // 创建一个套接字对象
  CSocket ChatRecieve;

  // 创建一个套接字
  if(!ChatReceive.Create()){
   MessageBox(“ReceiveSocket create failed!”,”Receive”,MB_ICONSTOP);
   return;
  }
  else{
   // 创建一个对等套接字
   ChatReceive.Connect(strAddr,nPort);
  }

  //接受一个CString 对象
  ChatReceive.ReceiveFrom(csReceiveText,csCounts,strAddr,nPort);

  // 关闭套接字
  ChatReceive.Close();

  上述两个进程完成的工作是:由Server 进程发送一字符串,Client 进程接收。 strAddr 和 nPort 的含义与方法1 中的相同 ;csSendText 和 csReceiveText 为发送与接收的字符串;csCounts为字串长度,这一长度在两个进程中要求接收长度小于或等于发送长度,否则会导致数据传输错误。另外,在程序中要加入头文件afxsock.h, CSocket 类的有关说明均在afxsock.h 中。

方法1 适合于对多个不同类型数据的通信,方法2 适合于对字符串的通信,具体选用何种方法则取决于具体应用的需求。

归类于:Linux— zwell @ 2:28 am 评论(1) 网络程序设计

关于Socket编程,在《Linux从入门到精通》里有简单的介绍,更详细的可以参考《UNIX网络编程 卷1:联网的API:套接字与XTI 第2版》清华影印版,其中还讲了线程(Thread)编程。极好的参考书,可惜没人把它翻译过来。胡淑瑜翻译了一篇“网络编程”,我把它收集进来了。如有更新,请参考胡先生的个人主页。
________________________________________________________________________________ | 版权声明 | | | | 1、本文可以转载、修改及引用,但请保留本声明和其后所付英文原文。 | | 2、任何情况下,作者和译者姓名不可删除。 | | 3、任何情况下,本文不可进入商业领域。 | | | | 胡淑瑜 | | (husuyu@linux.cqi.com.cn) | | | | 1998年11月 | |______________________________________________________________________________| 第59章 目录 网络程序设计 端口(Ports)和套接字(Sockets) 套接字程序设计 socket()系统调用(System Call) bind()系统调用 listen()系统调用 accept()系统调用 setsockopt和getsockopt系统调用 connect()系统调用 程序清单59.1服务器端(Server Side)面向套接字(socket-oriented)协议 程序清单59.2客户端函数(The lient Side function) 无连接(Connectionless)套接字程序设计 程序清单59.3服务端 注意 记录(Record)和文件锁定(Locking) 进程间通信 小结 ------------------------------------------------------------------------------- --第59章-- 网络程序设计 作者 Kamran Husain,Tim Parker 译者 胡淑瑜 本章内容 端口和套接字 套接字程序设计 记录和文件锁定 进程间通信 阅读本章需你具有如下网络程序设计的基本概念 端口和套接字 记录和文件锁定 进程间通信 本文不可能在几页之内就能与你说清网络程序设计的全部内容.事实上,一本第一卷就有 800页的专门描述网络程序设计的参考书是最有用的.如果你真想进行网络编程的话,你需要 具有编译器,TCP/IP和网络操作系统的大量经验.另外,你还需有极大的耐心. 欲知TCP/IP详细内容,请参见Tim Parker所著之<> (Sams Publish- ing). 端口和套接字 网络程序设计全靠套接字接受和发送信息.尽管套接字这个词好象显得有些神秘,但其实 这个概念极易理解. 大多数网络应用程序使用两个协议:传输控制协议(TCP)和用户数据包协议(UDP).他们都 使用一个端口号以识别应用程序.端口号为主机上所运行之程序所用,这样就可以通过号码 象名字一样来跟踪每个应用程序.端口号让操作系统更容易的知道有多少个应用程序在使用 系统,以及哪些服务有效. 理论上,端口号可由每台主机上的管理员自由的分配.但为了更好的通信通常采用一些约 定的协议.这些协议使能通过端口号识别一个系统向另一个系统所请求的服务的类型.基于 如此理由,大多数系统维护一个包含端口号及它们所提供哪些服务的文件. 端口号被从1开始分配.通常端口号超出255的部分被本地主机保留为私有用途.1到255之 间的号码被用于远程应用程序所请求的进程和网络服务.每个网络通信循环地进出主计算机 的TCP应用层.它被两个所连接的号码唯一地识别.这两个号码合起来叫做套接字.组成套接 字的这两个号码就是机器的IP地址和TCP软件所使用的端口号. 因为网络通讯至少包括两台机器,所以在发送和接收的机器上都存在一个套接字.由于每 台机器的IP地址是唯一的,端口号在每台机器中也是唯一的,所以套接字在网络中应该是唯 一的.这样的设置能使网络中的两个应用程序完全的基于套接字互相对话. 发送和接收的机器维护一个端口表,它列出了所有激活的端口号.两台机器都包括一个进 程叫做绑定,这是每个任务的入口,不过在两台机器上恰恰相反.换句话说,如果一台机器的 源端口号是23而目的端口号被设置成25,那么另一台机器的源端口号设置成25目的端口号设 置成23. 套接字程序设计 Linux支持伯克利(BSD)风格的套接字编程.它同时支持面向连接和不连接类型的套接字. 在面向连接的通讯中服务器和客户机在交换数据之前先要建立一个连接.再不连接通讯中数 据被作为信息的一部分被交换.无论那一种方式,服务器总是最先启动,把自己绑定(Banding )在一个套接字上,然后侦听信息.服务器究竟怎样试图去侦听就得依靠你编程所设定的连接 的类型了. 你需要了解一些系统调用 socket() bind() listen() accept() setsockopt()和getsockopt() connect() sendto() recvfrom() 我们将在以下的例子中使用这些系统调用. socket()系统调用 socket()系统调用为客户机或服务器创建一个套接字,套接字函数在如下定义: #include#includeint socket(int family, int type, int protocol) 在Linux中family=AF_UNIX.type可以是SOCK_STREAM它是可靠的虽然通讯速度较慢,也可 以是SOCK_DGRAM它通讯速度较快但不可靠.如果type=SOCK_STREAM那么protocol=IPPROTO_ TCP.如果type=SOCK_DGRAM那么protocol=IPPROTO_UDP. 如果出错,函数将返回-1.否则返回一个套接字描述符你可以在程序后面的调用中通过套 接字描述符使用这个套接字. 套接字创建时没有指定名字.客户机用套接字的名字读写它.这就是如下绑定函数所要做 之事. bind()系统调用 bind()系统调用为没有名字的套接字分配一个名字.绑定函数是这样定义的: #include#includeint bind(int sockfd, struct sockaddr *saddr, int addrlen) 第一个参数是一个套接字描述符.第二个参数是名字所用的一个结构.第三个参数是结构 的大小. 现在你已经为你的客户机或服务器限定了一个地址,你可以connect()服务器或是在服务 器上侦听了.如果你的程序是一个服务器,那么它把自己设置为侦听然后等待连接.让我们来 看看哪些函数能让你试图这样做. listen()系统调用 listen()系统调用被服务器所使用.下面有它的定义: #include#includeint listen(int sockfd, int backlog); sockfd是套接字描述符.backlog是在一时间内尚未被决定是否拒绝的连接的号码.一般 使用标准值5.如发生错误则返回值小于1. 如果这个调用成功,你就已经可以接受连接了. accept()系统调用 accept()调用被服务器用于接受任何从客户机connect()调用所引入的信息.必须明白的 是,如果没有接受到连接这个函数将不返回任何值.它是象这样定义的: #include#includeint accept(int sockfd, struct sockaddr *peeraddr, int addrlen) 除peeraddr指向发出连接请求的客户机的信息外,其它参数和bind()系统调用的相同.在 信息引入的基础上,peeraddr所指向的结构的域被填上相应的值. setsockopt()和getsockopt()系统调用 Linux所提供的socket库含有一个错误(bug).此错误表现为你不能为一个套接字重新启 用同一个端口号,即使在你正常关闭该套接字以后.例如,比方说,你编写一个服务器在一个 套接字上等待的程序.服务器打开套接字并在其上侦听是没有问题的.无论如何,总有一些原 因(不管是正常还是非正常的结束程序)使你的程序需要重新启动.然而重启动后你就不能把 它绑定在原来那个端口上了.从bind()系统调用返回的错误代码总是报告说你试图连接的端 口已经被别的进程所绑定. 问题就是Linux内核在一个绑定套接字的进程结束后从不把端口标记为未用.在大多数UN IX系统中,端口可以被一个进程重复使用,甚至可以被其它进程使用. 在Linux中绕开这个问题的办法是,但套接字已经打开但尚未有连接的时候用setsockopt ()系统调用在其上设定选项(options).setsockopt()调用设置选项而getsockopt()从给定 的套接字取得选项. 这里是这些调用的语法: #include#includeint getsockopt(int sockfd, int level, int name , char *value, int *optlen) int setsockopt(int sockfd, int level, int name , char *value, int *optlen) sockfd必须是一个已打开的套接字.level是函数所使用的协议标准(protocol level)(T CP/IP协议使用IPPROTO_TCP,套接字标准的选项实用SOL_SOCKET),选项的名称(name)在套接 字说明书中(man page)有详细说明.*value指向为getsockopt()函数所获取的值或setsocko pt()函数所设置的值的地址.optlen指针指向一个整数,该整数包含参数以字节计算的长度. 其值被getsockopt()设置且其值必须被程序员设定当使用一个经由setsockopt(). 选项的所有细节可以在使用手册中setsockopt的第二节(setsockopt(2))找到. 现在我们再回到Linux的错误上来.当你打开一个套接字时必须同时用下面的代码段来调 用setsockopt()函数: #ifdef LINUX opt = 1; len = sizeof(opt); setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt ,&len); #endif 只有当你想让程序不光是在Linux系统下使用时,#ifdef和#endif描述才是必须的.有些U NIX系统可能不支持或不需要SO_REUSEADDR标志. connect()系统调用 connect()调用被在面向连接的系统中客户机连接服务器时使用.connect()调用必须被 用在bind()调用之后.它是这样定义的: #include#includeint connect(int sockfd, struct sockaddr *servs addr, int addrlen) 除servsaddr外所有的参数都和bind调用相同,servsaddr指向客户机所连接的服务器的 信息.当接到请求后,accept调用为服务器创建一个新的套接字.然后服务器就可以fork()一 个新进程然后再去等待其它连接.在服务器端你可以象程序清单59.1所显示的那样编写代码 程序清单59.1 面向套接字协议的服务器端 #include#include#include#include#define MY_PORT 6545 main(int argc, char *argv[]) { int sockfd, newfd; int cpid; /* child id */ struct sockaddr_in servaddr; struct sockaddr_in clientInfo; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0) < 0) { myabort("Unable to create socket"); } #ifdef LINUX opt = 1; len = sizeof(opt); setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len); #endif bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_family = htons(MY_PORT); /* * The htonl (for a long integer) and htons (for short integer) convert * a host oriented byte order * into a network order. */ if (bind(sockfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr)) < 0) { myabort("Unable to bind socket"); } listen(sockfd,5); for (;;) { /* wait here */ newfd=accept(sockfd,(struct sockaddr *)&clientInfo, sizeof(struct sockaddr); if (newfd < 0) { myabort("Unable to accept on socket"); } if ((cpid = fork()) < 0) { myabort("Unable to fork on accept"); } else if (cpid == 0) { /* child */ close(sockfd); /* no need for original */ do_your_thing(newfd); exit(0); } close(newfd); /* in the parent */ } 在面向连接的协议的程序中,服务器执行以下函数: 调用socket()函数创建一个套接字. 调用bind()函数把自己绑定在一个地址上 调用listen()函数侦听连接 调用accept()函数接受所有引入的请求 调用read()函数获取引入的信息然后调用write()回答 现在让我们来看看客户端所要做的事情,见程序清单59.2. 程序清单59.2 客户端函数 #include#include#include#include#define MY_PORT 6545 #define MY_HOST_ADDR "204.25.13.1" int getServerSocketId() { int fd, len; struct sockaddr_in unix_addr; /* create a Unix domain stream socket */ if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { return(-1); } #ifdef LINUX opt = 1; len = sizeof(opt); setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len); #endif /* fill socket address structurew/our address */ memset(&unix_addr, 0, sizeof(unix_addr)); unix_addr.sin_family = AF_INET; /* convert internet address to binary value*/ unix_addr.sin_addr.s_addr = inet_addr(MY_HOST_ADDR); unix_addr.sin_family = htons(MY_PORT); if (bind(fd, (struct sockaddr *) &unix_addr, len) < 0) return(-2); memset(&unix_addr, 0, sizeof(unix_addr)); if (connect(fd, (struct sockaddr *) &unix_addr, len) < 0) return(-3); return(fd); } 在面向连接的通信中客户机要做如下一些事: 调用socket()函数创建一个套接字 调用connect()函数试图连接服务器 如果连接成功调用write()函数请求数据,调用read()函数接收引入的应答 不连接(Connectionless)套接字程序设计 现在让我们来考虑一下不连接的信息交换.其服务器端的原理和面向连接的协议有所不 同.服务器并不调用listen和accept而是调用recvfrom().同样,服务器用sendto()函数来 应答信息.服务器端程序见程序清单59.3. 程序清单59.3 服务器端 #include#include#include#include#define MY_PORT 6545 #define MAXM 4096 char mesg[MAXM]; main(int argc, char *argv[]) { int sockfd, newfd; int cpid; /* child id */ struct sockaddr_in servaddr; struct sockaddr_in clientInfo; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0) < 0) { myabort("Unable to create socket"); } #ifdef LINUX opt = 1; len = sizeof(opt); setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len); #endif bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_family = htons(MY_PORT); /* * The htonl (for a long integer) and htons (for short integer) convert * a host oriented byte order * into a network order. */ if (bind(sockfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr)) < 0) { myabort("Unable to bind socket"); } for (;;) { /* wait here */ n = recvfrom(sockfd, mesg, MAXM, 0, (struct sockaddr *)&clientInfo, sizeof(struct sockaddr)); doSomethingToIt(mesg); sendto(sockfd,mesg,n,0, (struct sockaddr *)&clientInfo, sizeof(struct sockaddr)); } } 看见了吗,处理每个消息只调用了两个函数,这比面向连接的协议更容易.但你必须,无论 如何,得在同一时间处理每个消息,因为消息从多台客户机向服务器涌来.而在面向连接的协 议中,子进程总是知道每条消息从哪里来. 客户机同样不能调用connect()系统调用.但是客户机可以直接调用sendto()函数.客户 机端和服务器端大致相同.只是它在调用recvfrom()之前调用sendto(): #include#includeint sendto((int sockfd, const void *message__, /* the pointer to message */ int length, /* of message */ unsigned int type, /* of routing, leave 0 */ const struct sockaddr * client, /* where to send it */ int length ); /* of sockaddr */ 注意:如果你使用的是BSD系统,请使用sendto()系统调用,不要使用sendmsg(),因为send to()性能更好. 如出错则返回-1,不过仅能检查出本地错误. recvfrom()系统调用是这样定义的: #include#includeint recvfrom(int sockfd, const void *message__, /* the pointer to message */ int length, /* of message */ unsigned int flags, /* of routing, leave 0 */ const struct sockaddr * client, /* where to send it */ int length ); /* of sockaddr */ 如果一个信息大得缓冲区都放不下,那么附加信息将被砍掉.该调用可以立即返回,也可 以永久的等待.这取决于你把flags设置成什么类型.你甚至可以设置超时(timeout)值.在说 明书(man pages)中可以找到recvfrom的更多信息. 在此你已学会了如何利用Linux的性能上的优点设计网络应用程序的基本知识.我们不打 算再进一步的描述更复杂的网络编程了.获得更多细节信息的一个极好的起点是参考W. Ric hard Stevens 的<>(Prentice Hall, 1990).此书乃众多大学所使用 的经典教材,内容极为详尽. 记录和文件锁定 当两个进程共享一个文件时,这之中存在一件非常危险的事情.如果一个进程改变了文件 目录那么必然影响到另一个进程.基于此理由,大多数操作系统采用一个互斥原则(mutually exclusive principle):当一个进程拥有一个文件时,其它进程就不能再碰这个文件.这叫做 文件锁定. 这个技术非常容易实现.通常所发生的事是,所谓"锁定文件"就是创建一个和源文件名同 名的文件再加上.lock扩展名.这就告诉其它进程这个文件不能再碰了.Linux假脱机打印系 统以及UUCP就是这样实现文件锁定的.这可能是一种粗暴的方法,但编程上非常简单. 不幸的是,你有几个进程要同时迅速的处理同一条信息时,这项技术对你并不实用.因为 等待文件打开和关闭所产生的延时将变得很长.同样一个进程如不能正确的释放文件,其它 进程将会挂在那里一直等待下去以获得存取权限. 由于这个原因,通常使用记录锁定.用记录锁定,一个大文件的一小部分被锁定以防止两 个进程同时改变它.如果有必要的话,记录锁定可以让多个进程同时存取相同文件的不同部 分记录.当然实现记录锁定编程要比实现文件锁定更复杂. 通常,要实现记录锁定你需要用到文件偏移量或是到文件起始处的字符数.在大多数程序 中,一个范围内的字符被锁定.程序记录锁定范围的起始处和长度,并保存在其它进程能查询 到的地方.不管是编写文件锁定还是记录锁定都需要对操作系统有很好的理解.但是并不难. 特别是可以从Internet,网络程序设计指导书和BBS上很容易地获得成千的程序.你可以察看 这些程序的源代码. 进程间通信 网络程序设计中通常包括两个或更多的进程将互相对话(interprocess communications ).因此进程通信的方法在网络程序设计中是极为重要的.网络程序设计在一些重要的方面不 同于一般程序设计通常所使用的方法.一个传统的程序可以通过全局变量或函数调用和不同 的模块(甚至同一机器上的其它应用程序)对话.但是在网络上却不行. 网络程序设计的一个重要的目标是保证进程间不互相干涉.否则系统可能被挂起或自锁. 因此,进程间必须使用简洁有效的方法进行通信.在此方面,UNIX具有非常显著的健壮性.因 为UNIX的许多基本性能如管道,队列等都非常适合网络. 和单个的应用程序代码相比,写进程间通信的代码十分复杂.如果你想写这类程序,可以 学习网络程序设计指导书和BBS站点上的例子程序.以了解这些任务是何以完成的. 小结 很少有人想写网络应用程序,因此进程的细节最好留给那些想写的人.实践和查阅大量的 例子程序是开始写网络代码的最好的方法.但是要掌握这门技术却要花许多年时间.

归类于:Linux— zwell @ 2:27 am 评论(0) 2004年08月04日 网络安全编程技术

网络安全编程技术(一)

UNIX系统为程序员提供了许多子程序,这些子程序可存取各种安全属性.有
些是信息子程序,返回文件属性,实际的和有效的UID,GID等信息.有些子程序可
改变文件属性.UID,GID等有些处理口令文件和小组文件,还有些完成加密和解密.
本文主要讨论有关系统子程序,标准C库子程序的安全,如何写安全的C程序
并从root的角度介绍程序设计(仅能被root调用的子程序).

1.系统子程序

(1)I/O子程序
*creat():建立一个新文件或重写一个暂存文件.
需要两个参数:文件名和存取许可值(8进制方式).如:
creat(“/usr/pat/read_write”,0666) /* 建立存取许可方式为0666的文件 */
调用此子程序的进程必须要有建立的文件的所在目录的写和执行许可,置
给creat()的许可方式变量将被umask()设置的文件建立屏蔽值所修改,新
文件的所有者和小组由有效的UID和GID决定.
返回值为新建文件的文件描述符.
*fstat():见后面的stat().
*open():在C程序内部打开文件.
需要两个参数:文件路径名和打开方式(I,O,I&O).
如果调用此子程序的进程没有对于要打开的文件的正确存取许可(包括文
件路径上所有目录分量的搜索许可),将会引起执行失败.
如果此子程序被调用去打开不存在的文件,除非设置了O_CREAT标志,调用
将不成功.此时,新文件的存取许可作为第三个参数(可被用户的umask修
改).
当文件被进程打开后再改变该文件或该文件所在目录的存取许可,不影响
对该文件的I/O操作.
*read():从已由open()打开并用作输入的文件中读信息.
它并不关心该文件的存取许可.一旦文件作为输入打开,即可从该文件中读
取信息.
*write():输出信息到已由open()打开并用作输出的文件中.同read()一样
它也不关心该文件的存取许可.
(2)进程控制
*exec()族:包括execl(),execv(),execle(),execve(),execlp()和execvp()
可将一可执行模快拷贝到调用进程占有的存贮空间.正被调用进
程执行的程序将不复存在,新程序取代其位置.
这是UNIX系统中一个程序被执行的唯一方式:用将执行的程序复盖原有的
程序.
安全注意事项:
. 实际的和有效的UID和GID传递给由exec()调入的不具有SUID和SGID许
可的程序.
. 如果由exec()调入的程序有SUID和SGID许可,则有效的UID和GID将设
置给该程序的所有者或小组.
. 文件建立屏蔽值将传递给新程序.
. 除设了对exec()关闭标志的文件外,所有打开的文件都传递给新程序.
用fcntl()子程序可设置对exec()的关闭标志.
*fork():用来建立新进程.其建立的子进程是与调用fork()的进程(父进程)
完全相同的拷贝(除了进程号外)
安全注意事项:
. 子进程将继承父进程的实际和有效的UID和GID.
. 子进程继承文件方式建立屏蔽值.
. 所有打开的文件传给子进程.
*signal():允许进程处理可能发生的意外事件和中断.
需要两个参数:信号编号和信号发生时要调用的子程序.
信号编号定义在signal.h中.
信号发生时要调用的子程序可由用户编写,也可用系统给的值,如:SIG_IGN
则信号将被忽略,SIG_DFL则信号将按系统的缺省方式处理.
如许多与安全有关的程序禁止终端发中断信息(BREAK和DELETE),以免自己
被用户终端终止运行.
有些信号使UNIX系统的产生进程的核心转储(进程接收到信号时所占内存
的内容,有时含有重要信息),此系统子程序可用于禁止核心转储.
(3)文件属性
*access():检测指定文件的存取能力是否符合指定的存取类型.
需要两个参数:文件名和要检测的存取类型(整数).
下面还有喔 (19%) │ 结束 ← q │ ↑/↓/PgUp/PgDn 移动 │ ? 辅助说明 │
需要两个参数:文件名和要检测的存取类型(整数).
存取类型定义如下:
0: 检查文件是否存在
1: 检查是否可执行(搜索)
2: 检查是否可写
3: 检查是否可写和执行

4: 检查是否可读
5: 检查是否可读和执行
6: 检查是否可读可写可执行
这些数字的意义和chmod命令中规定许可方式的数字意义相同.
此子程序使用实际的UID和GID检测文件的存取能力(一般有效的UID和GID
用于检查文件存取能力).
返回值: 0:许可 -1:不许可.
*chmod():将指定文件或目录的存取许可方式改成新的许可方式.
需要两个参数:文件名和新的存取许可方式.
*chown():同时改变指定文件的所有者和小组的UID和GID.(与chown命令不
同).
由于此子程序同时改变文件的所有者和小组,故必须取消所操作文件的SUID
和SGID许可,以防止用户建立SUID和SGID程序,然后运行chown()去获得别
人的权限.
*stat():返回文件的状态(属性).
需要两个参数:文件路径名和一个结构指针,指向状态信息的存放
的位置.
结构定义如下:
st_mode: 文件类型和存取许可方式
st_ino: I节点号
st_dev: 文件所在设备的ID
st_rdev: 特别文件的ID
st_nlink: 文件链接数
st_uid: 文件所有者的UID
st_gid: 文件小组的GID
st_size: 按字节计数的文件大小
st_atime: 最后存取时间(读)
st_mtime: 最后修改时间(写)和最后状态的改变
st_ctime: 最后的状态修改时间
返回值: 0:成功 1:失败
*umask():将调用进程及其子进程的文件建立屏蔽值设置为指定的存取许可.
需要一个参数: 新的文件建立屏值.
(4)UID和GID的处理
*getuid():返回进程的实际UID.
*getgid():返回进程的实际GID.
以上两个子程序可用于确定是谁在运行进程.
*geteuid():返回进程的有效UID.
*getegid():返回进程的有效GID.
以上两个子程序可在一个程序不得不确定它是否在运行某用户而不是运行
它的用户的SUID程序时很有用,可调用它们来检查确认本程序的确是以该
用户的SUID许可在运行.
*setuid():用于改变有效的UID.
对于一般用户,此子程序仅对要在有效和实际的UID之间变换的SUID程序才
有用(从原有效UID变换为实际UID),以保护进程不受到安全危害.实际上该
进程不再是SUID方式运行.
*setgid():用于改变有效的GID.


网络安全编程技术(二)

2.标准C库

(1)标准I/O
*fopen():打开一个文件供读或写,安全方面的考虑同open()一样.
*fread(),getc(),fgetc(),gets(),scanf()和fscanf():从已由fopen()打
开供读的文件中读取信息.它们并不关心文件的存取许可.这一点
同read().
*fwrite(),put(),fputc(),puts,fputs(),printf(),fprintf():写信息到
已由fopen()打开供写的文件中.它们也不关心文件的存取许可.
同write().
*getpass():从终端上读至多8个字符长的口令,不回显用户输入的字符.
需要一个参数: 提示信息.
该子程序将提示信息显示在终端上,禁止字符回显功能,从/dev/tty读取口
令,然后再恢复字符回显功能,返回刚敲入的口令的指针.
*popen():将在(5)运行shell中介绍.

(2)/etc/passwd处理
有一组子程序可对/etc/passwd文件进行方便的存取,可对文件读取到入口
项或写新的入口项或更新等等.
*getpwuid():从/etc/passwd文件中获取指定的UID的入口项.
*getpwnam():对于指定的登录名,在/etc/passwd文件检索入口项.
以上两个子程序返回一指向passwd结构的指针,该结构定义在
/usr/include/pwd.h中,定义如下:
struct passwd {
char * pw_n

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

负责支持和保护网络生活的云服务提供商阿卡迈技术公司(Akamai),近日在其日益丰富的云产品阵容中又增添了一款基于NVIDIA GPU的媒体优化型产品。这款全新的云服务产品基于NVIDIA RTX 4000 Ada Ge...

关键字: 视频解码器

业内消息,近日高通公司宣布推出针对桌面平台的全新骁龙 X Plus 处理器。

关键字: 高通 骁龙 X Plus 处理器

近日,台积电在圣克拉拉年度技术研讨会上宣布首个“埃级”制程技术:A16。A16 是台积电首次引入背面电源输送网络技术,计划于 2026 年下半年开始量产。同时,台积电也在重新命名工艺节点,标志着「埃级」时代的开始。

关键字: 台积电 A16

4 月 25 日消息,4 月 25 日,国际数据公司(IDC)发布 2024 年第一季度中国手机市场跟踪报告,荣耀以 17.1% 的市场份额拿下第一,华为占 17.0% 位列第二,OPPO、苹果和 vivo 分别位列第三...

关键字: 荣耀 华为

业内消息, 近日华为全新Pura 70系列手机正式开售引发广大 数码爱好者追捧,但是有网友注意到这款手机的“AI修图”功能,竟然可以将照片中的人物衣服消除,并拍成视频发布网络。

关键字: 华为Pura70 华为

据韩媒报道,近日韩国多位军方人士透露,韩国军方正在考虑全面禁止在军事建筑内使用苹果手机,军方担心敏感信息通过录音泄露。

关键字: iPhone 苹果

为了满足日益增长的数据处理需求,铁威马NAS推出了全新的性能巅峰2024年旗舰之作F4-424 Pro,并搭载了最新的操作系统--TOS 6。这款高效办公神器的问世,无疑将为企业和专业人士带来前所未有的便捷与效率。

关键字: 存储 Linux 服务器

继“特斯拉开启万人大裁员”之后,如今又一家车企扛不住了!

关键字: 电动汽车

轻量级AI定制模型助力低成本快速部署 北京2024年4月18日 /美通社/ -- 数据和人工智能(AI)领域的领先者SAS—今日在SAS Innovate大会上首次展示了一款变革性的、旨在帮助企业直面业务挑战的解决方案...

关键字: SAS AI模型 人工智能 INNOVATE

领先的数据和AI平台SAS Viya运用大语言模型的可解释性和可集成性改善现有流程;SAS Data Maker将在保护敏感数据的同时解决关键挑战 北京2024年4月18日...

关键字: SAS VI 生成式AI MAKER
关闭
关闭