unix网络编程——TCP套接字编程

  TCP客户端和服务端所需的基本套接字。服务器先启动,以后的某个时刻客户端启动并试图链接到服务器。以后客户端向服务器发送请求,服务器处理请求,并给客户端一个响应。该过程一直持续下去,直到客户端关闭,给服务端发送EOF(文件结束),服务器也关闭链接的服务器端,而后结束运行或者等待新的客户发起链接请求。如图1所示:服务器

图1 TCP网络套接字示意图网络

  在图中涉及到不一样的函数,接下来进行详细的介绍。socket


 

socket函数

  为了进行网络I/O,进程首先须要调用socket函数,指定使用的通讯协议类型(IPv4的TCP、IPv6的UDP、Inux域字节流协议等)。函数

#include<sys/socket.h> int socket(int family, int type, int protocol); 返回:若成功返回非负数,若失败返回-1 

   family表示协议族,协议族取值如表1所示:spa

family 说明
AF_INET

IPv4协议unix

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

表1 协议族family取值指针

  type表示套接字类型,套接字类型type如表2所示:blog

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

表2 套接字类型接口

  protocol表示某个协议类型常值,或者设置为0,以选择family和type组合的系统默认值,但并非全部的family和type组合都是有效的,表3给出了正确组合。队列

这里写图片描述表3 偷来的截图

  socket函数调用成功后返回一个小的非负整数值,称为套接字描述符(socket descriptor),简称sockfd。指定了协议族(IPv四、Ipv6或Unix)和套接字类型(字节流、数据报或原始套接字),并无指定本地协议地址或远程协议地址。


 

connect函数

  TCP客户端使用connect函数来创建与TCP服务器之间的链接。

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

  sockfd:socket函数返回的套接字描述符

  servaddr:套接字地址结构的指针

  addrlen:套接字地址结构的大小

  套接字地址结构必须含有服务器的IP地址和端口号。客户端在调用connect函数前没必要非要调用bind函数,由于若是须要的话,内核会确认源IP地址,并选择一个临时端口做为源端口。

  若是是TCP套接字,调用connect函数会激发TCP三次握手,并且仅在链接创建成功或失败时才会返回。


bind函数

  bind函数将一个本地协议地址赋予一个套接字,对于网际协议,协议地址是32位的Ipv4地址或128位的IPv6地址与16位的TCP或UDP端口号的组合。

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

  sockfd:socket函数返回的套接字描述符

  servaddr:套接字地址结构的指针

  addrlen:套接字地址结构的大小

  对于TCP,调用bind函数能够指定一个端口号和一个IP地址,也能够不指定。

  服务器在启动时绑定它们的众所周知的端口,当调用connect或listen的时候,内核会为相应的套接字选择一个临时端口。让内核选择临时端口对于TCP客户端来讲很正常,除非应用须要一个预留端口,然而对于TCP服务器来讲却极为罕见,由于服务器是经过它们众所周知的端口被你们认识的。

(例外状况:RPC服务器,它们一般就由内核为它们的监听套接字选择一个临时端口,而该端口随后经过RPC端口映射器进行注册。客户在connect这些服务器以前,必须与端口映射器联系来获取它们的临时端口,这种状况也是用与UDP的RPC服务器)

  进程能够将一个特定的IP地址绑定到它的套接字上,不过这个IP地址必须属于其所在主机的网络接口之一。对于TCP客户端,这就为在该套接字上发送的IP数据报指派了源IP地址,对于TCP服务器,这就限定该套接字只接收那些目的地为这个IP地址的客户链接。TCP客户一般不把IP地址绑定到套接字上。当链接套接字的时候,内核将根据所用外出网络接口选择源IP地址,而全部外出接口则取决于到服务器所需的路径。若是TCP服务器没有把IP地址绑定到套接字上,内核就把客户端发送的SYN的目的IP地址做为服务器的源IP地址。

  若是指定端口为0,那么内核就在bind被调用时选择一个临时端口。然而若是指定IP地址为通配地址,那么内核将等到套接字已链接(TCP)或已在套接字上发出数据报(UDP)时才选择一个本地IP地址。

  对于IPv4来讲,通配地址由常值INADDR_ANY来指定,其值通常为0。它告知内核去选择IP地址。


 listen函数

 

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

 

  sockfd:socket函数返回的套接字描述符

  backlog:内核应为响应套接字排队的最大链接个数

 

  listen函数一般在调用socket函数和bind函数以后,调用accept函数以前调用  

listen函数仅由TCP服务器调用,它作两件事情:

  当socket函数建立一个套接字的时候,它被假设为一个主动套接字,也就是说,它是一个将调用connect发起链接的客户端套接字。listen函数把一个未链接的套接字转换成一个被动套接字,指示内核应接受指向该套接字的链接请求。调用listen函数使得套接字从CLOSED状态转到LISTEN状态。

图2 TCP状态转换图

  为了理解backlog参数,必须为内核维护两个队列:

  (1)未完成链接队列(incomplete connection queue),每一个这样的SYN分节对应其中一项:已由某个客户端发起并达到服务器,而服务器正在等待完成相应的TCP三次握手过程。这些套接字处于SYN_RCVD状态(见图2);

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


 

 accept函数

  accept函数由TCP服务器调用,用于从已完成链接队列的头部返回下一个已链接,若是已链接队列为空,那么进程休眠(若是套接字是默认的阻塞方式)。

#include<sys/socket.h> int accept(int sockfd, struct *cliaddr, socklen_t *addrlen); 返回:若成功返回非负描述符,若失败返回-1

  sockfd:socket函数返回的套接字描述符

  cliaddr:返回已链接的对端(客户端)的协议地址

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


 

 fork和exec函数

  fork函数是unix派生新进程的惟一方法。

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

  fork函数调用一次,会返回两次。在父进程(调用进程)中返回一次,返回值是子进程(新派生进程)的进程ID号;在子进程中又返回一次,返回值为0。所以能够经过返回值来肯定是父进程仍是子进程。

  fork函数在父进程和子进程中返回值不一样的缘由:任何子进程只有一个父进程,并且子进程老是能够经过调用getppid取得父进程的进程ID,反之,父进程有不少子进程,并且没法获取各个子进程的进程ID。若是父进程想要追踪全部子进程的进程ID,那么必须记录每次调用fork的返回值(由于在每次调用fork的时候,父进程会返回子进程的ID号,若是每次都进行记录,那么就能够追踪全部子进程的进程ID)。

  父进程中调用fork以前打开的全部描述符在fork返回以后由子进程分享,咱们将看到网络服务器利用这个特性:父进程调用accept以后调用fork,所接受的已链接套接字随后在父进程和子进程之间共享。一般状况下,子进程接着读写这个已链接套接字,父进程则关闭这个已链接套接字。

  fork的典型用法:

  (1)一个进程建立一个自身的副本,这样每一个副本均可以在另外一个副本执行其余任务的同时处理各自的某个操做。这是网络服务器的典型用法。

  (2)一个进程想要执行另外一个程序。既然建立新进程的惟一方法是调用fork,该进程因而首先调用fork建立一个自身副本,而后其中一个副本(一般称为子进程)调用exec把自身替换成新的程序,

 

相关文章
相关标签/搜索