一、socket函数

作用:为了执行网络I/O,一个进程必须做的第一件事情就是调用socket函数,指定期望的通信协议类型

#include<sys/socket.h>
int socket(int family,int type,int protocol);
//返回:若成功则为非负描述符,若出错为-1

其中family参数指明协议族,该参数还被称为协议域。(AF_前缀表示地址族)

faily 说明
AF_INET

IPv4协议

AF_INET6 ipv6协议
AF_LOCAL Unix域协议
AF_ROUTE 路由套接字
AF_KEY 密钥套接字

type参数指明套接字类型

type 说明
SOCK_STREAM 字节流套接字
SOCK_DGRAN 数据报套接字
SOCK_SEQPACKET 有序分组套接字
SOCK_RAW 原始套接字

protocol参数值某个协议类型常值,或设为0

protocol 说明
IPPROTO_CP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议

socket函数中family和type参数组合

  AF_INET AF_INET6 AF_LOCAL AF_ROUTE AF_KEY
SOCK_STREAM TCP|SCTP TCP|SCTP    
SOCK_DGRAN UDP UDP    
SOCK_SEQPACKET SCTP SCTP    
SOCK_RAW IPv4 IPv6  

二、connect函数

作用:TCP客户用connect函数来建立与TCP服务器的连接。

#include<sys/socket.h>
int connect(int sockfd,const struct sockaddr *servaddr,socklen_t addrlen);
//返回:若成功则为0,若出错则为-1

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

套接字地址结构必须含有服务器的IP地址和端口号(fd可以理解为文件描述符)

TCP套接字:

调用connect函数将激发TCP的三路握手过程,而且仅在建立成功或出错时才返回。其中出错返回可能的情况如下。

  • 若TCP客户没有收到SYN分节的响应,则返回ETIMEDOUT。
  • 若对客户的SYN的响应是RST(表示复位),则表明该服务器主机在我们指定的端口上没有进程在等待与之连接(例如服务器进程也许没在运行)
  • 若客户发出的SYN在中间的某一路由器上引发一个“destination unreachable“(目的地不可达)ICMP错误,则认为是一种软错误(soft error)。

 connect函数导致当前套接字从CLOSED状态(该套接字自从由socket函数创建以来一直所处的状态)转移到SYN_SENT状态

若成功,则在转移到ESTABLISHED状态

若失败,则该套接字不再可用,必须关闭,我们不能对这样的套接字再次调用connect函数。

三、bind函数

作用:把一个本地协议地址赋予一个套接字。

对于网际网协议,协议地址是32位的IPv4地址或128位的IPv6地址与16位的TCP或UDP端口号组合。

#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
//返回:若成功则为0,若出错则为-1

sockfd是socket函数返回套接字的描述符。第二个参数是一个指向特定于协议的地址结构的指针,第三个参数时该地址结构的长度。

对于TCP,调用bind函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以都不指定。

如何根据预期结果,设置sin_addr和sin_port或者sin6_addr和sin6_port的值

(给bind函数指定要捆绑的IP地址和/或端口号产生的结果)

进程指定 结果
IP地址 端口
通配地址 0 内核选择IP地址和端口
通配地址 非0 内核选择IP地址,进程指定端口
本地IP地址 0 进程指定IP地址,内核选择端口
本地IP地址 非0 进程指定IP地址和端口

四、listen函数

作用:仅由TCP服务器调用

listen做的两件事

1)当socket函数创建一个套接字时,它被假设为一个主动套接字,它是一个被调用connect发起连接客户套接字。listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应该接受指向该套接字的连接要求。

根据TCP状态转换图,调用listen导致套接字从CLOSED状态转换到LISTEN状态。

2)本函数的第二个参数规定了内核应该为相应套接字排队的最大连接个数

#include<sys/socket.h>
int listen(int sockfd,int backlog);
//返回:若成功则为0,若出错则为-1

listen函数通常在调用socket函数和bind函数之后,并在调用accept函数之前调用。

为了理解backlog参数,我们必须认识到内核为任何一个给定的监听套接字维护两个队列:

1)未完成连接队列(incomplete connection queue),每个这样的SYN分节对于其中一项

2)已完成连接队列(completed connection queue),每个已完成TCP三路握手过程的客户对应其中一项。这些套接字处于ESTABLISHED状态。

五、accept函数

作用:accept函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠(假定套接字为默认的阻塞方式)

