accept函数_基础套接字函数入门2

想知道一个程序a.out是怎么从shell终端运行的吗?想知道并发服务的框架吗?想知道服务器怎么知道客户端的IP地址和端口吗?

1 fork函数

// /usr/include/unistd.h
extern __pid_t fork (void) __THROWNL;

调用一次fork有2次返回:

  • 在调用进程(父进程)中返回子进程的ID号,
  • 在子进程中返回0。

父进程中调用fork,之前打开的所有描述符在fork返回后由子进程共享。fork有两种经典用法:

  1. 一个进程创建一个自己的副本,每个副本独立工作。
  2. 一个进程想要执行另一个程序。先调用fork,然后子进程调用exec,把自己替换成新的程序。

面试题:存放在硬盘上的可执行程序文件能够被UNIX执行的唯一办法是什么?

调用exec函数(共有6种)中的某一个,exec把当前进程映像替换成新的程序文件,而且该新程序通常从main函数开始执行。进程ID并不改变,我们称调用exec的进程为调用进程,称新执行的程序为新程序。注意哈,这里没有创建新进程哦,只有fork才会。

2 exec函数

6个exec函数之间的区别:

  • 带执行的程序文件是文件名还是路径名。
  • 新程序的参数是一个一个列出还是一个指针数组。
  • 把调用进程的环境传递给新程序还是给新程序指定新的环境变量。

一般来说,只有execve是内核中的系统调用,其他5个都是调用execve的库函数。

注意:这里使用的指针数组,最后一个元素一定要是NULL,因为没有指定数组元素个数。

// /usr/include/unistd.h
/*路径名,指针数组,显示指定一个环境变量列表*/
extern int execve (const char *__path, char *const __argv[],char *const __envp[])
      __THROW __nonnull ((1, 2));
/*路径名,指针数组,使用外部变量environ作为新程序的环境变量*/
extern int execv (const char *__path, char *const __argv[])
      __THROW __nonnull ((1, 2));
/*路径名,参数,显示指定一个环境变量列表*/
extern int execle (const char *__path, const char *__arg, ...,char* const __envp)
      __THROW __nonnull ((1, 2));
/*路径名,参数,使用外部变量environ作为新程序的环境变量*/
extern int execl (const char *__path, const char *__arg, ...)
      __THROW __nonnull ((1, 2));
/*文件名,指针数组,使用外部变量environ作为新程序的环境变量*/
extern int execvp (const char *__file, char *const __argv[])
      __THROW __nonnull ((1, 2));
/*文件名,参数,使用外部变量environ作为新程序的环境变量*/
extern int execlp (const char *__file, const char *__arg, ...)
      __THROW __nonnull ((1, 2));

3 并发服务器简介

当一个连接建立时,accept返回,服务器接着调用fork,然后由子进程服务客户,父进程继续监听等待新的连接。需要注意的是fork之后,文件描述符的引用计数会自动加1,所以父进程需要close掉已经建立连接的connfd,子进程需要close掉监听描述符listenfd。
典型的并发服务器代码框架:

pid_t pid;
int listenfd,connfd;
listenfd = Sokcet(...);
Bind(listenfd,...);
Listen(listenfd,LISTENQ);
for(;;)
{
  connfd = Accept(listenfd,...);
  if( (pid = Fork()) == 0)
  {
    /*子进程*/
    Close(listenfd);
    doit(connfd);
    CLose(connfd);
    exit(0);
  }
  /*父进程*/
  Close(connfd);
}

4 getsockname函数

返回与某个套接字关联的本地协议地址,__len是值-结果参数,__addr需要被内核装填。

// sys/socket.h
extern int getsockname (int __fd, __SOCKADDR_ARG __addr,socklen_t *__restrict __len) 
__THROW;

使用场景:

  • 没有调用bind的TCP客户端:connect返回成功后,getsockname可以获取该连接的本地IP地址和本地端口号。(客户端)
  • 通配IP地址调用bind的TCP服务器:accept返回成功后,getsockname可以获取该链接的本地IP地址和本地端口号。(服务端)

5 getpeername函数

返回与某个套接字关联的外地协议地址,__len是值-结果参数,__addr需要被内核装填。

// sys/socket.h
extern int getpeername (int __fd, __SOCKADDR_ARG __addr,socklen_t *__restrict __len) 
__THROW;

使用场景:

  • 当一个服务器是由调用过accept的某个进程,通过调用exec执行程序时,它能够获取客户身份的唯一途径就是调用getpeername。

注意:getpeername需要connfd,这个fd获得有两种方法:

  1. 调用exec的进程可以把这个描述符格式化成一个字符串,再把它当做作为一个命令行参数传递给新程序。
  2. 约定在调用exec之前,总是把某个特定描述符置为所接受的已连接套接字的描述符。后面的inetfd采用该方法,总是把描述符0、1、2置为所接受的连接套接字的描述符。

例子:

运行结果:

b01dd99a88dee5b540d68fc9a75ebad5.png
值-结构参数:没有初始化
// 服务端代码
#include	"unp.h"
#include	<time.h>

int
main(int argc, char **argv)
{
	int					listenfd, connfd;
	struct sockaddr_in	cliaddr;
	struct sockaddr_in	servaddr;
        socklen_t clilen;
	char				buff[MAXLINE];
	time_t				ticks;

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(13);	/* daytime server */

	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

	Listen(listenfd, LISTENQ);
    
	for ( ; ; ) {
	  connfd = Accept(listenfd, (SA *) NULL, NULL);
          getpeername(connfd,(SA*) &cliaddr,&clilen);
          printf("client ip:%sn",sock_ntop((SA*) &cliaddr,clilen));
          ticks = time(NULL);
          snprintf(buff, sizeof(buff), "%.24srn", ctime(&ticks));
          Write(connfd, buff, strlen(buff));
	  Close(connfd);
	}
}
  • 咦?第一行什么鬼?我们说值-结果参数,这个值一定赋值对,要不然会出错。

声明变量的时候,给个初始化大小即可。socklen_t clilen = sizeof(cliaddr);

  • 那为什么其他的都正确呢?

因为这个clilen变量的作用域内,已经在第一次getpeername后被内核赋予正确的值了呀,下一次的getpeername的值-结果参数就没问题了,这样才能保证sockaddr_in地址结构被正确填写。

fb73f9339ec887c6b9d6e7b901fe1c75.png
值-结果参数,给对这个值很重要

参考文献:《UNIX网络编程 卷1:套接字联网API》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值