#incldue<sys/socket.h>
int accpet(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);
//返回:若成功则返回非负描述符,若出错则为-1

参数cliaddr和addrlen用来返回已连接的对端进程(客户)的协议地址。

addrlen是值-结果参数:调用前,我们将由*addrlen所引用的整数值置为cliaddr所指的套接字地址结构的长度,返回时,该整数值即为由内核存放在该套接字地址结构的确切字节数。

如果accept成功,那么其返回值是由内核自动生成的一个全新的描述符,代表与所返回客户的TCP连接。

本函数最多返回三个值:一个既可能是新套接字描述符也可能是出错知识的整数、用户进程的协议地址(由cliaddr指针所指)以及该地址的大小。

如果我们对返回客户协议地址不感兴趣,可以把cliaddr和addrlen均置为空指针。

六、fork和exec函数

fork函数作用:(包括有些系统可能提供的它的各种变体)是UNIX中派生新进程的唯一方法。

#include<unistd.h>
pid_t fork(void);
//返回:在子进程中为0,在父进程中为子进程的ID,若出错则为-1

fork函数的一个特点就是返回两次。它在调用进程(称为父进程)中返回一次,返回值是新派生进程(称为子进程)的进程ID号,在子进程又返回一次,返回值为0.返回值本身告知当前进程是子进程还是父进程。

fork函数的两个典型用法:

1)一个进程创建一个自身的副本,这样每个副本都可以在另一个副本执行其他任务的同时处理各自的某个操作。

2)一个进程想要执行另一个程序。既然创建新进程的唯一办法是调用fork,该进程于是首先调用fork创建一个自身的副本,然后其中一个副本(通常为子进程)调用exec把自身替换成新的程序。

exec函数

存放在硬盘上的可执行程序文件能够被UNIX执行的唯一方法:由一个现有进程调用留个exec函数中的某一个。

exec把当前进程映像替换成新的程序文件,而且该新程序通常从main函数开始执行。进程ID不改变。我们称调用exec的进程为调用进程,称新执行的程序为新程序。

这六个exec函数之间的区别:

待执行的程序文件是由文件名还是路由名指定

新程序的参数时意义列出还是有一个指针数组来引用

把调用进程的环境传递给新程序还是个新程序指定新的环境

#include<unistd.h>
int execl(const char *pathname,const char *arg0,..);
int execv(const char *pathname,char *const *argv[]);
int execle(const char *pathname,const char *arg0,...);
int execve(const char *pathname,char *const argv[],char *const envp[]);
int execlp(const char *filename,const char *arg0,...);
int execvp(const char *filename,char *const argv[]);
//均返回:若成功则不返回,若出错则为-1

七、并发服务器

典型的并发服务器程序轮廓

pid_t pid;
int listenfd,connfd;
listenfd=Socket(...);
Bind(listenfd,..);
Listen(listened,LISTENQ);
for( ; ; )
{
 connfd=Accept(listenfd,...);
if((pid=Fork())==0)
  {  Close(listenfd);
     doit(connfd);
     Close(connfd);
     exit(0);
   }
  Close(connfd);
}

并发服务器是同时调用父进程和子进程。

八、close函数

作用:用来关闭套接字,并终止TCP连接。

#include<unistd.h>
int close(int sockfd);
//返回:若成功则为0,若出错则为-1。

close一个TCP套接字的默认行为是把该套接字标记为已关闭,然后立即返回到调用进程。

九、getsockname和getpeername函数

这两个函数或者返回与某个套接字关联的本地协议地址,或者返回与某个套接字关联的外地协议地址

#include<sys/socket.h>
int getsockname(int sockfd,struct sockaddr *localaddr,socklen_t *addrlen);
int getpeername(int sockfd,struct sockaddr *peeraddr,socklen_t *addrlen);
//均返回:若成功则为0,若出错则为-1

注意,两个函数最后一个参数都是值-结果参数。这两个函数都得装填有localaddr或peeraddr指针所指的套接字地址结构。

获取套接字的地址族

sockfd_to_family函数返回某个套接字的地址族

#include "unp.h"
int
sockfd_to_family(int sockfd)
{
   struct sockaddr_storage ss;
   socklen_t len;
   len=sizeof(ss);
   if(getsockname(sockfd,(SA*)&ss,&len)<0)
      return(-1);
   return(ss.ss_family);
}